Condivisione della tecnologia

Programmazione di rete Qt QWebSocket

2024-07-12

한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina

Obiettivi di apprendimento: Programmazione di reti Qt QWebSocket

Pre-ambiente di apprendimento

Blog di comunicazione di rete multi-thread QT TCP-CSDN

Contenuto didattico

WebSocket è una tecnologia di rete che fornisce un canale di comunicazione full-duplex su una singola connessione TCP. Nel 2011, IETF ha standardizzato il protocollo WebSocket come RFC6455 e QWebSocket può essere utilizzato sia nelle applicazioni client che nelle applicazioni server.

Implementa la comunicazione full-duplex tra il browser e il server, consentendo al server di inviare informazioni in modo proattivo al client.

caratteristica principale:

  1. A differenza di HTTP, WebSocket consente al server di inviare attivamente dati al client senza richiedere al client di avviare una richiesta.

  2. Basato sul protocollo TCP, la comunicazione full duplex viene eseguita tra il server e il client su un'unica porta tramite il protocollo Ws (WebSocket).

  3. Supporta la trasmissione dei dati in formato testo o binario.

  4. Il protocollo è basato su una singola connessione TCP. Il server e il client devono creare solo una connessione e la connessione non verrà chiusa.

  5. Supporta librerie lato client e lato server in più linguaggi di programmazione, come JavaScript, Java, C#, Python, ecc.

Scenari applicativi comuni:

  1. Chat room: supporta conversazioni in tempo reale a bassa latenza.

  2. Gioco online: un motore di sincronizzazione che richiede la sincronizzazione in tempo reale dello stato del gioco.

  3. Quotazioni azionarie: software di quotazione che deve inviare quotazioni in tempo reale al cliente.

  4. Videoconferenza: è necessaria la comunicazione in tempo reale a bassa latenza di voce e video.

  5. Modifica collaborativa in tempo reale: ad esempio, l'editor di codice online richiede la sincronizzazione in tempo reale.

QWebSocket è la libreria di funzioni WebSocket fornita da Qt. È costruito sul modulo di rete Qt e implementa il protocollo WebSocket nello standard RFC6455. È necessario aggiungere QT+=websocket al file Qmake

  1. QWebSocket supporta solo formati di messaggi di testo/binari e non supporta altri formati estesi. Se è necessario aggiungere altri protocolli personalizzati, gli sviluppatori devono gestirli autonomamente a livello di applicazione.

  2. Supporta l'accesso ai servizi websocket utilizzando le porte http/https standard 80/443 e supporta anche il protocollo wss (websocket crittografato). Quindi può facilmente interagire con i server web esistenti.

  3. Poiché si basa sull'implementazione del socket QT CP, supporta completamente tutte le funzioni di rete Qt, come le impostazioni proxy, la configurazione SSL, ecc. Questo è più facile da usare rispetto ad alcune interfacce C di basso livello.

  4. Supporta sia la modalità di connessione attiva che quella passiva. Le connessioni attive avvengono tramite connectToHost() e le connessioni passive vengono accettate tramite la porta di ascolto Accept(). Entrambe le modalità sono molto convenienti.

  5. Per le applicazioni QT GUI, la ricezione dei messaggi e gli aggiornamenti dell'interfaccia possono essere eseguiti facilmente, evitando la complessità della programmazione multi-thread. Ad esempio, aggiorna semplicemente l'interfaccia direttamente in textMessageReceived().

  6. Dopo QT5.10, è supportato l'I/O asincrono e le prestazioni sono leggermente migliorate rispetto a prima. Anche il supporto per le estensioni di rete è migliore.

