网络与协议 · 71/72
1. Ajax、Axios、Fetch 对比 2. Ajax 原理 3. Ajax 技术与实现 4. 常见的应用层协议 5. 浏览器缓存的存储位置 6. 从输入 URL 到页面显示的过程 7. Cache-Control 常见配置值 8. CDN 工作原理 9. 为什么推荐将静态资源放到 CDN 上 10. Cookie 的弊端 11. Cookie 的 Secure 属性设置 12. CORS 请求携带身份凭证的方法 13. CORS 跨域原理 14. 复杂请求预检检查内容 15. CORS 预检请求 16. CORS简单请求的条件 17. 简单请求为何无需预检 18. DNS 域名解析与网络请求路由 19. 什么是跨域 20. 什么是 DNS 劫持? 21. DNS 预解析优化网页加载速度 22. DNS 解析过程与优化 23. URL 参数为什么需要 encodeURIComponent 转码 24. Last-Modified 和 ETag 的区别 25. Fetch 发送两次请求的原因 26. 正向代理与反向代理 27. 前后端通信方式 28. GET请求能否上传图片 29. GET 请求的传参长度限制 30. HTTP 缓存策略 31. GET 与 POST 的区别 32. HTTP状态码301与302的区别 33. HTTP 数据传输 34. HTTP 队头阻塞 35. HTTP 请求头和响应头的重要字段 36. HTTP发展历程 37. HTTP与HTTPS总结 38. HTTP 和 HTTPS 的区别 39. HTTP 报文结构与状态码 40. HTTP Keep-Alive 机制 41. HTTP管道机制的作用 42. HTTP协议优缺点 43. HTTP 重定向状态码 301/302/303/307/308 44. HTTP 请求方法 45. HTTP 协议版本演进 46. HTTP与TCP的区别 47. HTTP/2 多路复用原理 48. HTTPS 协议的缺点 49. HTTP/3 如何保证传输可靠性 50. HTTP/2 的改进 51. HTTPS 加密原理 52. 什么是负载均衡? 53. Nginx 负载均衡调度算法 54. Nginx 是什么 55. 对象存储 OSS 是什么 56. OPTIONS 请求方法及使用场景 57. 轮询与 WebSocket 对比 58. HTTPS 中 SSL 的 OSI 层位置 59. SSL连接恢复 60. 强缓存和协商缓存 61. TCP 三次握手与四次挥手 62. TCP三次握手中的数据传输 63. TCP 和 HTTP 请求的关系 64. TCP/IP 协议 65. TCP 如何判断丢包 66. TCP 与 UDP 的区别 67. WebSocket 的 Handshaking 握手过程 68. TLS 1.3 相比 TLS 1.2 的改进 69. URI、URL、URN 的区别 70. WebSocket 心跳机制 71. WebSocket 协议原理 72. XML与JSON对比

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 帧保持连接活跃,检测连接状态