进阶篇:WebSocket

WebSocket 是一套应用层双向通信协议,2008 年诞生,2011 年正式成为国际标准,现代浏览器全部原生支持。

核心特性:服务端可以主动向客户端推送消息,客户端也可以主动发消息,真正实现全双工通信,是服务端推送技术的主流方案。

主要特点:

  • 底层基于 TCP,服务端实现简单;
  • 握手请求为标准 HTTP,默认端口 80、443,可顺利穿过 HTTP 代理、防火墙,兼容性好;
  • 协议数据帧格式轻量,报文开销小,通信效率高;
  • 支持传输文本数据、二进制数据(ArrayBuffer、Blob);
  • 无同源策略限制,浏览器客户端可以对接任意后端服务;
  • 协议标识:ws://(明文)、wss://(加密)。

注意:Ajax 适用于一次性请求-响应场景,WebSocket 更适合长连接实时双向通信场景,二者不能互相完全替代。

工作原理

WebSocket 不是脱离 TCP 的全新协议,借助 HTTP 完成握手,握手成功之后彻底脱离 HTTP,使用自定义二进制帧传输业务数据。

WebSocket 连接必须由客户端发起

客户端握手请求

客户端发送标准 HTTP GET 请求,携带升级相关请求头,申请把 HTTP 连接升级为 WebSocket 协议。

GET ws://localhost:3000/ws/chat HTTP/1.1
Host: localhost
Upgrade: websocket
Connection: Upgrade
Origin: http://localhost:3000
Sec-WebSocket-Key: xxxxxxxx-client-random
Sec-WebSocket-Version: 13

请求头释义:

  • Connection: Upgrade:告知服务器,本次请求要做协议升级。
  • Upgrade: websocket:目标升级协议为 WebSocket。
  • Sec-WebSocket-Version: 13:客户端协议版本,主流固定为 13;服务端不支持则返回服务端支持的版本列表。
  • Sec-WebSocket-Key:客户端随机 Base64 字符串,用于简单校验防护,防止误连接、恶意请求。

服务端握手响应

服务端校验通过后,返回 101 Switching Protocols 状态码,完成协议切换。

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: xxxxxxxx-server-hash

响应头释义:

  • 101 Switching Protocols:HTTP 协议切换,握手完成。
  • Sec-WebSocket-Accept:计算规则:将客户端 Sec-WebSocket-Key 拼接固定魔数 258EAFA5-E914-47DA-95CA-C5AB0DC85B11,做 SHA-1 哈希,再转为 Base64 字符串。客户端收到后做校验,确认服务端合法。

握手完成之后,TCP 连接保留,不再使用 HTTP 协议,全部通信走 WebSocket 自定义帧格式,双方自由收发数据。

wss:// 加密连接流程:先建立 TLS 加密通道(和 HTTPS 一致),再在加密通道之上完成 WebSocket 握手,底层由 SSL/TLS 保障传输安全。

备注:TCP 本身原生支持全双工;HTTP 受限于「一问一答」的请求-响应模型,无法做到服务端主动推送,WebSocket 就是为了解决这个限制。

浏览器客户端 API

浏览器原生 WebSocket 对象,核心能力:建立、关闭连接,收发数据,处理异常。

const ws = new WebSocket('ws://localhost:8080/ws');

实例化即立刻发起握手连接。

实例属性(只读)

  • readyState:连接状态
    • 0 CONNECTING:正在建立连接
    • 1 OPEN:连接已打开,可以通信
    • 2 CLOSING:正在关闭
    • 3 CLOSED:已关闭或连接失败
  • binaryType:二进制数据类型,blobarraybuffer
  • bufferedAmount:待发送队列的字节数,可以判断发送缓冲区积压
  • url:实例对应的完整地址
  • extensionsprotocol:协商得到的扩展与子协议

事件

  • open:握手成功,连接打开
  • message:收到服务端消息
  • close:连接关闭
  • error:发生错误
const ws = new WebSocket('ws://localhost:8080/ws');

ws.onopen = (e) => {
    console.log('连接已打开');
    ws.send('hello server');
};
ws.onmessage = (e) => {
    console.log('收到服务端消息:', e.data);
};
ws.onclose = (e) => {
    // e.code 关闭状态码,1000 为正常关闭
    console.log('连接关闭', e.code, e.reason);
};

ws.onerror = (err) => {
    console.error('连接异常', err);
};

// addEventListener 写法
ws.addEventListener('message', e => {});

