WebSocket的原理及QT示例
一.WebSocket 介绍
1.概述
WebSocket 是一种在单个 TCP 连接上进行全双工通讯的协议,它在 2011 年被 IETF 定为标准 RFC 6455,并由 RFC7936 补充规范。与传统的 HTTP 协议不同,WebSocket 允许服务器和客户端之间进行实时、双向的数据传输,打破了 HTTP 协议请求 - 响应的模式限制,大大提高了数据传输的效率和实时性。
2.特点
全双工通信:服务器和客户端可以在任意时刻向对方发送数据,无需等待对方的请求。
实时性强:由于采用了全双工通信,数据可以即时传输,非常适合实时性要求较高的应用场景,如在线聊天、实时数据监控等。
较少的开销:WebSocket 握手阶段使用 HTTP 协议,建立连接后,数据传输不再需要像 HTTP 那样携带大量的头部信息,减少了数据传输的开销。
跨域支持:WebSocket 支持跨域通信,方便不同域名之间的服务器和客户端进行数据交互。
客户端与服务器交互图
3.应用场景
实时聊天应用:如在线客服、社交聊天等,用户可以即时收到对方发送的消息。
实时数据监控:如股票行情、传感器数据监控等,服务器可以实时将最新的数据推送给客户端。
多人在线游戏:实现游戏中玩家之间的实时交互,如位置更新、动作同步等。
二.代码示例
1.客户端代码
#include "Clientdialog.h"
#include <QLabel>
#include <QWidget>
#include <QHBoxLayout>
#include <QVBoxLayout>
#include <QtCore>
#include <QDebug>
#include <iostream>
ClientDialog::ClientDialog(QWidget *parent)
: QWidget(parent)
{
//layout1
QLabel *iplabel = new QLabel("Server IP");
m_iplineedit =new QLineEdit;
m_iplineedit->setText("127.0.0.1");
QLabel *portlabel =new QLabel("Server端口");
m_portspinbox = new QSpinBox;
m_portspinbox->setRange(0,65535);
m_portspinbox->setValue(5123);
m_linkbutton = new QPushButton("连接");
m_disconnectbutton = new QPushButton("断开");
pButtonGroup = new QButtonGroup();
pButtonGroup->setExclusive(true);
m_linkbutton->setCheckable(true);
m_disconnectbutton->setCheckable(true);
pButtonGroup->addButton(m_linkbutton,0);
pButtonGroup->addButton(m_disconnectbutton,1);
QHBoxLayout *qhboxlayout1 = new QHBoxLayout;
qhboxlayout1->addWidget(iplabel);
qhboxlayout1->addWidget(m_iplineedit);
qhboxlayout1->addWidget(portlabel);
qhboxlayout1->addWidget(m_portspinbox);
qhboxlayout1->addWidget(m_linkbutton);
qhboxlayout1->addWidget(m_disconnectbutton);
//layout2
QLabel *sendmessagelabel = new QLabel("发送消息");
QHBoxLayout *qhboxlayout2 = new QHBoxLayout;
qhboxlayout2->addWidget(sendmessagelabel);
//layout3
m_sendmessagetextedit = new QTextEdit;
m_sendmessagetextedit->setFixedHeight(50);
m_sendbutton = new QPushButton("发送");
m_sendbutton->setFixedHeight(50);
QHBoxLayout *qhboxlayout3 = new QHBoxLayout;
qhboxlayout3->addWidget(m_sendmessagetextedit);
qhboxlayout3->addWidget(m_sendbutton);
//layout4
QLabel *receivemessagelabel = new QLabel("接收消息");
QHBoxLayout *qhboxlayout4 = new QHBoxLayout;
qhboxlayout4->addWidget(receivemessagelabel);
//layout5
m_receivemessageTextEdit = new QTextEdit;
QHBoxLayout *qhboxlayout5 = new QHBoxLayout;
qhboxlayout5->addWidget(m_receivemessageTextEdit);
m_receivemessageTextEdit->setReadOnly(true);
//layout6
statusLabel = new QLabel("连接状态");
m_clean = new QPushButton("清除");
QHBoxLayout *qhboxlayout6 = new QHBoxLayout;
qhboxlayout6->addWidget(statusLabel);
qhboxlayout6->addStretch();
qhboxlayout6->addWidget(m_clean);
//
QVBoxLayout *mainlayout = new QVBoxLayout;
mainlayout->addLayout(qhboxlayout1,1);
mainlayout->addLayout(qhboxlayout2,0.5);
mainlayout->addLayout(qhboxlayout3,1);
mainlayout->addLayout(qhboxlayout4,0.5);
mainlayout->addLayout(qhboxlayout5,3);
mainlayout->addLayout(qhboxlayout6,1);
setLayout(mainlayout);
setWindowTitle("Websocket Client");
this->setFixedSize(800, 600); // 窗口固定为800x600像素,无法缩放
connect(m_linkbutton,SIGNAL(clicked(bool)),this,SLOT(connectToServer()));
connect(m_disconnectbutton,SIGNAL(clicked(bool)),this,SLOT(stopClicked()));
connect(m_sendbutton,SIGNAL(clicked(bool)),this,SLOT(onSendButtonClicked()));
connect(m_clean,SIGNAL(clicked(bool)),this,SLOT(onCleanButtonClicked()));
connect(&m_websocket,SIGNAL(connected()),this,SLOT(onconnected()));
connect(&m_websocket,SIGNAL(disconnected()),this,SLOT(closeConnection()));
connect(&m_websocket,SIGNAL(textMessageReceived(QString)),this,SLOT(onTextMessageReceived(QString)));
}
ClientDialog::~ClientDialog()
{
m_websocket.errorString();
m_websocket.close();
}
//断开连接操作
void ClientDialog::closeConnection(){
m_linkbutton->setEnabled(true);
m_disconnectbutton->setEnabled(false);
m_sendmessagetextedit->setEnabled(false);
m_sendbutton->setEnabled(false);
m_receivemessageTextEdit->setEnabled(false);
m_clean->setEnabled(false);
statusLabel->setText(tr("disconnected"));
}
//连接服务器
void ClientDialog::connectToServer()
{
QString path = QString("ws://%1:%2").arg(m_iplineedit->text()).arg(m_portspinbox->text());
QUrl url = QUrl(path);
m_websocket.open(url);
}
//连接上之后
void ClientDialog::onconnected(){
qDebug() << "hello word!";
statusLabel->setText(tr("connected"));
m_linkbutton->setEnabled(false);
m_disconnectbutton->setEnabled(true);
m_sendmessagetextedit->setEnabled(true);
m_sendbutton->setEnabled(true);
m_receivemessageTextEdit->setEnabled(true);
m_clean->setEnabled(true);
}
//收到消息
void ClientDialog::onTextMessageReceived(const QString &message)
{
QString time = current_date_time->currentDateTime().toString("yyyy.MM.dd hh:mm:ss.zzz ddd");
m_receivemessageTextEdit->setText(time + "\r\n" + "message: " + message);
}
//断开
void ClientDialog::stopClicked()
{
m_websocket.close();
}
//发送消息
void ClientDialog::onSendButtonClicked()
{
QString msg= m_sendmessagetextedit->document()->toPlainText();
m_websocket.sendTextMessage(msg);
}
//清除内容
void ClientDialog::onCleanButtonClicked()
{
m_receivemessageTextEdit->clear();
}
2.服务端代码
#include "webserver.h"
#include "ui_webserver.h"
#include <QtCore/QVariant>
#include <QtWidgets/QApplication>
#include <QtWidgets/QButtonGroup>
#include <QtWidgets/QHeaderView>
#include <QtWidgets/QLabel>
#include <QtWidgets/QListWidget>
#include <QtWidgets/QPushButton>
#include <QtWidgets/QSpinBox>
#include <QtWidgets/QTextBrowser>
#include <QtWidgets/QWidget>
#include <QHBoxLayout>
webserver::webserver(QWidget *parent) :
QWidget(parent),
ui(new Ui::webserver)
{
ui->setupUi(this);
{
//qhboxLayout1部分代码
QLabel* monitorLabel= new QLabel("监听端口");
m_monitorSpinBox = new QSpinBox();
m_monitorSpinBox->setRange(0,65535);
m_monitorSpinBox->setValue(5123);
m_startButton = new QPushButton("启动服务");
m_stopButton = new QPushButton("停止服务");
m_stopButton->setEnabled(false);
QHBoxLayout *qhboxLayout = new QHBoxLayout;
qhboxLayout->addWidget(monitorLabel);
qhboxLayout->addWidget(m_monitorSpinBox);
qhboxLayout->addWidget(m_startButton);
qhboxLayout->addWidget(m_stopButton);
//qhboxLayout2部分代码
QLabel* sendLabel=new QLabel("发送消息");
sendLabel->setFixedHeight(30);
QHBoxLayout *qhboxLayout2 = new QHBoxLayout;
qhboxLayout2->addWidget(sendLabel);
//qhboxLayout3部分代码
m_sendTextedit = new QTextEdit;
m_sendTextedit = new QTextEdit();
m_sendTextedit->setEnabled(false);
m_sendTextedit->setFixedHeight(80);
m_sendButton= new QPushButton("发送");
m_sendButton->setEnabled(false);
m_sendButton->setFixedHeight(50);
QHBoxLayout *qhboxlayout3 = new QHBoxLayout;
qhboxlayout3->addWidget(m_sendTextedit,2);
qhboxlayout3->addWidget(m_sendButton,1);
//qvboxlayout411
QLabel* receiveLabel = new QLabel("接收消息");
receiveLabel->setFixedHeight(30);
m_receiveTextEdit = new QTextEdit();
m_receiveTextEdit->setReadOnly(true);
QVBoxLayout *qvboxlayout411 = new QVBoxLayout;
qvboxlayout411->addWidget(receiveLabel);
qvboxlayout411->addWidget(m_receiveTextEdit);
//qhboxlayout412
m_cleanButton = new QPushButton("清除");
m_cleanButton->setEnabled(false);
QHBoxLayout *qhboxlayout412 = new QHBoxLayout;
qhboxlayout412->addStretch();
qhboxlayout412->addWidget(m_cleanButton);
//qvboxlayout41
QVBoxLayout *qvboxlayout41 = new QVBoxLayout;
qvboxlayout41->addLayout(qvboxlayout411);
qvboxlayout41->addLayout(qhboxlayout412);
//qvboxlayout42
QLabel* linkclientLabel=new QLabel("连接客户端");
linkclientLabel->setFixedHeight(30);
m_linkclientListWidget=new QListWidget;
QVBoxLayout* qvboxlayout42 = new QVBoxLayout;
qvboxlayout42->addWidget(linkclientLabel);
qvboxlayout42->addWidget(m_linkclientListWidget);
//qvboxlayout4
QHBoxLayout *qhboxlayout4 = new QHBoxLayout;
qhboxlayout4->addLayout(qvboxlayout41,2);
qhboxlayout4->addLayout(qvboxlayout42,1);
//mainlayout
QVBoxLayout *mainLayout = new QVBoxLayout;
mainLayout->addLayout(qhboxLayout,1);
mainLayout->addLayout(qhboxLayout2,1);
mainLayout->addLayout(qhboxlayout3,1);
mainLayout->addLayout(qhboxlayout4,3);
this->setLayout(mainLayout);
this->setWindowTitle("Websocket Server -- Neo");
this->setFixedSize(800, 600); // 窗口固定为800x600像素,无法缩放
}
m_WebSocketServer=new QWebSocketServer("server",QWebSocketServer::NonSecureMode);
connect(m_WebSocketServer,SIGNAL(newConnection()),this,SLOT(onNewConnection()));
connect(m_WebSocketServer, SIGNAL(closed()), this, SLOT(onClosed()));
connect(m_WebSocketServer, SIGNAL(serverError(QWebSocketProtocol::CloseCode)),
this, SLOT(onServerError(QWebSocketProtocol::CloseCode)));
connect(m_startButton,SIGNAL(clicked(bool)),this,SLOT(onStartButtonClick()));
connect(m_stopButton,SIGNAL(clicked(bool)),this,SLOT(onStopButtonClick()));
connect(m_cleanButton,SIGNAL(clicked(bool)),this,SLOT(onCleanButtonClick()));
connect(m_sendButton,SIGNAL(clicked(bool)),this,SLOT(onSendButtonClick()));
}
webserver::~webserver()
{
if(m_WebSocketServer)
{
m_WebSocketServer->close();
}
delete ui;
}
//开启服务
void webserver::onStartButtonClick(){
int i_port = m_monitorSpinBox->text().toInt();
m_WebSocketServer->listen(QHostAddress::Any,i_port);
m_startButton->setEnabled(false);
m_stopButton->setEnabled(true);
qDebug()<<m_WebSocketServer->isListening();
qDebug()<<m_WebSocketServer->serverPort();
qDebug()<<m_WebSocketServer->serverAddress();
}
//停止服务
void webserver::onStopButtonClick(){
m_startButton->setEnabled(true);
m_stopButton->setEnabled(false);
m_WebSocketServer->close();
}
//发送信息
void webserver::onSendButtonClick(){
QString msg = m_sendTextedit->document()->toPlainText();
int currenRow = m_linkclientListWidget->currentRow();//当前单击选中ListWidget控件的行号
if(currenRow==-1)
{
currenRow = 0;
}
QString key = m_linkclientListWidget->item(currenRow)->text();
if(_hashIpPort2PWebSocket.contains(key))
{
_hashIpPort2PWebSocket.value(key)->sendTextMessage(msg);
}
}
void webserver::onCleanButtonClick(){
m_receiveTextEdit->clear();
}
//连接上之后
void webserver::onNewConnection(){
qDebug() << "connect ok";
m_startButton->setEnabled(false);
m_stopButton->setEnabled(true);
m_sendTextedit->setEnabled(true);
m_sendButton->setEnabled(true);
m_cleanButton->setEnabled(true);
QWebSocket *pWebSocket = m_WebSocketServer->nextPendingConnection();
connect(pWebSocket,SIGNAL(textMessageReceived(QString)),this,SLOT(slot_processTextMessage(QString)));
connect(pWebSocket,SIGNAL(disconnected()),this,SLOT(slot_socketDisconnected()));
connect(pWebSocket, SIGNAL(error(QAbstractSocket::SocketError)),
this , SLOT(slot_error(QAbstractSocket::SocketError)));
quint32 ipv4Address = pWebSocket->peerAddress().toIPv4Address();
QString ipString = QHostAddress(ipv4Address).toString();
_hashIpPort2PWebSocket.insert(QString("%1-%2").arg(ipString).arg(pWebSocket->peerPort()),pWebSocket);
QString item = QString("%1-%2").arg(ipString).arg(pWebSocket->peerPort());
m_linkclientListWidget->addItem(item);
}
//停止之后
void webserver::onClosed()
{
QList<QWebSocket *> _listWebSocket = _hashIpPort2PWebSocket.values();
for(int index = 0; index < _listWebSocket.size(); index++)
{
_listWebSocket.at(index)->close();
}
_hashIpPort2PWebSocket.clear();
m_linkclientListWidget->clear();
}
//连接错误
void webserver::onServerError(QWebSocketProtocol::CloseCode closeCode)
{
QWebSocket *pWebSocket = dynamic_cast<QWebSocket *>(sender());
if(!pWebSocket)
{
return;
}
}
void webserver::slot_error(QAbstractSocket::SocketError error)
{
QWebSocket *pWebSocket = dynamic_cast<QWebSocket *>(sender());
if(!pWebSocket)
{
return;
}
}
//收到消息并显示
void webserver::slot_processTextMessage(QString message){
QWebSocket *pWebSocket = dynamic_cast<QWebSocket *>(sender());
if(!pWebSocket)
{
return;
}
QString time = current_date_time->currentDateTime().toString("yyyy.MM.dd hh:mm:ss.zzz ddd");
quint32 ipv4Address = pWebSocket->peerAddress().toIPv4Address();
QString ipString = QHostAddress(ipv4Address).toString();
QString item = QString("IP: %1, port: %2").arg(ipString).arg(pWebSocket->peerPort());
m_receiveTextEdit->append(time+ "\r\n" + item + "\r\n" + "message: " + message + "\r\n");
//qDebug()<< pWebSocket->peerAddress().toString();
//qDebug() << "Client IP address: " << ipString;
}
//连接断开的操作
void webserver::slot_socketDisconnected(){
QWebSocket *pWebSocket = dynamic_cast<QWebSocket *>(sender());
if(!pWebSocket)
{
return;
}
//qDebug() << __FILE__ << __LINE__ << __FUNCTION__;
quint32 ipv4Address = pWebSocket->peerAddress().toIPv4Address();
QString ipString = QHostAddress(ipv4Address).toString();
QString item1 = QString("%1-%2").arg(ipString).arg(pWebSocket->peerPort());
_hashIpPort2PWebSocket.remove(QString("%1-%2").arg(ipString).arg(pWebSocket->peerPort()));
QListWidgetItem *item3;
for(int i=0;i<m_linkclientListWidget->count();i++)
{
QString str = m_linkclientListWidget->item(i)->text();
if(str == item1)
{
item3 = m_linkclientListWidget->takeItem(i);
m_linkclientListWidget->removeItemWidget(item3);
delete item3;
}
}
}
void webserver::on_m_linkclientListWidget_clicked(const QModelIndex &index)
{
int currenRow = m_linkclientListWidget->currentRow();
}
3.完整工程代码下载
https://download.csdn.net/download/xieliru/90787178