WebSockets 技术指南
本文档适合开发与架构参考使用,包含技术概述、协议细节、实现方案、应用场景、性能优化、兼容性与 RFC 链接。
目录
- 技术概述
- 协议细节
- [握手过程(HTTP Upgrade)](#握手过程(HTTP Upgrade))
- 数据帧格式
- 心跳机制(Ping/Pong)
- 实现方案
- [Linux 客户端(libwebsockets)](#Linux 客户端(libwebsockets))
- [Linux 服务端(libwebsockets/Nginx 反向代理)](#Linux 服务端(libwebsockets/Nginx 反向代理))
- 安全考虑
- 应用场景
- 性能优化
- 兼容性支持
- [RFC 参考链接](#RFC 参考链接)
技术概述
- 定义:WebSockets 是建立在单个 TCP 连接上的全双工通信协议,使客户端与服务器能够低延迟、双向实时传输数据。
- 相对 HTTP 轮询:
- 轮询优点:兼容性好、易部署;缺点:额外请求开销、延迟大、服务端推送困难。
- WebSockets 优点:单连接双向实时、消息头小、延迟低;缺点:需后端长期持久连接管理、对代理与负载均衡有特殊要求。
- 协议前缀:
ws://不加密,适用于受信局域网或非敏感场景。wss://通过 TLS 加密,提供机密性与完整性,适用于公网与敏感数据。
协议细节
握手过程(HTTP Upgrade)
-
客户端通过 HTTP(S) 发起 Upgrade 请求,服务器返回
101 Switching Protocols并升级到 WebSocket。 -
请求示例:
GET /chat HTTP/1.1
Host: example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13
Origin: https://example.com -
响应示例:
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
Sec-WebSocket-Protocol: chat -
关键点:
Sec-WebSocket-Key与服务器计算的Sec-WebSocket-Accept(基于 GUID 拼接后 SHA-1 再 Base64)。- 可选
Sec-WebSocket-Protocol用于子协议协商;Origin头建议服务端验证以防跨站连接。
数据帧格式
- 帧头:
FIN(1bit):是否为消息最后一个片段。RSV1--RSV3(3bit):扩展保留位,默认应为 0;压缩扩展可能使用RSV1。Opcode(4bit):0x1文本、0x2二进制、0x9Ping、0xAPong、0x0继续帧、0x8关闭。Mask(1bit):客户端发送必须置 1,含 32bit mask;服务器发送通常为 0。Payload len:7bits 或扩展 16/64bits。Masking-key:客户端帧包含,用于对负载做异或掩码。
- 负载:文本以 UTF-8 编码;二进制为不透明字节流。消息可由多个片段组成,最终片段
FIN=1。
心跳机制(Ping/Pong)
Ping/Pong是协议级心跳与保活。服务器通常定时发送Ping,客户端收到后自动回复Pong。- 浏览器 API 不直接提供 Ping 帧发送接口,客户端可采用应用层心跳消息作为替代。
实现方案
Linux 客户端(libwebsockets)
c
#include <libwebsockets.h>
static int cb_client(struct lws *wsi, enum lws_callback_reasons reason,
void *user, void *in, size_t len) {
switch (reason) {
case LWS_CALLBACK_CLIENT_ESTABLISHED:
lws_callback_on_writable(wsi);
break;
case LWS_CALLBACK_CLIENT_WRITEABLE: {
unsigned char buf[LWS_PRE + 128];
unsigned char *p = &buf[LWS_PRE];
size_t n = lws_snprintf((char*)p, 128, "hello");
lws_write(wsi, p, n, LWS_WRITE_TEXT);
break;
}
case LWS_CALLBACK_CLIENT_RECEIVE:
// 处理服务器返回数据(in/len)
break;
case LWS_CALLBACK_CLIENT_CONNECTION_ERROR:
// 连接错误处理
break;
default:
break;
}
return 0;
}
static const struct lws_protocols protos[] = {
{ "echo", cb_client, 0, 4096 },
{ NULL, NULL, 0, 0 }
};
int main() {
struct lws_context_creation_info info = {0};
info.port = CONTEXT_PORT_NO_LISTEN; // 客户端不监听
info.protocols = protos;
info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT; // 允许 TLS 初始化
// 指定根 CA(验证 wss 服务端证书),依据系统路径调整
info.client_ssl_ca_filepath = "/etc/ssl/certs/ca-certificates.crt";
struct lws_context *ctx = lws_create_context(&info);
if (!ctx) return 1;
struct lws_client_connect_info cc = {0};
cc.context = ctx;
cc.address = "example.com"; // 服务器地址
cc.port = 443; // wss 端口
cc.path = "/ws"; // 路径
cc.protocol = "echo"; // 子协议
cc.host = cc.address; // SNI/Host
cc.origin = "https://example.com";
cc.ssl_connection = LWS_USE_SSL; // 启用 TLS(wss://)
struct lws *wsi = lws_client_connect_via_info(&cc);
if (!wsi) return 1;
while (lws_service(ctx, 0) >= 0) {}
lws_context_destroy(ctx);
return 0;
}
- 构建示例:
gcc -o lws_client client.c -lwebsockets(按后端可能需要-lssl -lcrypto或 mbedTLS 依赖)。 - 心跳:libwebsockets 按协议自动处理 Ping/Pong;应用可通过定时器实现业务心跳与超时清理。
Linux 服务端(libwebsockets/Nginx 反向代理)
- 基于 libwebsockets 的最小服务器:
c
#include <libwebsockets.h>
static int cb_echo(struct lws *wsi, enum lws_callback_reasons reason,
void *user, void *in, size_t len) {
switch (reason) {
case LWS_CALLBACK_ESTABLISHED:
break;
case LWS_CALLBACK_RECEIVE:
lws_callback_on_writable(wsi);
break;
case LWS_CALLBACK_SERVER_WRITEABLE: {
unsigned char buf[LWS_PRE + 1024];
unsigned char *p = &buf[LWS_PRE];
size_t n = lws_snprintf((char*)p, 1024, "pong");
lws_write(wsi, p, n, LWS_WRITE_TEXT);
break;
}
default:
break;
}
return 0;
}
static const struct lws_protocols protos[] = {
{ "echo", cb_echo, 0, 4096 },
{ NULL, NULL, 0, 0 }
};
int main() {
struct lws_context_creation_info info = {0};
info.port = 9000; // 监听端口(ws://)
info.protocols = protos;
struct lws_context *ctx = lws_create_context(&info);
if (!ctx) return 1;
while (lws_service(ctx, 0) >= 0) {}
lws_context_destroy(ctx);
return 0;
}
- Nginx 反向代理(支持 WebSocket Upgrade 与 wss 终止):
nginx
server {
listen 443 ssl;
server_name example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
location /ws {
proxy_pass http://127.0.0.1:9000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
proxy_set_header Host $host;
}
}
安全考虑
- 强制使用
wss://于公网与敏感数据传输,启用现代 TLS(TLS 1.2/1.3)。 - 验证
Origin头并实施白名单,防止跨站 WebSocket 滥用。 - 认证与授权:在握手阶段使用 Cookie/Token/自定义头;建立后在应用层维持会话态与权限校验。
- 资源配额与限速:限制每连接消息大小与速率,防止 DoS。
- 压缩安全:启用
permessage-deflate时注意潜在 CRIME/BREACH 类泄露风险,避免在同连接内混合敏感与可控输入。
应用场景
- 实时聊天:多人房间、私聊、消息既时达。
- 在线协作:文档/白板/音视频信令等状态同步。
- 金融行情推送:高频低延迟广播与个性化订阅。
性能优化
- 连接复用:在同域与同用户场景,复用单连接承载多通道(子协议或应用层路由),减少连接数量。
- 二进制传输:使用
ArrayBuffer/TypedArray 降低序列化开销;结构化数据建议 Protocol Buffer/FlatBuffers。 - 压缩扩展:启用
permessage-deflate(RFC 7692)在大消息场景降低带宽;结合分片与批处理。 - 回压与批量:实现发送队列与背压处理,聚合多条消息后统一写出,降低系统调用次数。
- 心跳与超时:合理设置服务器 Ping 与连接空闲超时,及时清理断链。
兼容性支持
- Chrome 16+、Firefox 11+、Safari 6+、Edge 12+、现代移动浏览器均支持 RFC 6455 WebSocket。
- 代理与负载均衡需支持 HTTP Upgrade 与长连接(Nginx、HAProxy、Envoy 均可通过配置支持)。
RFC 参考链接
- RFC 6455(The WebSocket Protocol):https://datatracker.ietf.org/doc/html/rfc6455
- RFC 7692(Compression Extensions for WebSocket):https://datatracker.ietf.org/doc/html/rfc7692
- RFC 8446(TLS 1.3,用于 wss):https://datatracker.ietf.org/doc/html/rfc8446
握手流程示意图
Browser Server HTTP GET + Upgrade: websocket 101 Switching Protocols 升级为 WebSocket, 建立双向通道 Frames (Text/Binary) Frames (Text/Binary) Ping Pong Browser Server