实例方法

  • send(data):发送数据,支持字符串、Blob、ArrayBuffer、ArrayBufferView。
  • close(code?, reason?):主动关闭连接,可以携带关闭码与关闭原因。
ws.send('文本消息');

const buf = new Uint8Array([0x01, 0x02]).buffer;
ws.send(buf);

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

服务端实现

WebSocket 服务端各个语言均有成熟库。

Node.js 生态常用:

  • ws:轻量高性能标准实现,最常用。
  • uWebSockets.js:性能极高,底层 C++ 实现。
  • Socket.IO不是标准 WebSocket,是上层封装库,内置降级(轮询、长轮询兜底)、断线重连、心跳、房间广播等能力;自定义协议,原生 WebSocket 客户端无法直连
const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 8080 });

wss.on('connection', (clientWs) => {
    console.log('有客户端接入');

    clientWs.on('message', (data) => {
        console.log('收到客户端:', data.toString());
        clientWs.send('服务端回执:' + data);
    });

    const timer = setInterval(() => {
        clientWs.send('服务端时间:' + Date.now());
    }, 1000);

    clientWs.on('close', () => {
        clearInterval(timer);
        console.log('客户端断开');
    });
});

常见问题

HTTP 长连接和 WebSocket 的区别

HTTP/1.1 默认开启 Connection: keep-alive,即 HTTP 长连接。

  • HTTP 长连接:仍然严格遵守一问一答。一个 Request 对应一个 Response;服务端不能主动推送,只能客户端发起请求。TCP 连接可以复用多次 HTTP 请求。
  • WebSocket:握手完成之后脱离 HTTP,双向平等收发,服务端可随时主动下发消息。

本质区别:HTTP keep-alive 只是复用 TCP 连接,应用层依旧是 HTTP 请求-响应模型;WebSocket 是更换了应用层协议。

WebSocket 之前的实时通信备选方案

在 WebSocket 出现之前,要实现服务端推送,只能基于 HTTP 模拟:

  • 短轮询:客户端定时器不断发起 HTTP 请求;没有新数据也返回。缺点:请求量大、实时性差、浪费流量与服务器资源。
  • 长轮询:客户端发起请求,服务端阻塞挂起连接;有消息才返回响应;客户端收到后立刻再次发起请求。
  • SSE(Server-Sent Events)单向通信,仅服务端向客户端推送,客户端不能发送消息。原生基于 HTTP,无需协议升级。适合日志、数据流等单向场景,不适合聊天这类双向交互业务。

SSE 与 EventSource

SSE(Server-Sent Events)是通信规范;EventSource 是浏览器实现该规范的客户端 API。

  • SSE(Server-Sent Events):基于 HTTP 的服务端单向推送协议。定义响应头、消息帧格式、重连策略;连接始终为普通 HTTP 长流,不做协议升级。
  • EventSource:浏览器原生 JS 对象,是 SSE 的客户端实现,封装 HTTP 请求、解析 SSE 数据流、内置自动重连。

关键特性

  • 通信方向:单向,仅服务端向客户端推送,客户端无法发送数据
  • 请求方式:仅支持 GET 请求。
  • 重连:EventSource 内置断开自动重连,服务端可通过 retry: 控制重连间隔。
  • 同源:受同源策略限制,跨域需要服务端配置 CORS。
  • 数据:仅文本,不支持二进制。

和 WebSocket 的核心差异:

  • SSE:单向、普通 HTTP 流、自带重连、仅 GET、文本;适合日志、进度广播等单向场景。
  • WebSocket:双向全双工、HTTP 握手后切换自定义协议、无内置重连、支持二进制;适合聊天、双向实时交互。
const es = new EventSource('/sse/stream');
es.onopen = () => {
    console.log('连接建立');
};
es.onmessage = (e) => {
    console.log('收到服务端推送', e.data);
};
es.onerror = (err) => {
    console.error('出错/断开');
};

es.close();

WebSocket 生产环境注意事项

  • ws:// 明文协议在 HTTPS 页面中会被浏览器拦截,生产环境优先使用 wss://
  • WebSocket 连接容易因网络抖动、代理超时、设备休眠被断开,业务层必须实现心跳检测、断线自动重连
  • Nginx、反向代理需要配置 WebSocket Upgrade 相关请求头,否则握手失败、连接被切断。
  • bufferedAmount 可以判断发送队列堆积,避免短时间大量发送二进制消息造成内存压力。

参考资料

MDN WebSocket

MDN EventSource

© lizhao all right reserved,powered by Gitbook文件修订时间: 2026-08-23 00:12:33

results matching ""

    No results matching ""