내 연락처 정보
우편메소피아@프로톤메일.com
2024-07-12
한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina
사전 환경 학습
QT TCP 다중 스레드 네트워크 통신-CSDN 블로그
WebSocket은 단일 TCP 연결을 통해 전이중 통신 채널을 제공하는 네트워크 기술입니다. 2011년 IETF는 WebSocket 프로토콜을 RFC6455로 표준화했으며, QWebSocket은 클라이언트 애플리케이션과 서버 애플리케이션 모두에서 사용할 수 있습니다.
브라우저와 서버 간의 전이중 통신을 구현하여 서버가 클라이언트에 사전에 정보를 보낼 수 있도록 합니다.
주요 특징:
HTTP와 달리 WebSocket을 사용하면 클라이언트가 요청을 시작하지 않고도 서버가 클라이언트에 데이터를 적극적으로 보낼 수 있습니다.
TCP 프로토콜을 기반으로 구축되어 서버와 클라이언트 간의 전이중 통신이 Ws(WebSocket) 프로토콜을 통해 단일 포트에서 수행됩니다.
텍스트 또는 바이너리 형식의 데이터 전송을 지원합니다.
프로토콜은 단일 TCP 연결을 기반으로 구축되었습니다. 서버와 클라이언트는 하나의 연결만 생성하면 되며 연결이 닫히지 않습니다.
JavaScript, Java, C#, Python 등과 같은 여러 프로그래밍 언어로 클라이언트 측 및 서버 측 라이브러리를 지원합니다.
일반적인 애플리케이션 시나리오:
채팅방: 지연 시간이 짧은 실시간 대화를 지원합니다.
온라인 게임: 게임 상태의 실시간 동기화가 필요한 동기화 엔진입니다.
주식 시세: 실시간 시세를 클라이언트에 푸시해야 하는 시세 소프트웨어입니다.
화상 회의: 지연 시간이 짧은 음성 및 영상의 실시간 통신이 필요합니다.
실시간 공동 편집: 예를 들어 온라인 코드 편집기에는 실시간 동기화가 필요합니다.
QWebSocket은 Qt에서 제공하는 WebSocket 함수 라이브러리입니다. 이는 Qt 네트워크 모듈을 기반으로 구축되었으며 RFC6455 표준에서 WebSocket 프로토콜을 구현합니다. Qmake 파일에 QT+=websockets를 추가해야 합니다.
QWebSocket은 텍스트/이진 메시지 형식만 지원하며 다른 확장 형식은 지원하지 않습니다. 다른 사용자 정의 프로토콜을 추가해야 하는 경우 개발자는 애플리케이션 계층에서 이를 직접 처리해야 합니다.
표준 http/https 포트 80/443을 사용하여 웹소켓 서비스에 대한 액세스를 지원하고 wss(암호화된 웹소켓) 프로토콜도 지원합니다. 따라서 기존 웹 서버와 쉽게 상호 작용할 수 있습니다.
QT CP 소켓 구현을 기반으로 하기 때문에 프록시 설정, SSL 구성 등 Qt 네트워크의 모든 기능을 완벽하게 지원합니다. 이는 일부 저수준 C 인터페이스보다 사용하기 쉽습니다.
능동 및 수동 연결 모드를 모두 지원합니다. 활성 연결은 connectToHost()를 통해 이루어지며, 수동 연결은 수신 포트 accept()를 통해 허용됩니다. 두 모드 모두 매우 편리합니다.
QT GUI 애플리케이션의 경우 메시지 수신 및 인터페이스 업데이트를 쉽게 수행할 수 있어 멀티스레드 프로그래밍의 복잡성을 피할 수 있습니다. 예를 들어 textMessageReceived()에서 직접 인터페이스를 업데이트하면 됩니다.
QT5.10 이후에는 비동기 I/O가 지원되며, 이전에 비해 성능이 소폭 향상되었습니다. 네트워크 확장에 대한 지원도 더 좋습니다.
-
- origin()
- 即 websocket=new QWebSocket("C1我是客户端",QWebSocketProtocol::VersionLatest,this);
- websocket->origin() -》 C1我是客户端
-
- void connectToHost(const QUrl &url) - 用于连接到指定主机的websocket服务,这个函数是异步的。
- void close() - 关闭与服务器的连接。
- void textMessageReceived(const QString &message) - 收到文本消息时触发的信号,其参数就是收到的文本消息内容。
- void binaryMessageReceived(const QByteArray &message) - 收到二进制消息时触发的信号,参数是原始二进制数据。
- void error(QAbstractSocket::SocketError socketError) - 发生错误时触发的信号,参数是错误类型。
- void stateChanged(QAbstractSocket::SocketState state) - 连接状态变化时触发,可以得知连接是否建立等。
- void textMessageSent(qint64 numBytes) - 发送文本消息完成后触发,numBytes是字节数。
- void bytesWritten(qint64 bytes) - 消息发送过程中的写入回调, bytes是一个部分发送出去的字节数。
- void abort() - 主动断开连接。
- bool waitForConnected(int msec = 30000) - 阻塞等待连接建立成功。
- QString hostName() - 获取当前连接的主机名,常用于判断连接是否成功。
- quint16 port() - 获取主机端口号。
- bool openMode() - 判断当前是否为主动连接还是被动接受模式。
- void writeTextMessage(const QString &text) - 发送文本消息,相比textMessage等更直观。
- void writeMessage(const QByteArray &data) - 发送二进制数据。
- qint64 bytesAvailable() - 查看接收缓存中可读取字节数。
- qint64 readBufferSize() - 设置双向数据接收缓存大小。
- void pauseIncomingPayload() - 暂停接收消息流。
- void resumeIncomingPayload() - 恢复接收。
- bool isValid() - 检查连接是否有效。
- 另外,作为QT套接字,它还支持一些通用功能:
- void setProxy() - 设置代理。
- void encrypt() - 设置SSL安全连接。
- void flush() - 强制输出缓存写出。
- bool waitForBytesWritten() - 等待数据发送完毕。
-
- void QWebSocket::sendTextMessage(const QString &message) 用于发送文本消息
- 使用这个函数发送文本消息主要有以下几点需要注意的地方:
- 1发送文本消息前请确保WebSocket连接已经建立。可以通过ReadyState判断连接状态。
- 2发送的消息内容必须是纯文本,不支持转义编码等更多格式。
- 3一条消息发送完毕后,会触发textMessageSent()信号通知。
- 4可以通过waitForBytesWritten()等待数据完全发送出去。
- 5发送数据顺序可能与收到响应顺序不一致,需要应用层自己处理序号等。
- 6若消息较大,建议使用write或send到套接字后flush,而不是sendTextMessage。
- 7跨平台考虑,消息内容编码最好使用QString而不是QByteArray。
- 8使用该函数发送的文本消息类型,服务端一般对应文本框接受。
- 9可以绑定消息发送断开连接的异常处理等。
sendTextMessage 및 writeTextMessage 함수는 모두 문자 메시지를 보내는 데 사용할 수 있지만 몇 가지 차이점이 있습니다.
sendTextMessage는 QWebSocket의 멤버 함수에 속합니다.
writeTextMessage는 QAbstractSocket의 멤버 함수이고 QWebSocket은 QAbstractSocket에서 상속됩니다.
내부적으로 sendTextMessage는 먼저 메시지를 QByteArray로 변환한 다음 한 번 더 변환하는 쓰기 기능을 통해 보냅니다.
writeTextMessage는 전송해야 하는 QString을 직접 작성하므로 약간 더 효율적입니다.
sendTextMessage는 동기 작업이며 전송이 완료된 후 반환됩니다.
writeTextMessage는 비동기 호출을 지원하며 콜백 함수는 Lambda를 통해 지정할 수 있습니다.
sendTextMessage는 오류 정보를 반환하지 않으며 신호 오류를 통해서만 처리될 수 있습니다.
writeTextMessage는 반환된 오류 코드를 가져와 전송 상황을 확인할 수 있습니다.
sendTextMessage는 WebSocket에 중점을 두고 있으며 WebSocket API를 호출하는 데 적합합니다.
writeTextMessage는 더 일반적이며 다른 QAbstractSocket 하위 클래스와 함께 사용할 수 있습니다.
일반적으로:
sendTextMessage는 사용이 더 간단하고 잘 캡슐화되어 있으며 기본 사용에 적합합니다.
writeTextMessage는 약간 더 효율적이고 비동기 및 오류 처리와 같은 더 많은 기능을 지원하며 고성능 또는 제어 요구 사항이 있는 시나리오에 적합합니다.
클라이언트와 클라이언트 간의 비공개 통신, 클라이언트와 서버 간의 통신.
프로세스: QWebServer를 생성하고, 새로운 연결 콜백을 바인딩하고, 연결 끊김을 수신합니다.
새 연결 콜백: 새 연결이 컬렉션에 추가되면 오프라인, 수신 및 오류 콜백이 새 연결 소켓에 바인딩됩니다.
메시지 보내기 버튼: 모두와 하나로 분류하고, 설정된 컬렉션을 순회하고, sendTextMesg를 사용하여 보냅니다.
- #include "widget.h"
- #include "ui_widget.h"
- //这个是服务器端
- Widget::Widget(QWidget *parent)
- : QWidget(parent)
- , ui(new Ui::Widget)
- {
- ui->setupUi(this);
- /*
- * mode
- NonSecureMode: 不安全模式,即不使用SSL/TLS进行通信。这是默认值。
- SecureMode: 安全模式,以SSL/TLS安全通信。客户端和服务端之间的连接将使用SSL握手建立安全通道。
- AutomaticallyAcceptServerCertificates: 自动接受服务器证书。在SecureMode下,客户端无法验证证书时,自动接受服务器发来的证书以建立连接。
- VerifyNone: 不验证证书。以SecureMode运行,但不会验证客户端和服务端使用的证书。
- */
- webServer = new QWebSocketServer("testWebServer",QWebSocketServer::NonSecureMode,this);
- QObject::connect(webServer,&QWebSocketServer::newConnection,this,&Widget::MyselfNewConnectCallBackSlot);
-
- webServer->listen(QHostAddress::Any,8888);
- }
- void Widget::MyselfNewConnectCallBackSlot(){//新连接回调
-
- if(webServer->hasPendingConnections()){
- QWebSocket* websocket=webServer->nextPendingConnection();
- ui->msgtext->append(websocket->origin()+"客户端已连接到服务器");
- sockets<<websocket;
-
- QListWidgetItem * item =new QListWidgetItem;
- item->setText(websocket->origin());
- ui->clinetls->addItem(item);
-
-
- //绑定离开
- QObject::connect(websocket,&QWebSocket::disconnected,this,[websocket,this](){
- ui->msgtext->append(websocket->origin()+"客户端断开服务器连接");
- sockets.removeOne(websocket);
- for(int i=0;i<ui->clinetls->count();i++)
- {
- QListWidgetItem *item=ui->clinetls->item(i);
-
- if(item->text()==websocket->origin())
- {
- ui->clinetls->removeItemWidget(item);
- delete item;
- break;
- }
- }
-
- websocket->deleteLater();
-
- });
-
-
- //接受消息回调
- QObject::connect(websocket,&QWebSocket::textMessageReceived,this,[this](const QString &msg){
- QJsonDocument doc =QJsonDocument::fromJson(msg.toLatin1().data());
- if(doc.isNull()){
- QWebSocket* websocket =qobject_cast<QWebSocket*>(sender());//sender 触发信号的源头
- ui->msgtext->append("收到客户端消息["+websocket->origin()+"]--->"+msg);
-
- }else{
- //客户端之间的单发消息
- QJsonObject obj=doc.object();
- QString dst=doc["dst"].toString();
- for (auto& socket : sockets) {
- if(dst == socket->origin()){
- socket->sendTextMessage(msg);
- }
- }
- }
- });
-
-
- QObject::connect(websocket,QOverload<QAbstractSocket::SocketError>::of(&QWebSocket::error),
- this,[this](QAbstractSocket::SocketError error){
- QWebSocket* web =qobject_cast<QWebSocket*>(sender());
- ui->msgtext->append(web->origin()+"出错"+web->errorString());
- });
- }
-
-
-
- }
- Widget::~Widget()
- {
- delete ui;
- for(auto socket:sockets)
- {
- socket->close();
- }
-
- webServer->close();
- }
-
-
- void Widget::on_sendpb_clicked()
- {
- QString send =ui->sendtext->toPlainText().trimmed();
- if(send.isEmpty())return;
- if(ui->allradio->isChecked()){
- //群发
- if(sockets.size()==0)return;
- foreach(auto &socket,sockets){
- socket->sendTextMessage(send);
- }
- ui->msgtext->append("服务器给所有连接发送:"+send);
- }else{ //私发 取客户端名称 找 发
- if(!ui->clinetls->currentItem())return;
- QString cname =ui->clinetls->currentItem()->text();
-
- for(auto &socket:sockets)
- {
- if(socket->origin()==cname)
- {
- socket->sendTextMessage(send);
- ui->msgtext->append("服务端给["+socket->origin()+"]发送--->"+send);
-
- break;
- }
- }
- }
-
-
- ui->sendtext->clear();
- }
버튼을 클릭하면 websocket 연결 프로세스를 구현합니다: websocket 생성, 다양한 콜백 바인딩, open(url)을 통해 연결
메시지 보내기 버튼: 비공개 메시지와 서버 메시지를 분류하고, 비공개 메시지를 json 형식으로 캡슐화하여 보냅니다.
- #include "widget.h"
- #include "ui_widget.h"
- #include<QMessageBox>
- Widget::Widget(QWidget *parent)
- : QWidget(parent)
- , ui(new Ui::Widget)
- {
- ui->setupUi(this);
-
- websocket=nullptr;
-
- }
-
- Widget::~Widget()
- {
- delete ui;
- }
-
-
- void Widget::on_sendpb_clicked()
- {
- if(!websocket && !websocket->isValid())
- return;
-
- QString send=ui->sendtext->toPlainText().trimmed();
- if(send.isEmpty())return;
-
-
- QString cname =ui->onetextmsg->text().trimmed();
- if(cname.isEmpty()){ //发给服务器
- websocket->sendTextMessage(send);
- ui->msgtext->append("发送消息:"+send);
- }else{
- //私聊发
- QJsonObject obj;
- obj["src"]=websocket->origin();
- obj["dst"]=cname;
- obj["msg"]=send;
- //将一个QJsonObject类型的obj转换成JSON字符串。 Compact参数指定格式化方式为紧凑格式(每个元素占一行)。紧凑格式输出结构清晰,容量小,适合传输和存储。
- QString str(QJsonDocument(obj).toJson(QJsonDocument::Compact));
- websocket->sendTextMessage(str);
- }
- ui->sendtext->clear();
-
-
- }
-
- void Widget::on_connect_clicked()
- {
-
- if(websocket == nullptr){
- if(ui->server_addr->text().trimmed().isEmpty()){
- QMessageBox::critical(this,"错误","服务器名称不能为空,请重新检查!",QMessageBox::Yes);
- return;
- }
- //用于移除字符串开头和结尾处的空白字符。 输入: " test " trimmed()后的结果: "test" VersionLatest使用最新的websocket协议版本。
- websocket=new QWebSocket(ui->client_name->text().trimmed(),QWebSocketProtocol::VersionLatest,this);
-
- //连接回调
- QObject::connect(websocket,&QWebSocket::connected,this,[this](){
- ui->msgtext->append("已经连接上"+websocket->peerAddress().toString());
- isConnecting=true;
- ui->connect->setText("断开服务器");
- });
- //断开回调
- QObject::connect(websocket,&QWebSocket::disconnected,this,[this](){
- ui->msgtext->append("已"+websocket->peerAddress().toString()+"断开连接");
- isConnecting=false;
- ui->connect->setText("连接服务器");
- });
-
- QObject::connect(websocket,QOverload<QAbstractSocket::SocketError>::of(&QWebSocket::error)
- ,this,[this](QAbstractSocket::SocketError){
- ui->msgtext->append(websocket->origin()+"出错"+websocket->errorString());
-
- });
-
-
- //接受消息回调 网上知名bug 当你连续发送 A:A*100 B:200时 接收方:A*100200 通过消息队列方式发送即可
- connect(websocket,&QWebSocket::textMessageReceived
- ,this,[this](const QString &msg){
- QJsonDocument jsd=QJsonDocument::fromJson(msg.toUtf8().data());
-
- if(jsd.isNull()) //解析失败 即没有 c1:c2 客户端与客户端私聊
- {
- ui->msgtext->append("收到消息:"+msg);
- }
- else
- {
- QJsonObject jsobj=jsd.object();
- ui->msgtext->append("收到来自"+jsobj["src"].toString()+"的消息:"+jsobj["msg"].toString());
- }
- },Qt::QueuedConnection);
-
-
-
- }
-
- if(isConnecting){ //连接是成功的
- websocket->close();
- websocket->deleteLater();
- websocket=nullptr;
-
- } else
- {
- websocket->open(QUrl(ui->server_addr->text().trimmed()));
- }
- }
일반적으로 QT 네트워크 라이브러리의 구성 요소인 QWebSocket은 WebSocket 클라이언트 및 서버 개발을 위한 편리한 API 세트를 제공합니다.
주요 장점은 다음과 같습니다.
완전한 객체 지향 설계로 API는 간단하고 사용하기 쉽습니다.
SSL/프록시 지원과 같은 다른 QT 네트워크 구성 요소와 고도로 통합됩니다.
이벤트 중심 모델을 사용하면 개발자는 멀티스레딩과 같은 기본 세부 사항을 처리할 필요가 없습니다.
Qt GUI 애플리케이션과 자연스럽게 통합되어 메시지 및 인터페이스 업데이트를 직접 호출할 수 있습니다.
개발을 용이하게 하기 위해 즉시 사용할 수 있는 WebSocket 기본 사양의 완전한 구현을 제공합니다.
특히 QT5.10이 비동기 I/O 호출을 지원하므로 성능도 좋습니다.
풍부한 예제와 오픈소스 프로젝트를 참고할 수 있으며 진입 장벽이 낮습니다.
참고할 사항은 다음과 같습니다.
일부 확장된 웹소켓 프로토콜 형식은 지원되지 않으며 직접 구현해야 합니다.
메시지 전송 및 수신 순서 매칭은 사용자가 직접 제어해야 합니다.
파일 및 스트리밍 빅데이터 전송 지원은 친절하지도 간단하지도 않습니다.
사용되는 기본 스마트 포인터 및 메모리 관리 메커니즘은 변경할 수 없습니다.
새로운 C++ 표준 기능에 대한 지원은 상대적으로 보수적입니다.
전반적으로 QWebSocket은 대부분의 Tcp 기반 WebSocket 애플리케이션에 대해 매우 훌륭하고 성숙한 선택을 제공합니다. 개발 효율성이 높고 버그가 거의 없습니다. QT 애플리케이션에도 선호됩니다. 더 높은 수준의 요구 사항이 있는 경우 다른 기본 구현을 고려할 수 있습니다. 그러나 대부분의 경우 QWebSocket이면 충분합니다.