Κοινή χρήση τεχνολογίας

Προγραμματισμός δικτύου Qt QWebSocket

2024-07-12

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

Στόχοι μάθησης: Προγραμματισμός δικτύου Qt QWebSocket

Μαθησιακό προ-περιβάλλον

Επικοινωνία δικτύου πολλαπλών νημάτων QT TCP-CSDN Blog

Μαθησιακό Περιεχόμενο

Το WebSocket είναι μια τεχνολογία δικτύου που παρέχει ένα κανάλι επικοινωνίας πλήρους διπλής όψης σε μία μόνο σύνδεση TCP. Το 2011, το IETF τυποποίησε το πρωτόκολλο WebSocket ως RFC6455 και το QWebSocket μπορεί να χρησιμοποιηθεί τόσο σε εφαρμογές πελάτη όσο και σε εφαρμογές διακομιστή.

Εφαρμόζει πλήρη αμφίδρομη επικοινωνία μεταξύ του προγράμματος περιήγησης και του διακομιστή, επιτρέποντας στον διακομιστή να στέλνει προληπτικά πληροφορίες στον πελάτη.

κύριο χαρακτηριστικό:

  1. Σε αντίθεση με το HTTP, το WebSocket επιτρέπει στον διακομιστή να στέλνει ενεργά δεδομένα στον πελάτη χωρίς να απαιτεί από τον πελάτη να ξεκινήσει ένα αίτημα.

  2. Ενσωματωμένη στο πρωτόκολλο TCP, η πλήρης αμφίδρομη επικοινωνία πραγματοποιείται μεταξύ του διακομιστή και του πελάτη σε μία μόνο θύρα μέσω του πρωτοκόλλου Ws (WebSocket).

  3. Υποστηρίζει τη μετάδοση δεδομένων σε μορφή κειμένου ή δυαδικής μορφής.

  4. Το πρωτόκολλο είναι χτισμένο σε μία μόνο σύνδεση TCP Ο διακομιστής και ο πελάτης χρειάζεται να δημιουργήσουν μόνο μία σύνδεση και η σύνδεση δεν θα κλείσει.

  5. Υποστηρίζει βιβλιοθήκες από πλευράς πελάτη και διακομιστή σε πολλές γλώσσες προγραμματισμού, όπως JavaScript, Java, C#, Python κ.λπ.

Συνηθισμένα σενάρια εφαρμογής:

  1. Chat Room: Υποστηρίζει συνομιλίες σε πραγματικό χρόνο χαμηλής καθυστέρησης.

  2. Διαδικτυακό παιχνίδι: μια μηχανή συγχρονισμού που απαιτεί συγχρονισμό της κατάστασης του παιχνιδιού σε πραγματικό χρόνο.

  3. Τιμές μετοχών: Λογισμικό προσφοράς που πρέπει να προωθήσει τις τιμές σε πραγματικό χρόνο στον πελάτη.

  4. Τηλεδιάσκεψη: Απαιτείται επικοινωνία φωνής και βίντεο σε πραγματικό χρόνο χαμηλής καθυστέρησης.

  5. Συνεργατική επεξεργασία σε πραγματικό χρόνο: Για παράδειγμα, ο διαδικτυακός επεξεργαστής κώδικα απαιτεί συγχρονισμό σε πραγματικό χρόνο.

Το QWebSocket είναι η βιβλιοθήκη λειτουργιών WebSocket που παρέχεται από την Qt. Είναι χτισμένο στη μονάδα δικτύου Qt και υλοποιεί το πρωτόκολλο WebSocket στο πρότυπο RFC6455. Πρέπει να προσθέσετε QT+=websockets στο αρχείο Qmake

  1. Το QWebSocket υποστηρίζει μόνο μορφές μηνυμάτων κειμένου/δυαδικών μηνυμάτων και δεν υποστηρίζει άλλες εκτεταμένες μορφές. Εάν πρέπει να προστεθούν άλλα προσαρμοσμένα πρωτόκολλα, οι προγραμματιστές πρέπει να το χειριστούν μόνοι τους στο επίπεδο εφαρμογής.

  2. Υποστηρίζει την πρόσβαση σε υπηρεσίες websocket χρησιμοποιώντας τυπικές θύρες http/https 80/443, και υποστηρίζει επίσης πρωτόκολλο wss (κρυπτογραφημένο websocket). Έτσι μπορεί εύκολα να αλληλεπιδράσει με τους υπάρχοντες διακομιστές Ιστού.

  3. Δεδομένου ότι βασίζεται στην υλοποίηση της υποδοχής QT CP, υποστηρίζει πλήρως όλες τις λειτουργίες του δικτύου Qt, όπως ρυθμίσεις διακομιστή μεσολάβησης, διαμόρφωση SSL κ.λπ. Αυτό είναι πιο εύκολο στη χρήση από ορισμένες διεπαφές 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 και μετά θα το στείλει μέσω της συνάρτησης εγγραφής, που είναι μια ακόμη μετατροπή.

  • Το writeTextMessage γράφει απευθείας το QString που πρέπει να σταλεί, το οποίο είναι ελαφρώς πιο αποτελεσματικό.

  1. Η υποστήριξη Async είναι διαφορετική:
  • Το sendTextMessage είναι μια σύγχρονη λειτουργία και θα επιστρέψει μετά την ολοκλήρωση της αποστολής.

  • Το writeTextMessage υποστηρίζει ασύγχρονες κλήσεις και η λειτουργία επανάκλησης μπορεί να καθοριστεί μέσω Lambda.

  1. Ο χειρισμός σφαλμάτων είναι διαφορετικός:
  • Το sendTextMessage δεν θα επιστρέψει πληροφορίες σφάλματος και μπορεί να αντιμετωπιστεί μόνο μέσω σφαλμάτων σήματος.

  • Το writeTextMessage μπορεί να λάβει τον επιστρεφόμενο κωδικό σφάλματος για να προσδιορίσει την κατάσταση αποστολής.

  1. Διαφορετικά σενάρια χρήσης:
  • Το sendTextMessage εστιάζει στο WebSocket και είναι κατάλληλο για κλήση του WebSocket API.

  • Το writeTextMessage είναι πιο γενικό και μπορεί να χρησιμοποιηθεί με άλλες υποκλάσεις QAbstractSocket.

