Partage de technologie

Programmation réseau Qt QWebSocket

2024-07-12

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

Objectifs d'apprentissage : Programmation réseau Qt QWebSocket

Pré-environnement d’apprentissage

Communication réseau multithread QT TCP-CSDN Blog

Contenu d'apprentissage

WebSocket est une technologie réseau qui fournit un canal de communication full-duplex sur une seule connexion TCP. En 2011, l'IETF a standardisé le protocole WebSocket sous le nom de RFC6455, et QWebSocket peut être utilisé à la fois dans les applications client et dans les applications serveur.

Il implémente une communication full-duplex entre le navigateur et le serveur, permettant au serveur d'envoyer de manière proactive des informations au client.

caractéristique principale:

  1. Contrairement à HTTP, WebSocket permet au serveur d'envoyer activement des données au client sans que ce dernier n'ait à lancer de requête.

  2. Construite sur le protocole TCP, la communication full-duplex s'effectue entre le serveur et le client sur un seul port via le protocole Ws (WebSocket).

  3. Prend en charge la transmission de données au format texte ou binaire.

  4. Le protocole est construit sur une seule connexion TCP. Le serveur et le client n'ont besoin de créer qu'une seule connexion, et la connexion ne sera pas fermée.

  5. Prend en charge les bibliothèques côté client et côté serveur dans plusieurs langages de programmation, tels que JavaScript, Java, C#, Python, etc.

Scénarios d'application courants :

  1. Salon de discussion : prend en charge les conversations en temps réel à faible latence.

  2. Jeu en ligne : un moteur de synchronisation qui nécessite une synchronisation en temps réel de l'état du jeu.

  3. Cotations boursières : logiciel de cotation qui doit transmettre les cotations en temps réel au client.

  4. Vidéoconférence : une communication vocale et vidéo en temps réel à faible latence est requise.

  5. Édition collaborative en temps réel : par exemple, l'éditeur de code en ligne nécessite une synchronisation en temps réel.

QWebSocket est la bibliothèque de fonctions WebSocket fournie par Qt. Il est construit sur le module réseau Qt et implémente le protocole WebSocket dans la norme RFC6455. Vous devez ajouter QT+=websockets au fichier Qmake

  1. QWebSocket prend uniquement en charge les formats de message texte/binaire et ne prend pas en charge les autres formats étendus. Si d’autres protocoles personnalisés doivent être ajoutés, les développeurs doivent les gérer eux-mêmes au niveau de la couche application.

  2. Il prend en charge l'accès aux services Websocket à l'aide des ports http/https standard 80/443 et prend également en charge le protocole wss (websocket crypté). Il peut donc facilement interagir avec les serveurs Web existants.

  3. Puisqu'il est basé sur l'implémentation du socket QT CP, il prend entièrement en charge toutes les fonctions réseau Qt, telles que les paramètres de proxy, la configuration SSL, etc. C'est plus facile à utiliser que certaines interfaces C de bas niveau.

  4. Il prend en charge les modes de connexion actifs et passifs. Les connexions actives s'effectuent via connectToHost() et les connexions passives sont acceptées via le port d'écoute accept(). Les deux modes sont très pratiques.

  5. Pour les applications QT GUI, la réception des messages et les mises à jour de l'interface peuvent être facilement effectuées, évitant ainsi la complexité de la programmation multithread. Par exemple, mettez simplement à jour l’interface directement dans textMessageReceived().

  6. Après QT5.10, les E/S asynchrones sont prises en charge et les performances sont légèrement améliorées par rapport à avant. La prise en charge des extensions réseau est également meilleure.

Fonctions membres communes de 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可以绑定消息发送断开连接的异常处理等。

