WebSocket 协议原理

WebSocket 握手过程、通信机制及与 HTTP 的区别

问题

解释 WebSocket 协议的工作原理、握手过程,以及它与 HTTP 协议的区别。

解答

WebSocket 是什么

WebSocket 是一种在单个 TCP 连接上进行全双工通信的协议。它允许服务器主动向客户端推送数据,解决了 HTTP 协议只能由客户端发起请求的限制。

握手过程

WebSocket 连接通过 HTTP 升级机制建立:

1. 客户端发起握手请求

GET /chat HTTP/1.1
Host: example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13

2. 服务器响应握手

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=

Sec-WebSocket-Accept 的计算方式:

const crypto = require('crypto');

function generateAcceptKey(clientKey) {
  // 固定的 GUID,由 RFC 6455 规定
  const GUID = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11';
  return crypto
    .createHash('sha1')
    .update(clientKey + GUID)
    .digest('base64');
}

// 示例
const clientKey = 'dGhlIHNhbXBsZSBub25jZQ==';
console.log(generateAcceptKey(clientKey)); // s3pPLMBiTxaQ9kYGzzhZRbK+xOo=

客户端使用示例

// 创建 WebSocket 连接
const ws = new WebSocket('ws://example.com/socket');

// 连接建立
ws.onopen = function() {
  console.log('连接已建立');
  ws.send('Hello Server!');
};

// 接收消息
ws.onmessage = function(event) {
  console.log('收到消息:', event.data);
};

// 连接关闭
ws.onclose = function(event) {
  console.log('连接关闭:', event.code, event.reason);
};

// 错误处理
ws.onerror = function(error) {
  console.error('WebSocket 错误:', error);
};

// 发送数据
ws.send('文本消息');
ws.send(new Blob(['二进制数据']));
ws.send(new ArrayBuffer(8));

// 关闭连接
ws.close(1000, '正常关闭');

服务端示例(Node.js)

const WebSocket = require('ws');

const wss = new WebSocket.Server({ port: 8080 });

wss.on('connection', function(ws) {
  console.log('客户端已连接');

  // 接收消息
  ws.on('message', function(message) {
    console.log('收到:', message.toString());
    
    // 向客户端发送消息
    ws.send('服务器收到: ' + message);
  });

  // 主动推送消息
  setInterval(() => {
    if (ws.readyState === WebSocket.OPEN) {
      ws.send('服务器推送: ' + new Date().toISOString());
    }
  }, 5000);

  ws.on('close', function() {
    console.log('客户端断开');
  });
});

WebSocket 与 HTTP 的区别

特性HTTPWebSocket
通信方式单向(客户端发起)双向(全双工)
连接状态无状态,每次请求新建连接持久连接
数据格式文本文本或二进制
头部开销每次请求携带完整头部握手后头部开销小(2-10 字节)
实时性需要轮询或长轮询原生支持实时通信
协议标识http:// 或 https://ws:// 或 wss://

数据帧格式

 0                   1                   2                   3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-------+-+-------------+-------------------------------+
|F|R|R|R| opcode|M| Payload len |    Extended payload length    |
|I|S|S|S|  (4)  |A|     (7)     |             (16/64)           |
|N|V|V|V|       |S|             |   (if payload len==126/127)   |
| |1|2|3|       |K|             |                               |
+-+-+-+-+-------+-+-------------+-------------------------------+
|     Extended payload length continued, if payload len == 127  |
+-------------------------------+-------------------------------+
|                               |Masking-key, if MASK set to 1  |
+-------------------------------+-------------------------------+
| Masking-key (continued)       |          Payload Data         |
+-------------------------------+-------------------------------+
|                     Payload Data continued ...                |
+---------------------------------------------------------------+

常见 opcode 值:

  • 0x0: 继续帧
  • 0x1: 文本帧
  • 0x2: 二进制帧
  • 0x8: 关闭连接
  • 0x9: Ping
  • 0xA: Pong

关键点

  • 握手基于 HTTP:通过 Upgrade: websocket 头将 HTTP 连接升级为 WebSocket
  • 全双工通信:连接建立后,客户端和服务器可以随时互相发送数据
  • 持久连接:一次握手后保持连接,避免重复建立连接的开销
  • 低延迟:数据帧头部仅 2-10 字节,适合实时应用
  • 心跳机制:通过 Ping/Pong 帧保持连接活跃,检测连接状态