从零实现 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. 关键知识点
  1. 握手验证:所有实现都需要计算: $$ \text{accept} = \text{Base64}(\text{SHA1}(\text{key} + \text{magic_string})) $$
  2. 数据帧解析
    • 第1字节:FIN(最高位) + Opcode(低4位)
    • 第2字节:MASK(最高位) + Payload长度(低7位)
    • 掩码处理:$\text{decoded}[i] = \text{payload}[i] \oplus \text{mask}[i \mod 4]$
  3. 跨语言兼容
    • Python/Node.js 适合快速原型
    • C++ 提供高性能底层控制
    • 端口统一使用 8080

注意:以上为教学示例,实际生产需添加:

  • 错误处理
  • 分帧支持
  • 心跳机制
  • TLS 加密
  • 多线程/异步IO
Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