Compartir tecnología

Programación de red Qt QWebSocket

2024-07-12

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

Objetivos de aprendizaje: Programación de redes Qt QWebSocket

Pre-entorno de aprendizaje

Comunicación de red multiproceso QT TCP-CSDN Blog

Contenido de aprendizaje

WebSocket es una tecnología de red que proporciona un canal de comunicación full-duplex a través de una única conexión TCP. En 2011, IETF estandarizó el protocolo WebSocket como RFC6455 y QWebSocket se puede utilizar tanto en aplicaciones cliente como en aplicaciones de servidor.

Implementa comunicación full-duplex entre el navegador y el servidor, permitiendo al servidor enviar información de forma proactiva al cliente.

caracteristica principal:

  1. A diferencia de HTTP, WebSocket permite que el servidor envíe datos activamente al cliente sin necesidad de que el cliente inicie una solicitud.

  2. Construida sobre el protocolo TCP, la comunicación full-duplex se realiza entre el servidor y el cliente en un único puerto a través del protocolo Ws (WebSocket).

  3. Admite la transmisión de datos en formato texto o binario.

  4. El protocolo se basa en una única conexión TCP. El servidor y el cliente solo necesitan crear una conexión y la conexión no se cerrará.

  5. Admite bibliotecas del lado del cliente y del servidor en múltiples lenguajes de programación, como JavaScript, Java, C#, Python, etc.

Escenarios de aplicación comunes:

  1. Sala de chat: admite conversaciones en tiempo real de baja latencia.

  2. Juego en línea: un motor de sincronización que requiere sincronización en tiempo real del estado del juego.

  3. Cotizaciones de acciones: software de cotizaciones que necesita enviar cotizaciones en tiempo real al cliente.

  4. Videoconferencia: se requiere comunicación de voz y vídeo en tiempo real y de baja latencia.

  5. Edición colaborativa en tiempo real: por ejemplo, el editor de código en línea requiere sincronización en tiempo real.

QWebSocket es la biblioteca de funciones WebSocket proporcionada por Qt. Está construido sobre el módulo de red Qt e implementa el protocolo WebSocket en el estándar RFC6455. Debe agregar QT+=websockets al archivo Qmake

  1. QWebSocket solo admite formatos de mensajes de texto/binarios y no admite otros formatos extendidos. Si es necesario agregar otros protocolos personalizados, los desarrolladores deben manejarlos ellos mismos en la capa de aplicación.

  2. Admite el acceso a servicios websocket utilizando los puertos http/https estándar 80/443 y también admite el protocolo wss (websocket cifrado). Por lo que puede interactuar fácilmente con los servidores web existentes.

  3. Dado que se basa en la implementación del socket QT CP, es totalmente compatible con todas las funciones de la red Qt, como la configuración de proxy, la configuración SSL, etc. Esto es más fácil de usar que algunas interfaces C de bajo nivel.

  4. Admite modos de conexión tanto activos como pasivos. Las conexiones activas se realizan a través de connectToHost() y las conexiones pasivas se aceptan a través del puerto de escucha aceptar(). Ambos modos son muy convenientes.

  5. Para las aplicaciones QT GUI, la recepción de mensajes y las actualizaciones de la interfaz se pueden realizar fácilmente, evitando la complejidad de la programación multiproceso. Por ejemplo, simplemente actualice la interfaz directamente en textMessageReceived().

  6. Después de QT5.10, se admite E/S asíncrona y el rendimiento ha mejorado ligeramente en comparación con antes. La compatibilidad con extensiones de red también es mejor.

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

