技術共有

Qt QWebSocket ネットワーク プログラミング

2024-07-12

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

学習目標: Qt QWebSocket ネットワーク プログラミング

学習前の環境

QT TCP マルチスレッドネットワーク通信 - CSDN ブログ

学習内容

WebSocket は、単一の TCP 接続上で全二重通信チャネルを提供するネットワーク テクノロジです。 2011 年に IETF は WebSocket プロトコルを RFC6455 として標準化し、QWebSocket はクライアント アプリケーションとサーバー アプリケーションの両方で使用できます。

ブラウザとサーバー間の全二重通信を実装し、サーバーがクライアントに情報を積極的に送信できるようにします。

主な特徴:

  1. HTTP とは異なり、WebSocket を使用すると、クライアントがリクエストを開始する必要がなく、サーバーがクライアントにデータをアクティブに送信できます。

  2. TCP プロトコルに基づいて構築されており、全二重通信は、Ws (WebSocket) プロトコルを介して単一のポート上でサーバーとクライアント間で実行されます。

  3. テキストまたはバイナリ形式でのデータ送信をサポートします。

  4. このプロトコルは単一の TCP 接続に基づいて構築されており、サーバーとクライアントは 1 つの接続を作成するだけでよく、接続は閉じられません。

  5. JavaScript、Java、C#、Python などの複数のプログラミング言語でクライアント側およびサーバー側のライブラリをサポートします。

一般的なアプリケーション シナリオ:

  1. チャット ルーム: 低遅延のリアルタイム会話をサポートします。

  2. オンライン ゲーム: ゲーム ステータスのリアルタイム同期を必要とする同期エンジン。

  3. 株価: リアルタイムの株価をクライアントにプッシュする必要がある株価ソフトウェア。

  4. ビデオ会議: 音声とビデオの低遅延リアルタイム通信が必要です。

  5. リアルタイムの共同編集: たとえば、オンライン コード エディターにはリアルタイムの同期が必要です。

QWebSocket は、Qt が提供する WebSocket 関数ライブラリです。これは Qt ネットワーク モジュール上に構築されており、RFC6455 標準の WebSocket プロトコルを実装しています。 QT+=websockets を Qmake ファイルに追加する必要があります

  1. QWebSocket はテキスト/バイナリ メッセージ形式のみをサポートし、他の拡張形式はサポートしません。他のカスタム プロトコルを追加する必要がある場合、開発者はアプリケーション層でそれを自分で処理する必要があります。

  2. 標準の http/https ポート 80/443 を使用した WebSocket サービスへのアクセスをサポートし、wss (暗号化 WebSocket) プロトコルもサポートします。そのため、既存の Web サーバーと簡単に対話できます。

  3. QT CP ソケット実装に基づいているため、プロキシ設定、SSL 構成など、すべての Qt ネットワーク機能を完全にサポートしています。これは、一部の低レベル C インターフェイスよりも使いやすいです。

  4. アクティブ接続モードとパッシブ接続モードの両方をサポートします。アクティブな接続は connectToHost() を通じて行われ、パッシブな接続はリスニング ポート accept() を通じて受け入れられます。どちらのモードも非常に便利です。

  5. QT GUI アプリケーションの場合、メッセージの受信とインターフェイスの更新を簡単に実行できるため、マルチスレッド プログラミングの複雑さが回避されます。たとえば、textMessageReceived() でインターフェイスを直接更新するだけです。

  6. QT5.10以降は非同期I/Oがサポートされ、以前と比べて若干パフォーマンスが向上しています。ネットワーク拡張のサポートも強化されています。

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

