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