从零实现 WebSocket 服务器:C++/Node.js/Python 全栈指南
·
从零实现 WebSocket 服务器:C++/Node.js/Python 全栈指南
1. WebSocket 核心原理
WebSocket 协议基于 TCP 实现双向通信,关键步骤:
- 握手阶段:HTTP 升级请求(客户端)→ 101 响应(服务器)
- 数据帧:握手后传输二进制帧,格式如下: $$ \begin{array}{|c|c|c|c|c|c|c|c|} \hline \text{FIN} & \text{RSV} & \text{Opcode} & \text{MASK} & \text{Payload Len} & \text{Extended Len} & \text{Masking Key} & \text{Payload} \ \hline 1b & 3b & 4b & 1b & 7b & 0/16/64b & 0/32b & \text{变长} \ \hline \end{array} $$
2. Python 实现(基础版)
import socket, hashlib, base64
def ws_handshake(client_sock):
data = client_sock.recv(1024).decode()
key = [line.split(":")[1].strip() for line in data.splitlines() if "Sec-WebSocket-Key" in line][0]
accept = base64.b64encode(hashlib.sha1((key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11").encode()).digest())
response = f"HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: {accept.decode()}\r\n\r\n"
client_sock.send(response.encode())
def parse_frame(data):
if len(data) < 2: return None
payload_len = data[1] & 127
mask_start = 2 + (4 if payload_len == 126 else 8 if payload_len == 127 else 0)
masks = data[mask_start-4:mask_start] if data[1] & 128 else None
payload = data[mask_start:mask_start+payload_len]
return bytes(payload[i] ^ masks[i%4] for i in range(len(payload))) if masks else payload
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind(('localhost', 8080))
server.listen()
client, addr = server.accept()
ws_handshake(client)
while True:
frame = client.recv(1024)
message = parse_frame(frame)
if message: print("Received:", message.decode())
3. Node.js 实现(事件驱动)
const net = require('net');
const crypto = require('crypto');
const server = net.createServer(socket => {
let buffer = Buffer.alloc(0);
socket.on('data', data => {
buffer = Buffer.concat([buffer, data]);
// 握手阶段
if (buffer.toString().includes('Sec-WebSocket-Key')) {
const key = buffer.toString().match(/Sec-WebSocket-Key: (.+)/)[1];
const accept = crypto.createHash('sha1').update(key + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11').digest('base64');
socket.write(`HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: ${accept}\r\n\r\n`);
buffer = Buffer.alloc(0);
return;
}
// 数据处理
if (buffer.length > 2) {
const payloadLen = buffer[1] & 0x7F;
let offset = 2;
if (payloadLen === 126) offset += 2;
if (payloadLen === 127) offset += 8;
const masks = buffer.slice(offset, offset + 4);
offset += 4;
const payload = buffer.slice(offset, offset + payloadLen);
const decoded = payload.map((byte, i) => byte ^ masks[i % 4]);
console.log('Received:', decoded.toString());
buffer = buffer.slice(offset + payloadLen);
}
});
});
server.listen(8080, () => console.log('Node server running on port 8080'));
4. C++ 实现(Linux 环境)
#include <iostream>
#include <sys/socket.h>
#include <netinet/in.h>
#include <openssl/sha.h>
#include <cstring>
#include <vector>
std::string base64_encode(const std::string &in) {
static const std::string base64_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
std::string out;
int val = 0, valb = -6;
for (unsigned char c : in) {
val = (val << 8) + c;
valb += 8;
while (valb >= 0) {
out.push_back(base64_chars[(val>>valb)&0x3F]);
valb -= 6;
}
}
if (valb>-6) out.push_back(base64_chars[((val<<8)>>(valb+8))&0x3F]);
while (out.size()%4) out.push_back('=');
return out;
}
void ws_handshake(int client_sock) {
char buffer[1024];
recv(client_sock, buffer, 1024, 0);
std::string data(buffer);
size_t key_pos = data.find("Sec-WebSocket-Key: ") + 19;
std::string key = data.substr(key_pos, data.find("\r\n", key_pos) - key_pos);
key += "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
unsigned char hash[SHA_DIGEST_LENGTH];
SHA1(reinterpret_cast<const unsigned char*>(key.c_str()), key.size(), hash);
std::string accept = base64_encode(std::string(reinterpret_cast<char*>(hash), SHA_DIGEST_LENGTH));
std::string response = "HTTP/1.1 101 Switching Protocols\r\n"
"Upgrade: websocket\r\n"
"Connection: Upgrade\r\n"
"Sec-WebSocket-Accept: " + accept + "\r\n\r\n";
send(client_sock, response.c_str(), response.size(), 0);
}
int main() {
int server_fd = socket(AF_INET, SOCK_STREAM, 0);
sockaddr_in address{AF_INET, htons(8080), INADDR_ANY};
bind(server_fd, (struct sockaddr*)&address, sizeof(address));
listen(server_fd, 3);
int client_sock = accept(server_fd, nullptr, nullptr);
ws_handshake(client_sock);
while (true) {
char buffer[1024];
int len = recv(client_sock, buffer, 1024, 0);
if (len < 2) break;
int payload_len = buffer[1] & 0x7F;
int mask_offset = 2 + (payload_len == 126 ? 2 : payload_len == 127 ? 8 : 0);
char masks[4];
memcpy(masks, buffer + mask_offset, 4);
std::string decoded;
for (int i = mask_offset + 4; i < len; ++i)
decoded.push_back(buffer[i] ^ masks[(i - mask_offset - 4) % 4]);
std::cout << "Received: " << decoded << std::endl;
}
close(server_fd);
return 0;
}
5. 关键知识点
- 握手验证:所有实现都需要计算: $$ \text{accept} = \text{Base64}(\text{SHA1}(\text{key} + \text{magic_string})) $$
- 数据帧解析:
- 第1字节:FIN(最高位) + Opcode(低4位)
- 第2字节:MASK(最高位) + Payload长度(低7位)
- 掩码处理:$\text{decoded}[i] = \text{payload}[i] \oplus \text{mask}[i \mod 4]$
- 跨语言兼容:
- Python/Node.js 适合快速原型
- C++ 提供高性能底层控制
- 端口统一使用 8080
注意:以上为教学示例,实际生产需添加:
- 错误处理
- 分帧支持
- 心跳机制
- TLS 加密
- 多线程/异步IO
更多推荐
所有评论(0)