Funzioni membro comuni di QWebSocket

  1. origin()
  2. 即 websocket=new QWebSocket("C1我是客户端",QWebSocketProtocol::VersionLatest,this);
  3. websocket->origin()  -》 C1我是客户端
  4. void connectToHost(const QUrl &url) - 用于连接到指定主机的websocket服务,这个函数是异步的。
  5. void close() - 关闭与服务器的连接。
  6. void textMessageReceived(const QString &message) - 收到文本消息时触发的信号,其参数就是收到的文本消息内容。
  7. void binaryMessageReceived(const QByteArray &message) - 收到二进制消息时触发的信号,参数是原始二进制数据。
  8. void error(QAbstractSocket::SocketError socketError) - 发生错误时触发的信号,参数是错误类型。
  9. void stateChanged(QAbstractSocket::SocketState state) - 连接状态变化时触发,可以得知连接是否建立等。
  10. void textMessageSent(qint64 numBytes) - 发送文本消息完成后触发,numBytes是字节数。
  11. void bytesWritten(qint64 bytes) - 消息发送过程中的写入回调, bytes是一个部分发送出去的字节数。
  12. void abort() - 主动断开连接。
  13. bool waitForConnected(int msec = 30000) - 阻塞等待连接建立成功。
  14. QString hostName() - 获取当前连接的主机名,常用于判断连接是否成功。
  15. quint16 port() - 获取主机端口号。
  16. bool openMode() - 判断当前是否为主动连接还是被动接受模式。
  17. void writeTextMessage(const QString &text) - 发送文本消息,相比textMessage等更直观。
  18. void writeMessage(const QByteArray &data) - 发送二进制数据。
  19. qint64 bytesAvailable() - 查看接收缓存中可读取字节数。
  20. qint64 readBufferSize() - 设置双向数据接收缓存大小。
  21. void pauseIncomingPayload() - 暂停接收消息流。
  22. void resumeIncomingPayload() - 恢复接收。
  23. bool isValid() - 检查连接是否有效。
  24. 另外,作为QT套接字,它还支持一些通用功能:
  25. void setProxy() - 设置代理。
  26. void encrypt() - 设置SSL安全连接。
  27. void flush() - 强制输出缓存写出。
  28. bool waitForBytesWritten() - 等待数据发送完毕。
  29. void QWebSocket::sendTextMessage(const QString &message) 用于发送文本消息
  30. 使用这个函数发送文本消息主要有以下几点需要注意的地方:
  31. 1发送文本消息前请确保WebSocket连接已经建立。可以通过ReadyState判断连接状态。
  32. 2发送的消息内容必须是纯文本,不支持转义编码等更多格式。
  33. 3一条消息发送完毕后,会触发textMessageSent()信号通知。
  34. 4可以通过waitForBytesWritten()等待数据完全发送出去。
  35. 5发送数据顺序可能与收到响应顺序不一致,需要应用层自己处理序号等。
  36. 6若消息较大,建议使用write或send到套接字后flush,而不是sendTextMessage。
  37. 7跨平台考虑,消息内容编码最好使用QString而不是QByteArray。
  38. 8使用该函数发送的文本消息类型,服务端一般对应文本框接受。
  39. 9可以绑定消息发送断开连接的异常处理等。

Entrambe le funzioni sendTextMessage e writeTextMessage possono essere utilizzate per inviare messaggi di testo, ma presentano alcune differenze:

  1. La definizione della funzione è diversa:
  • sendTextMessage appartiene alla funzione membro di QWebSocket;

  • writeTextMessage è una funzione membro di QAbstractSocket e QWebSocket eredita da QAbstractSocket.

  1. L'efficienza di invio è diversa:
  • Internamente sendTextMessage convertirà prima il messaggio in QByteArray, quindi lo invierà tramite la funzione di scrittura, che è un'altra conversione;

  • writeTextMessage scrive direttamente la QString che deve essere inviata, il che è leggermente più efficiente.

  1. Il supporto asincrono è diverso:
  • sendTextMessage è un'operazione sincrona e verrà restituita al termine dell'invio;

  • writeTextMessage supporta chiamate asincrone e la funzione di callback può essere specificata tramite Lambda.

  1. La gestione degli errori è diversa:
  • sendTextMessage non restituirà informazioni sull'errore e potrà essere gestito solo tramite errori di segnale;

  • writeTextMessage può ottenere il codice di errore restituito per determinare la situazione di invio.

  1. Diversi scenari di utilizzo:
  • sendTextMessage si concentra su WebSocket ed è adatto per chiamare l'API WebSocket;

  • writeTextMessage è più generale e può essere utilizzato con altre sottoclassi QAbstractSocket.

