websocket
websocket协议是基于tcp的协议,它的基本结构如图:建立websocket之间先跟websocket服务器建立tcp连接,再发送http请求实现协议切换,websocket服务器可以使用python搭建一个:注意:python需要安装websockets库:运行以上脚本就会创建一个websocket的回传服务器,建立回传服务器之后,使用qt的tcp编程连接这个服务器,然后发送http请求,
websocket协议是基于tcp的协议,它的基本结构如图:
1.建立websocket连接
建立websocket之间先跟websocket服务器建立tcp连接,再发送http请求实现协议切换,websocket服务器可以使用python搭建一个:
import asyncio
import websockets
async def echo(websocket):
async for message in websocket:
await websocket.send(f"服务器回复: {message}")
async def main():
async with websockets.serve(echo, "0.0.0.0", 8765):
await asyncio.Future()
asyncio.run(main())
注意:python需要安装websockets库:
pip install websockets
运行以上脚本就会创建一个websocket的回传服务器,建立回传服务器之后,使用qt的tcp编程连接这个服务器,然后发送http请求,转换为websocket连接,qt主要程序如:
1.1 建立tcp链接:
WebSocketTcpClient::WebSocketTcpClient(QObject *parent)
: QObject(parent)
, m_handshakeDone(false)
{
m_tcpSocket = new QTcpSocket(this);
m_timer = new QTimer(this);
connect(m_tcpSocket, &QTcpSocket::connected, this, &WebSocketTcpClient::onTcpConnected);
connect(m_tcpSocket, &QTcpSocket::readyRead, this, &WebSocketTcpClient::onDataReceived);
connect(m_timer, &QTimer::timeout, this, &WebSocketTcpClient::sendAutoMessage);
m_tcpSocket->connectToHost("127.0.0.1", 8765);
}
1.2 切换协议为websocket:
void WebSocketTcpClient::sendUpgradeRequest()
{
QString key = "dGhlIHNhbXBsZSBub25jZQ==";
QString req = QString(
"GET / HTTP/1.1\r\n"
"Host: 127.0.0.1:8765\r\n"
"Upgrade: websocket\r\n"
"Connection: Upgrade\r\n"
"Sec-WebSocket-Key: %1\r\n"
"Sec-WebSocket-Version: 13\r\n\r\n"
).arg(key);
m_tcpSocket->write(req.toUtf8());
}
其中key:QString key = "dGhlIHNhbXBsZSBub25jZQ==";
① key 必须是一个随机生成的 16 字节数据
② 然后做 Base64 编码
结果就是:Base64 编码后的字符串长度 = 24 个字符(可能末尾带 ==)
✔ Base64 编码后长度固定为 24 字符
✔ 内容必须随机,不能每次都一样
✔ 这个固定 key 只是为了测试
Sec-WebSocket-Version:13 :这个版本是固定的。
1.3 websocket服务器响应成功:
void WebSocketTcpClient::onDataReceived()
{
QByteArray data = m_tcpSocket->readAll();
if (!m_handshakeDone) {
// 打印服务器返回的握手信息(调试用)
qDebug() << "[WS] 握手响应:\n" << data;
// 1. 检查是否切换协议成功
if (data.contains("101 Switching Protocols")) {
// ==============================================
// ✅【关键增加】开始校验 Sec-WebSocket-Accept
// ==============================================
QString response = QString::fromUtf8(data);
// ① 从服务器返回的头中,提取 Sec-WebSocket-Accept
QString serverAccept;
int acceptIndex = response.indexOf("Sec-WebSocket-Accept:");
if (acceptIndex != -1) {
int start = acceptIndex + 21;
int end = response.indexOf("\r\n", start);
serverAccept = response.mid(start, end - start).trimmed();
}
// ② 客户端自己用【固定算法】计算正确的 Accept
// 你发送的 Key(必须和发送握手时用的一样!)
QString clientKey = "dGhlIHNhbXBsZSBub25jZQ==";
// WebSocket 标准固定串(全球唯一,不能改)
QString magic = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
// SHA1 + Base64 编码
QByteArray sha1Result = QCryptographicHash::hash(
(clientKey + magic).toUtf8(),
QCryptographicHash::Sha1
);
QString realAccept = sha1Result.toBase64();
// ③ 校验:服务器返回的 和 我算的 是否一致
if (serverAccept == realAccept) {
qDebug() << "[WS] ✅ 握手成功 + 服务端认证成功!";
m_handshakeDone = true;
m_timer->start(1000);
} else {
qDebug() << "[WS] ❌ 认证失败!非法服务器!";
qDebug() << "期望:" << realAccept;
qDebug() << "收到:" << serverAccept;
m_tcpSocket->close();
}
}
} else {
// 已经握手完成,正常解析帧
parseWebSocketFrame(data);
}
}
实际握手响应字符串[WS] :
"HTTP/1.1 101 Switching Protocols\r\n
Date: Sat, 23 May 2026 17:16:42 GMT\r\n
Upgrade: websocket\r\nConnection: Upgrade\r\n
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n
Server: Python/3.12 websockets/16.0\r\n\r\n"
websocket服务器响应的 Sec-WebSocket-Accept: 计算方法如代码所示。使用 key + WebSocket 标准固定串 做 sha1 哈希运算,再做 Base64 编码得出字符串。
2.websocket数据发送
// ============================
// 发送(标准掩码,正确)
// ============================
void WebSocketTcpClient::sendAutoMessage()
{
static int count = 0;
count++;
QString msg = QString("TCP原生WebSocket消息 %1").arg(count);
QByteArray payload = msg.toUtf8();
uchar mask[4] = {0x11,0x22,0x33,0x44};
QByteArray encoded = payload;
for (int i=0; i<payload.size(); i++) {
encoded[i] = payload[i] ^ mask[i%4];
}
QByteArray frame;
frame.append((char)0x81);
frame.append((char)(0x80 | payload.size()));
frame.append((char)mask[0]);
frame.append((char)mask[1]);
frame.append((char)mask[2]);
frame.append((char)mask[3]);
frame.append(encoded);
m_tcpSocket->write(frame);
qDebug() << "[SEND] " << msg;
}
发送函数如上所示,把要发送的文本做utf-8编码。
定义掩码(MASK):
- 掩码是4 个随机字节
- 我这里写死只是为了演示
- 真实环境应该随机生成
编码数据:编码后数据 = 原始数据 异或(^) 掩码,如代码所示循环异或所有数据。
第 1 个字节:0x81,二进制:10000001:
- 1 (FIN) 表示这是最后一帧
- 000 (RSV) 保留位,必须为0
- 0001 (opcode) 表示这是文本帧
第 2 个字节:0x80 | payload.size():
最高位 1 表示有掩码, 低7位 表示数据长度
追加 4 字节掩码 key,客户端必须发送掩码。
最终发送数据结构:
- 第1字节:0x81(FIN + 文本帧)
- 第2字节:0x80 | 长度(掩码+长度)
- 第3~6字节:4字节掩码
- 第7字节开始:编码后的 payload 数据
3.websocket数据接收
// ============================
// 解析 WebSocket 帧(含关闭处理)
// ============================
void WebSocketTcpClient::parseWebSocketFrame(const QByteArray &data)
{
if (data.size() < 2)
return;
const uchar *buf = (const uchar*)data.constData();
int pos = 0;
// 第1字节:FIN + 操作码
uchar fin_op = buf[pos++];
// 第2字节:掩码位 + 长度
uchar mask_len = buf[pos++];
// 解析
bool fin = (fin_op & 0x80) != 0; // 是否最后一帧
int opcode = fin_op & 0x0F; // 帧类型
bool mask = (mask_len & 0x80) != 0; // 是否有掩码
int payload_len = mask_len & 0x7F; // 数据长度
// ==========================================
// ✅ 【关键】如果是 关闭帧(opcode == 0x08)
// ==========================================
if (opcode == 0x08) {
qDebug() << "[WS] 收到服务器关闭帧 → 安全关闭TCP连接";
m_timer->stop(); // 停止定时器
m_tcpSocket->close(); // 关闭TCP
return;
}
// 读取掩码key
QByteArray maskKey;
if (mask) {
maskKey = data.mid(pos, 4);
pos += 4;
}
// 读取数据
QByteArray payload = data.mid(pos, payload_len);
// 解码(只有mask=1才解码,服务器下发没有掩码)
if (mask) {
for (int i = 0; i < payload_len; i++) {
payload[i] = payload[i] ^ maskKey[i % 4];
}
}
qDebug() << "[RECV] 正确收到:" << QString::fromUtf8(payload);
}
4.关闭websocket
// ============================
// 标准关闭 WebSocket(客户端主动关闭)
// ============================
void WebSocketTcpClient::closeWebSocket()
{
if (m_tcpSocket->state() == QAbstractSocket::ConnectedState)
{
qDebug() << "[WS] 主动发送关闭帧,请求断开连接";
// 关闭帧格式
QByteArray closeFrame;
closeFrame.append((char)0x88); // FIN=1 + opcode=8 (关闭帧)
closeFrame.append((char)0x00); // 长度0,无数据
m_tcpSocket->write(closeFrame);
m_tcpSocket->flush();
m_timer->stop(); // 立即停止发送
}
}
注意:这里只是发送了关闭帧,websocket回复消息后parseWebSocketFrame自动关闭tcp socket。
5.示例工程
https://github.com/1358484518/websockets/tree/main
https://github.com/1358484518/websockets/tree/main
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐
所有评论(0)