Γενικά:

  • Το sendTextMessage είναι πιο απλό στη χρήση, καλά ενσωματωμένο και κατάλληλο για βασική χρήση.

  • Το writeTextMessage είναι ελαφρώς πιο αποτελεσματικό, υποστηρίζει περισσότερες δυνατότητες όπως η ασύγχρονη διαχείριση και ο χειρισμός σφαλμάτων και είναι κατάλληλος για σενάρια με απαιτήσεις υψηλών επιδόσεων ή ελέγχου.

Πραγματοποιήστε το έργο

Ιδιωτική επικοινωνία μεταξύ πελάτη και πελάτη, επικοινωνία πελάτη και διακομιστή.

πυρήνα κώδικα

Υπηρέτης

Διαδικασία: Δημιουργήστε QWebServer, συνδέστε νέα επανάκληση σύνδεσης και ακούστε για αποσύνδεση.

Επανάκληση νέας σύνδεσης: Όταν προστίθεται μια νέα σύνδεση στη συλλογή, οι επανακλήσεις εκτός σύνδεσης και λήψης και σφαλμάτων συνδέονται στη νέα υποδοχή σύνδεσης.

Κουμπί αποστολής μηνύματος: ταξινομήστε όλα και ένα, διασχίστε τη συλλογή και στείλτε χρησιμοποιώντας το 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, παρέχει ένα σύνολο βολικών API για την ανάπτυξη πελατών και διακομιστών WebSocket.

Τα κύρια πλεονεκτήματά του είναι:

  1. Πλήρως αντικειμενοστραφής σχεδιασμός, το API είναι απλό και εύκολο στη χρήση.

  2. Ιδιαίτερα ενσωματωμένο με άλλα στοιχεία δικτύου QT, όπως υποστήριξη SSL/proxy.

  3. Χρησιμοποιώντας ένα μοντέλο που βασίζεται σε συμβάντα, οι προγραμματιστές δεν χρειάζεται να ασχοληθούν με υποκείμενες λεπτομέρειες όπως η πολλαπλή νήμα.

  4. Φυσικά ενσωματωμένα με εφαρμογές Qt GUI, τα μηνύματα και οι ενημερώσεις διεπαφής μπορούν να κληθούν απευθείας.

  5. Παρέχει μια πλήρη εφαρμογή των βασικών προδιαγραφών του WebSocket, οι οποίες μπορούν να χρησιμοποιηθούν εκτός συσκευασίας για να διευκολυνθεί η ανάπτυξη.

  6. Η απόδοση είναι επίσης καλή, ειδικά επειδή το QT5.10 υποστηρίζει ασύγχρονη κλήση I/O.

  7. Πλούσια παραδείγματα και έργα ανοιχτού κώδικα είναι διαθέσιμα για αναφορά και το εμπόδιο εισόδου είναι χαμηλό.

Μερικά σημεία που πρέπει να σημειωθούν περιλαμβάνουν:

  1. Ορισμένες μορφές πρωτοκόλλου εκτεταμένου websocket δεν υποστηρίζονται και πρέπει να υλοποιηθούν από εσάς.

  2. Η αντιστοίχιση σειράς αποστολής και λήψης μηνυμάτων πρέπει να ελέγχεται από εσάς.

  3. Η υποστήριξη μετάδοσης αρχείων και ροής μεγάλων δεδομένων δεν είναι αρκετά φιλική και απλή.

  4. Οι υποκείμενοι έξυπνοι δείκτες και οι μηχανισμοί διαχείρισης μνήμης που χρησιμοποιούνται δεν μπορούν να αλλάξουν.

  5. Η υποστήριξη για νέες τυπικές λειτουργίες C++ είναι σχετικά συντηρητική.

Συνολικά, το QWebSocket παρέχει μια πολύ καλή και ώριμη επιλογή για τις περισσότερες εφαρμογές WebSocket που βασίζονται σε Tcp. Υψηλή απόδοση ανάπτυξης και λίγα σφάλματα. Προτιμάται επίσης για εφαρμογές QT. Εάν υπάρχουν απαιτήσεις υψηλότερου επιπέδου, μπορούν να εξεταστούν άλλες υποκείμενες υλοποιήσεις. Αλλά για τις περισσότερες περιπτώσεις, το QWebSocket είναι αρκετά καλό.

Τέλος, επισυνάψτε τον σύνδεσμο πηγαίου κώδικα
Εάν σας βοηθάει, δώστε μου ένα αστέρι

Επίδειξη Qt: διαδικασία εκμάθησης qt (gitee.com)