Les fonctions sendTextMessage et writeTextMessage peuvent être utilisées pour envoyer des messages texte, mais elles présentent quelques différences :

  1. La définition de la fonction est différente :
  • sendTextMessage appartient à la fonction membre de QWebSocket ;

  • writeTextMessage est une fonction membre de QAbstractSocket et QWebSocket hérite de QAbstractSocket.

  1. L'efficacité de l'envoi est différente :
  • En interne, sendTextMessage convertira d'abord le message en QByteArray, puis l'enverra via la fonction d'écriture, ce qui est une conversion supplémentaire ;

  • writeTextMessage écrit directement le QString qui doit être envoyé, ce qui est légèrement plus efficace.

  1. La prise en charge asynchrone est différente :
  • sendTextMessage est une opération synchrone et sera renvoyée une fois l'envoi terminé ;

  • writeTextMessage prend en charge les appels asynchrones et la fonction de rappel peut être spécifiée via Lambda.

  1. La gestion des erreurs est différente :
  • sendTextMessage ne renverra pas d'informations d'erreur et ne pourra être traité que via des erreurs de signal ;

  • writeTextMessage peut obtenir le code d'erreur renvoyé pour déterminer la situation d'envoi.

  1. Différents scénarios d'utilisation :
  • sendTextMessage se concentre sur WebSocket et convient pour appeler l'API WebSocket ;

  • writeTextMessage est plus général et peut être utilisé avec d'autres sous-classes de QAbstractSocket.

En général:

  • sendTextMessage est plus simple à utiliser, bien encapsulé et adapté à un usage de base ;

  • writeTextMessage est légèrement plus efficace, prend en charge davantage de fonctionnalités telles que la gestion asynchrone et des erreurs, et convient aux scénarios avec des exigences de performances ou de contrôle élevées.

Réaliser le projet

Communication privée entre client et client, communication entre client et serveur.

code de base

Serveur

Processus : créez QWebServer, liez le nouveau rappel de connexion et écoutez la déconnexion.

Rappel de nouvelle connexion : lorsqu'une nouvelle connexion est ajoutée à la collection, les rappels hors ligne, de réception et d'erreur sont liés au nouveau socket de connexion.

Bouton Envoyer un message : classez tout et un, parcourez la collection définie et envoyez à l'aide de 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. }

client

Cliquez sur le bouton pour implémenter le processus de connexion websocket : créez un websocket, liez divers rappels et connectez-vous via open(url)

Bouton Envoyer un message : classifiez les messages privés et les messages du serveur, encapsulez les messages privés au format json, puis envoyez-les.

  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. }

Résumer

En général, QWebSocket, en tant que composant de la bibliothèque réseau QT, fournit un ensemble d'API pratiques pour développer des clients et des serveurs WebSocket.

Ses principaux avantages sont :

  1. Conception entièrement orientée objet, l’API est simple et facile à utiliser.

  2. Fortement intégré aux autres composants réseau QT, tels que la prise en charge SSL/proxy.

  3. Grâce à un modèle basé sur les événements, les développeurs n'ont pas besoin de gérer les détails sous-jacents tels que le multithreading.

  4. Naturellement intégrés aux applications Qt GUI, les messages et les mises à jour de l'interface peuvent être appelés directement.

  5. Fournit une implémentation complète des spécifications de base de WebSocket, qui peuvent être utilisées immédiatement pour faciliter le développement.

  6. Les performances sont également bonnes, d'autant plus que QT5.10 prend en charge les appels d'E/S asynchrones.

  7. De riches exemples et des projets open source sont disponibles pour référence, et la barrière à l'entrée est faible.

Voici quelques points à noter :

  1. Certains formats de protocole Websocket étendu ne sont pas pris en charge et doivent être implémentés par vous-même.

  2. La correspondance des ordres d’envoi et de réception des messages doit être contrôlée par vous-même.

  3. La prise en charge de la transmission de données volumineuses en fichiers et en streaming n'est pas assez conviviale et simple.

  4. Les pointeurs intelligents sous-jacents et les mécanismes de gestion de la mémoire utilisés ne peuvent pas être modifiés.

  5. La prise en charge des nouvelles fonctionnalités standard C++ est relativement conservatrice.

Dans l'ensemble, QWebSocket constitue un choix très efficace et mature pour la plupart des applications WebSocket basées sur TCP. Haute efficacité de développement et peu de bugs. Également préféré pour les applications QT. S'il existe des exigences de niveau supérieur, d'autres implémentations sous-jacentes peuvent être envisagées. Mais dans la plupart des cas, QWebSocket suffit.

Enfin, joignez le lien du code source
Si cela vous est utile, donnez-moi une étoile

Démo Qt : apprentissage du processus Qt (gitee.com)