简介
WebSocket 是一种网络传输协议,可在单个 TCP 连接上进行全双工通信,位于 OSI 模型的应用层。允许服务端主动向客户端推送数据。
在 WebSocket API 中,浏览器和服务器只需要完成一次握手,两者之间就可以创建持久性的连接,并进行双向数据传输。WebSocket协议基于TCP协议实现,包含初始的握手过程,以及后续的多次数据帧双向传输过程。
默认情况下:WebSocket 协议使用 80 端口;
WebSocket目前支持两种统一资源标志符ws和wss,类似于HTTP和HTTPS。
如: ws://example.com:80/some/path
如: ws://127.0.0.1:45678 即ip+端口
产生背景:
因为 HTTP 协议有一个缺陷:通信只能由客户端发起。
轮询的效率低,非常浪费资源(因为必须不停连接,或者 HTTP 连接始终打开), 因此websocket应运而生。
优点:
1)较少的控制开销:在连接创建后,服务器和客户端之间交换数据时,用于协议控制的数据包头部相对较小;
2)更强的实时性:由于协议是全双工的,所以服务器可以随时主动给客户端下发数据。相对于 HTTP 请求需要等待客户端发起请求服务端才能响应,延迟明显更少;
3)保持连接状态:与 HTTP 不同的是,WebSocket 需要先创建连接,这就使得其成为一种有状态的协议,之后通信时可以省略部分状态信息;
4)更好的二进制支持:WebSocket 定义了二进制帧,相对 HTTP,可以更轻松地处理二进制内容;
5)可以支持扩展:WebSocket 定义了扩展,用户可以扩展协议、实现部分自定义的子协议。
QWebSocket
要使用 Qt 的 WebSocket 模块,先在 pro 文件中加上 websockets.
QT += websockets
QWebSocket类
常用函数
QHostAddress localAddress() const;
quint16 localPort() const;
QUrl requestUrl() const;
QNetworkRequest request() const;
当有需要接收的数据时,会发出该信号
void textMessageReceived(const QString &message); //字符串的方式接收数据信息
void binaryMessageReceived(const QByteArray &message);//二进制的方式接收数据信息
当需要发送数据时,调用下面的发送函数
qint64 sendTextMessage(const QString &message); //字符串的方式发送数据信息
qint64 sendBinaryMessage(const QByteArray &data);//二进制的方式发送数据信息
客户端断开连接,收到断开信号:
void disconnected();
服务端QWebSocketServer
QWebSocketServer以 QTcpServer 为模型,并且行为相同。所以,如果你知道如何使用 QTcpServer,你就知道如何使用 QWebSocketServer.
常用的函数、信号、槽函数:
- bool listen(const QHostAddress &address = QHostAddress::Any, quint16 port = 0);
监听连接。 如果端口为 0,则自动选择一个端口。 如果地址是 QHostAddress::Any,则服务器将侦听所有网络接口。
- void newConnection(); 信号,接收连接,发出该信号
- virtual QWebSocket *nextPendingConnection(); //获取连接的客户端
- void close(); //关闭监听套接字
流程
- 创建服务器:new QWebSocketServer
- 监听:listen
m_pWebSocketServer->listen(QHostAddress::LocalHost, mPort);//端口号
- 有新的连接,触发这个信号:QWebSocketServer::newConnection
- 在处理newConnection信号的槽函数中,获得新的QWebSocket客户端:QWebSocketServer::nextPendingConnection
- 接收到信息时候,触发信号:QWebSocket::binaryMessageReceived或者QWebSocket::textMessageReceived
- 发送数据,调用函数QWebSocket::sendTextMessage或QWebSocket::sendBinaryMessage
- 客户端断开连接,触发信号:QWebSocket::disconnected,在处理disconnected信号的槽函数中,QWebSocket客户端调用deleteLater,进行释放资源。
- 主动关闭服务端,调用QWebSocketServer::close,所有的客户端连接qDeleteAll(m_clients.begin(), m_clients.end());
客户端QWebSocket
客户端直接使用QWebSocket的函数进行操作即可
流程
- 创建客户端:new QWebSocket
- 通过QWebSocket的open 函数连接服务端的 Url
- 连接服务端成功后,会触发QWebSocket::connected信号。从而获知连接成功
- 发送数据,调用函数QWebSocket::sendTextMessage或QWebSocket::sendBinaryMessage
- 接收到信息时候,触发信号:QWebSocket::binaryMessageReceived或者QWebSocket::textMessageReceived
- 客户端关闭时,要主动调QWebSocket的close函数,让服务端知道该客户端已断开连接。
- 断开连接成功,会触发信号QWebSocket::disconnected,在该信号的槽函数处理断开连接的相应工作。
完整代码
开发环境:QT5.15.2 MSVC 2019 64Bit
服务端
#ifndef WIDGET_H#define WIDGET_H#include <QWidget>#include <QWebSocketServer>#include <QWebSocket>#include <QMap>QT_BEGIN_NAMESPACEnamespace Ui { class Widget; }QT_END_NAMESPACEclass Widget : public QWidget{Q_OBJECTpublic:Widget(QWidget *parent = nullptr);~Widget();signals:void sendTextMessageSignal(QString msg);private slots:void on_pushButton_Listen_clicked();void OnNewConnectionSlot();void OnTextReceivedSlot(QString msg);void OndisconnectedSlot();void on_pushButton_send_clicked();private:Ui::Widget *ui;QWebSocketServer *server;QList<QWebSocket*> m_clientList;QMap<QWebSocket*,QString> m_clientStatus;};#endif // WIDGET_H#include "widget.h"#include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget){ui->setupUi(this);server = new QWebSocketServer("testserver",QWebSocketServer::NonSecureMode,this);connect(server,&QWebSocketServer::newConnection,this,&Widget::OnNewConnectionSlot);ui->lineEdit_address->setText("Any");ui->lineEdit_port->setText("45678");}Widget::~Widget(){qDeleteAll(m_clientList);server->close();delete ui;}void Widget::on_pushButton_Listen_clicked(){QHostAddress address;if(ui->lineEdit_address->text()=="Any"){address=QHostAddress::Any;}else{address=QHostAddress(ui->lineEdit_address->text());}int nPort = ui->lineEdit_port->text().toInt();bool bListen = server->listen(address,nPort);if(bListen)ui->pushButton_Listen->setEnabled(false);}void Widget::OnNewConnectionSlot(){QWebSocket *client = server->nextPendingConnection();m_clientList.append(client);qDebug() << "Client:" << client->peerAddress().toString() << client->peerName() <<client->peerPort() ;QString strstatus = "ip:" + client->peerAddress().toString() + " 端口:"+ QString::number(client->peerPort()) + "连接成功\n";m_clientStatus.insert(client,strstatus);ui->textEdit_clientlist->insertPlainText(strstatus);ui->textEdit_clientlist->connect(client,&QWebSocket::textMessageReceived,this,&Widget::OnTextReceivedSlot);connect(this,&Widget::sendTextMessageSignal,client,&QWebSocket::sendTextMessage);//给所有客户端发送数据connect(client,&QWebSocket::disconnected,this,&Widget::OndisconnectedSlot);}void Widget::OnTextReceivedSlot(QString msg){ui->textEdit_recv->setText(msg);QWebSocket *pClient = qobject_cast<QWebSocket *>(sender());pClient->sendTextMessage("回答" + msg);}void Widget::OndisconnectedSlot(){QWebSocket *pClient = qobject_cast<QWebSocket *>(sender());m_clientStatus.remove(pClient);ui->textEdit_clientlist->clear();QList<QString> textlist = m_clientStatus.values();for(auto text : textlist){ui->textEdit_clientlist->insertPlainText(text);}m_clientList.removeAll(pClient);pClient->deleteLater();}void Widget::on_pushButton_send_clicked(){QString msg = ui->textEdit_send->toPlainText();emit sendTextMessageSignal(msg);}
客户端
#ifndef WIDGET_H#define WIDGET_H#include <QWidget>#include <QWebSocket>QT_BEGIN_NAMESPACEnamespace Ui { class Widget; }QT_END_NAMESPACEclass Widget : public QWidget{Q_OBJECTpublic:Widget(QWidget *parent = nullptr);~Widget();private slots:void on_pushButton_connect_clicked();void onConnectedSlot();void ondisconnectedSlot();void onTextReceivedSlot(QString message);void on_pushButton_send_clicked();void on_pushButton_disconnect_clicked();private:Ui::Widget *ui;QWebSocket *m_ClientSocket;};#endif // WIDGET_H#include "widget.h"#include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget){ui->setupUi(this);m_ClientSocket = new QWebSocket();m_ClientSocket->setParent(this);ui->label_connet->setText("尚未连接服务端");ui->pushButton_connect->setEnabled(true);ui->pushButton_disconnect->setEnabled(false);ui->lineEdit_url->setText("ws://127.0.0.1:45678");}Widget::~Widget(){m_ClientSocket->close();delete ui;}void Widget::on_pushButton_connect_clicked(){QUrl url(ui->lineEdit_url->text());m_ClientSocket->open(url);connect(m_ClientSocket, &QWebSocket::connected, this, &Widget::onConnectedSlot);connect(m_ClientSocket, &QWebSocket::disconnected, this, &Widget::ondisconnectedSlot);}void Widget::onConnectedSlot(){ui->pushButton_connect->setEnabled(false);ui->pushButton_disconnect->setEnabled(true);QString strstatus = "连接服务端" + ui->lineEdit_url->text() + "成功";ui->label_connet->setText(strstatus);connect(m_ClientSocket, &QWebSocket::textMessageReceived, this, &Widget::onTextReceivedSlot);}void Widget::ondisconnectedSlot(){QString strstatus = "断开服务端" + ui->lineEdit_url->text() + "连接";ui->label_connet->setText(strstatus);ui->pushButton_connect->setEnabled(true);ui->pushButton_disconnect->setEnabled(false);}void Widget::onTextReceivedSlot(QString message){ui->textEdit_recv->setText(message);}void Widget::on_pushButton_send_clicked(){QString message = ui->textEdit_send->toPlainText();m_ClientSocket->sendTextMessage(message);}void Widget::on_pushButton_disconnect_clicked(){m_ClientSocket->close();}