Generalmente:

  • sendTextMessage è più semplice da usare, ben incapsulato e adatto all'utilizzo di base;

  • writeTextMessage è leggermente più efficiente, supporta più funzionalità come la gestione asincrona e degli errori ed è adatto a scenari con requisiti di prestazioni o controllo elevati.

Realizzare il progetto

Comunicazione privata tra client e client, comunicazione tra client e server.

codice fondamentale

server

Processo: crea QWebServer, associa il nuovo callback di connessione e ascolta la disconnessione.

Callback di nuova connessione: quando una nuova connessione viene aggiunta alla raccolta, i callback offline, di ricezione e di errore vengono associati al nuovo socket di connessione.

Pulsante Invia messaggio: classifica tutti e uno, attraversa la raccolta di set e invia utilizzando sendTextMesg

  1. #include "widget.h"
  2. #include "ui_widget.h"
  3. //这个是服务器端
  4. Widget::Widget(QWidget *parent)
  5. : QWidget(parent)
  6. , ui(new Ui::Widget)
  7. {
  8. ui->setupUi(this);
  9. /*
  10. * mode
  11. NonSecureMode: 不安全模式,即不使用SSL/TLS进行通信。这是默认值。
  12. SecureMode: 安全模式,以SSL/TLS安全通信。客户端和服务端之间的连接将使用SSL握手建立安全通道。
  13. AutomaticallyAcceptServerCertificates: 自动接受服务器证书。在SecureMode下,客户端无法验证证书时,自动接受服务器发来的证书以建立连接。
  14. VerifyNone: 不验证证书。以SecureMode运行,但不会验证客户端和服务端使用的证书。
  15. */
  16. webServer = new QWebSocketServer("testWebServer",QWebSocketServer::NonSecureMode,this);
  17. QObject::connect(webServer,&QWebSocketServer::newConnection,this,&Widget::MyselfNewConnectCallBackSlot);
  18. webServer->listen(QHostAddress::Any,8888);
  19. }
  20. void Widget::MyselfNewConnectCallBackSlot(){//新连接回调
  21. if(webServer->hasPendingConnections()){
  22. QWebSocket* websocket=webServer->nextPendingConnection();
  23. ui->msgtext->append(websocket->origin()+"客户端已连接到服务器");
  24. sockets<<websocket;
  25. QListWidgetItem * item =new QListWidgetItem;
  26. item->setText(websocket->origin());
  27. ui->clinetls->addItem(item);
  28. //绑定离开
  29. QObject::connect(websocket,&QWebSocket::disconnected,this,[websocket,this](){
  30. ui->msgtext->append(websocket->origin()+"客户端断开服务器连接");
  31. sockets.removeOne(websocket);
  32. for(int i=0;i<ui->clinetls->count();i++)
  33. {
  34. QListWidgetItem *item=ui->clinetls->item(i);
  35. if(item->text()==websocket->origin())
  36. {
  37. ui->clinetls->removeItemWidget(item);
  38. delete item;
  39. break;
  40. }
  41. }
  42. websocket->deleteLater();
  43. });
  44. //接受消息回调
  45. QObject::connect(websocket,&QWebSocket::textMessageReceived,this,[this](const QString &msg){
  46. QJsonDocument doc =QJsonDocument::fromJson(msg.toLatin1().data());
  47. if(doc.isNull()){
  48. QWebSocket* websocket =qobject_cast<QWebSocket*>(sender());//sender 触发信号的源头
  49. ui->msgtext->append("收到客户端消息["+websocket->origin()+"]--->"+msg);
  50. }else{
  51. //客户端之间的单发消息
  52. QJsonObject obj=doc.object();
  53. QString dst=doc["dst"].toString();
  54. for (auto& socket : sockets) {
  55. if(dst == socket->origin()){
  56. socket->sendTextMessage(msg);
  57. }
  58. }
  59. }
  60. });
  61. QObject::connect(websocket,QOverload<QAbstractSocket::SocketError>::of(&QWebSocket::error),
  62. this,[this](QAbstractSocket::SocketError error){
  63. QWebSocket* web =qobject_cast<QWebSocket*>(sender());
  64. ui->msgtext->append(web->origin()+"出错"+web->errorString());
  65. });
  66. }
  67. }
  68. Widget::~Widget()
  69. {
  70. delete ui;
  71. for(auto socket:sockets)
  72. {
  73. socket->close();
  74. }
  75. webServer->close();
  76. }
  77. void Widget::on_sendpb_clicked()
  78. {
  79. QString send =ui->sendtext->toPlainText().trimmed();
  80. if(send.isEmpty())return;
  81. if(ui->allradio->isChecked()){
  82. //群发
  83. if(sockets.size()==0)return;
  84. foreach(auto &socket,sockets){
  85. socket->sendTextMessage(send);
  86. }
  87. ui->msgtext->append("服务器给所有连接发送:"+send);
  88. }else{ //私发 取客户端名称 找 发
  89. if(!ui->clinetls->currentItem())return;
  90. QString cname =ui->clinetls->currentItem()->text();
  91. for(auto &socket:sockets)
  92. {
  93. if(socket->origin()==cname)
  94. {
  95. socket->sendTextMessage(send);
  96. ui->msgtext->append("服务端给["+socket->origin()+"]发送--->"+send);
  97. break;
  98. }
  99. }
  100. }
  101. ui->sendtext->clear();
  102. }