sendTextMessage 関数と writeTextMessage 関数はどちらもテキスト メッセージの送信に使用できますが、いくつかの違いがあります。

  1. 関数の定義が異なります。
  • sendTextMessage は QWebSocket のメンバー関数に属します。

  • writeTextMessage は QAbstractSocket のメンバー関数であり、QWebSocket は QAbstractSocket を継承します。

  1. 送信効率が異なります:
  • 内部的には、sendTextMessage はまずメッセージを QByteArray に変換し、次に write 関数を通じて送信します。これはもう 1 回の変換です。

  • writeTextMessage は、送信する必要がある QString を直接書き込みます。これは若干効率的です。

  1. 非同期サポートは異なります。
  • sendTextMessage は同期操作であり、送信が完了すると戻ります。

  • writeTextMessage は非同期呼び出しをサポートしており、コールバック関数は Lambda を通じて指定できます。

  1. エラー処理は異なります。
  • sendTextMessage はエラー情報を返さず、信号エラーを通じてのみ処理できます。

  • writeTextMessage は返されたエラー コードを取得して、送信状況を判断できます。

  1. さまざまな使用シナリオ:
  • sendTextMessage は WebSocket に焦点を当てており、WebSocket API の呼び出しに適しています。

  • writeTextMessage はより一般的で、他の QAbstractSocket サブクラスと一緒に使用できます。

一般的に:

  • sendTextMessage は使用が簡単で、適切にカプセル化されており、基本的な使用に適しています。

  • writeTextMessage は若干効率が高く、非同期やエラー処理などのより多くの機能をサポートしており、高いパフォーマンスや制御要件が必要なシナリオに適しています。

プロジェクトを実現する

クライアントとクライアント間のプライベート通信、クライアントとサーバー間の通信。

コアコード

サーバ

プロセス: QWebServer を作成し、新しい接続コールバックをバインドし、切断をリッスンします。

新しい接続コールバック: 新しい接続がコレクションに追加されると、オフライン コールバック、受信コールバック、およびエラー コールバックが新しい接続ソケットにバインドされます。

メッセージ送信ボタン: すべてと 1 つを分類し、セットのコレクションを走査し、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. }

クライアント

ボタンをクリックして WebSocket 接続プロセスを実装します。WebSocket を作成し、さまざまなコールバックをバインドし、open(url) を介して接続します。

メッセージ送信ボタン:プライベートメッセージとサーバーメッセージを分類し、プライベートメッセージをjson形式でカプセル化して送信します。

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

要約する

一般に、QWebSocket は、QT ネットワーク ライブラリのコンポーネントとして、WebSocket クライアントおよびサーバーを開発するための便利な API セットを提供します。

その主な利点は次のとおりです。

  1. 完全なオブジェクト指向設計で、API はシンプルで使いやすいです。

  2. SSL/プロキシのサポートなど、他の QT ネットワーク コンポーネントと高度に統合されています。

  3. イベント駆動型モデルを使用すると、開発者はマルチスレッドなどの基礎となる詳細に対処する必要がありません。

  4. Qt GUI アプリケーションと自然に統合されており、メッセージやインターフェイスの更新を直接呼び出すことができます。

  5. WebSocket の基本仕様の完全な実装を提供します。これは、開発を容易にするためにすぐに使用できます。

  6. 特に QT5.10 が非同期 I/O 呼び出しをサポートしているため、パフォーマンスも良好です。

  7. 豊富なサンプルとオープンソース プロジェクトが参照可能であり、参入障壁は低いです。

注意すべき点は次のとおりです。

  1. 一部の拡張 WebSocket プロトコル形式はサポートされていないため、自分で実装する必要があります。

  2. メッセージ送受信の順序合わせは自分で制御する必要があります。

  3. ファイルおよびストリーミングのビッグ データ転送のサポートは、十分にフレンドリーで簡単ではありません。

  4. 使用される基礎となるスマート ポインターとメモリ管理メカニズムは変更できません。

  5. 新しい C++ 標準機能のサポートは比較的保守的です。

全体として、QWebSocket は、ほとんどの Tcp ベースの WebSocket アプリケーションにとって非常に優れた成熟した選択肢を提供します。開発効率が高く、バグが少ない。 QT アプリケーションにも推奨されます。より高いレベルの要件がある場合は、他の基礎となる実装を検討できます。ただし、ほとんどの場合、QWebSocket で十分です。

最後にソースコードのリンクを添付します
お役に立てましたら、星をお願いします

Qt デモ: Qt プロセスの学習 (gitee.com)