Ambas funciones sendTextMessage y writeTextMessage se pueden utilizar para enviar mensajes de texto, pero tienen algunas diferencias:

  1. La definición de la función es diferente:
  • sendTextMessage pertenece a la función miembro de QWebSocket;

  • writeTextMessage es una función miembro de QAbstractSocket y QWebSocket hereda de QAbstractSocket.

  1. La eficiencia del envío es diferente:
  • Internamente, sendTextMessage primero convertirá el mensaje a QByteArray y luego lo enviará a través de la función de escritura, que es una conversión más;

  • writeTextMessage escribe directamente el QString que debe enviarse, lo cual es un poco más eficiente.

  1. El soporte asíncrono es diferente:
  • sendTextMessage es una operación sincrónica y regresará una vez completado el envío;

  • writeTextMessage admite llamadas asincrónicas y la función de devolución de llamada se puede especificar a través de Lambda.

  1. El manejo de errores es diferente:
  • sendTextMessage no devolverá información de error y solo se puede manejar mediante errores de señal;

  • writeTextMessage puede obtener el código de error devuelto para determinar la situación del envío.

  1. Diferentes escenarios de uso:
  • sendTextMessage se centra en WebSocket y es adecuado para llamar a la API de WebSocket;

  • writeTextMessage es más general y se puede utilizar con otras subclases de QAbstractSocket.

En general:

  • sendTextMessage es más sencillo de usar, está bien encapsulado y es adecuado para uso básico;

  • writeTextMessage es un poco más eficiente, admite más funciones, como el manejo asincrónico y de errores, y es adecuado para escenarios con requisitos de alto rendimiento o control.

Realizar el proyecto

Comunicación privada entre cliente y cliente, comunicación entre cliente y servidor.

código central

Servidor

Proceso: cree QWebServer, vincule una nueva devolución de llamada de conexión y escuche la desconexión.

Nueva devolución de llamada de conexión: cuando se agrega una nueva conexión a la colección, las devoluciones de llamada fuera de línea, de recepción y de error están vinculadas al nuevo socket de conexión.

Botón Enviar mensaje: clasifica todos y uno, recorre la colección establecida y envía usando 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

Haga clic en el botón para implementar el proceso de conexión de websocket: cree websocket, vincule varias devoluciones de llamada y conéctese a través de open (url)

Botón Enviar mensaje: clasifique los mensajes privados y los mensajes del servidor, encapsule los mensajes privados en formato json y luego envíelos.

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

Resumir

En general, QWebSocket, como componente de la biblioteca de red QT, proporciona un conjunto de API convenientes para desarrollar clientes y servidores WebSocket.

Sus principales ventajas son:

  1. Diseño completamente orientado a objetos, API es simple y fácil de usar.

  2. Altamente integrado con otros componentes de la red QT, como soporte SSL/proxy.

  3. Al utilizar un modelo basado en eventos, los desarrolladores no necesitan lidiar con detalles subyacentes como los subprocesos múltiples.

  4. Naturalmente integrado con las aplicaciones Qt GUI, los mensajes y las actualizaciones de la interfaz se pueden llamar directamente.

  5. Proporciona una implementación completa de las especificaciones básicas de WebSocket, que se pueden utilizar de forma inmediata para facilitar el desarrollo.

  6. El rendimiento también es bueno, especialmente porque QT5.10 admite llamadas de E/S asíncronas.

  7. Hay ejemplos ricos y proyectos de código abierto disponibles como referencia, y la barrera de entrada es baja.

Algunos puntos a tener en cuenta incluyen:

  1. Algunos formatos de protocolo websocket extendido no son compatibles y deben implementarse usted mismo.

  2. Usted debe controlar el orden de coincidencia del envío y la recepción de mensajes.

  3. El soporte para la transmisión de big data de archivos y streaming no es lo suficientemente amigable y sencillo.

  4. Los punteros inteligentes subyacentes y los mecanismos de gestión de memoria utilizados no se pueden cambiar.

  5. El soporte para nuevas características estándar de C++ es relativamente conservador.

En general, QWebSocket proporciona una opción muy buena y madura para la mayoría de las aplicaciones WebSocket basadas en TCP. Alta eficiencia de desarrollo y pocos errores. También se prefiere para aplicaciones QT. Si existen requisitos de nivel superior, se pueden considerar otras implementaciones subyacentes. Pero en la mayoría de los casos, QWebSocket es suficiente.

Finalmente, adjunte el enlace del código fuente.
Si te es útil por favor dame una estrella.

Demostración de Qt: aprendizaje del proceso qt (gitee.com)