cliente

Fare clic sul pulsante per implementare il processo di connessione websocket: creare websocket, associare vari callback e connettersi tramite open(url)

Pulsante Invia messaggio: classifica i messaggi privati ​​e i messaggi del server, incapsula i messaggi privati ​​in formato json e quindi invia.

  1. #include "widget.h"
  2. #include "ui_widget.h"
  3. #include<QMessageBox>
  4. Widget::Widget(QWidget *parent)
  5. : QWidget(parent)
  6. , ui(new Ui::Widget)
  7. {
  8. ui->setupUi(this);
  9. websocket=nullptr;
  10. }
  11. Widget::~Widget()
  12. {
  13. delete ui;
  14. }
  15. void Widget::on_sendpb_clicked()
  16. {
  17. if(!websocket && !websocket->isValid())
  18. return;
  19. QString send=ui->sendtext->toPlainText().trimmed();
  20. if(send.isEmpty())return;
  21. QString cname =ui->onetextmsg->text().trimmed();
  22. if(cname.isEmpty()){ //发给服务器
  23. websocket->sendTextMessage(send);
  24. ui->msgtext->append("发送消息:"+send);
  25. }else{
  26. //私聊发
  27. QJsonObject obj;
  28. obj["src"]=websocket->origin();
  29. obj["dst"]=cname;
  30. obj["msg"]=send;
  31. //将一个QJsonObject类型的obj转换成JSON字符串。 Compact参数指定格式化方式为紧凑格式(每个元素占一行)。紧凑格式输出结构清晰,容量小,适合传输和存储。
  32. QString str(QJsonDocument(obj).toJson(QJsonDocument::Compact));
  33. websocket->sendTextMessage(str);
  34. }
  35. ui->sendtext->clear();
  36. }
  37. void Widget::on_connect_clicked()
  38. {
  39. if(websocket == nullptr){
  40. if(ui->server_addr->text().trimmed().isEmpty()){
  41. QMessageBox::critical(this,"错误","服务器名称不能为空,请重新检查!",QMessageBox::Yes);
  42. return;
  43. }
  44. //用于移除字符串开头和结尾处的空白字符。 输入: " test " trimmed()后的结果: "test" VersionLatest使用最新的websocket协议版本。
  45. websocket=new QWebSocket(ui->client_name->text().trimmed(),QWebSocketProtocol::VersionLatest,this);
  46. //连接回调
  47. QObject::connect(websocket,&QWebSocket::connected,this,[this](){
  48. ui->msgtext->append("已经连接上"+websocket->peerAddress().toString());
  49. isConnecting=true;
  50. ui->connect->setText("断开服务器");
  51. });
  52. //断开回调
  53. QObject::connect(websocket,&QWebSocket::disconnected,this,[this](){
  54. ui->msgtext->append("已"+websocket->peerAddress().toString()+"断开连接");
  55. isConnecting=false;
  56. ui->connect->setText("连接服务器");
  57. });
  58. QObject::connect(websocket,QOverload<QAbstractSocket::SocketError>::of(&QWebSocket::error)
  59. ,this,[this](QAbstractSocket::SocketError){
  60. ui->msgtext->append(websocket->origin()+"出错"+websocket->errorString());
  61. });
  62. //接受消息回调 网上知名bug 当你连续发送 A:A*100 B:200时 接收方:A*100200 通过消息队列方式发送即可
  63. connect(websocket,&QWebSocket::textMessageReceived
  64. ,this,[this](const QString &msg){
  65. QJsonDocument jsd=QJsonDocument::fromJson(msg.toUtf8().data());
  66. if(jsd.isNull()) //解析失败 即没有 c1:c2 客户端与客户端私聊
  67. {
  68. ui->msgtext->append("收到消息:"+msg);
  69. }
  70. else
  71. {
  72. QJsonObject jsobj=jsd.object();
  73. ui->msgtext->append("收到来自"+jsobj["src"].toString()+"的消息:"+jsobj["msg"].toString());
  74. }
  75. },Qt::QueuedConnection);
  76. }
  77. if(isConnecting){ //连接是成功的
  78. websocket->close();
  79. websocket->deleteLater();
  80. websocket=nullptr;
  81. } else
  82. {
  83. websocket->open(QUrl(ui->server_addr->text().trimmed()));
  84. }
  85. }

Riassumere

In generale, QWebSocket, come componente nella libreria di rete QT, fornisce una serie di API utili per lo sviluppo di client e server WebSocket.

I suoi principali vantaggi sono:

  1. Design completamente orientato agli oggetti, l'API è semplice e facile da usare.

  2. Altamente integrato con altri componenti di rete QT, come il supporto SSL/proxy.

  3. Utilizzando un modello basato sugli eventi, gli sviluppatori non devono occuparsi dei dettagli sottostanti come il multi-threading.

  4. Naturalmente integrato con le applicazioni GUI Qt, i messaggi e gli aggiornamenti dell'interfaccia possono essere richiamati direttamente.

  5. Fornisce un'implementazione completa delle specifiche di base di WebSocket, che possono essere utilizzate immediatamente per facilitare lo sviluppo.

  6. Anche le prestazioni sono buone, soprattutto perché QT5.10 supporta le chiamate I/O asincrone.

  7. Sono disponibili numerosi esempi e progetti open source come riferimento e la barriera all'ingresso è bassa.

Alcuni punti da notare includono:

  1. Alcuni formati di protocollo websocket estesi non sono supportati e devono essere implementati dall'utente.

  2. La corrispondenza dell'ordine di invio e ricezione dei messaggi deve essere controllata dall'utente.

  3. Il supporto per la trasmissione di file e streaming di big data non è abbastanza semplice e intuitivo.

  4. I puntatori intelligenti sottostanti e i meccanismi di gestione della memoria utilizzati non possono essere modificati.

  5. Il supporto per le nuove funzionalità standard C++ è relativamente conservativo.

Nel complesso, QWebSocket fornisce una scelta molto valida e matura per la maggior parte delle applicazioni WebSocket basate su Tcp. Elevata efficienza di sviluppo e pochi bug. Preferito anche per applicazioni QT. Se sono presenti requisiti di livello superiore, è possibile prendere in considerazione altre implementazioni sottostanti. Ma nella maggior parte dei casi QWebSocket è abbastanza buono.

Infine, allega il collegamento al codice sorgente
Se ti è stato utile, per favore, dammi una stella

Demo Qt: imparare il processo qt (gitee.com)