握手是怎么断的
WebSocket 的连接始于一次特殊的 HTTP 请求:客户端带 Upgrade: websocket 头请求升级,服务端回 101 Switching Protocols,之后这条 TCP 连接就"改行"了,双方开始双向收发帧。问题在于 Nginx 默认按普通 HTTP 代理处理:HTTP/1.0 协议转发、不透传 Upgrade 头——后端根本收不到"要升级"的信号,连接退化成普通请求。前端的症状很典型:连接能建但很快掉,或反复重连,Network 面板里永远看不到 101。
标准配置
# Upgrade 头是"逐跳"的,代理层默认丢弃,必须手动接力
map $http_upgrade $connection_upgrade {
default upgrade; # 客户端要升级 → Connection: upgrade
"" close; # 普通请求 → Connection: close
}
server {
location /ws/ {
proxy_pass http://ws_backend;
proxy_http_version 1.1; # 必须 1.1,1.0 无升级机制
proxy_set_header Upgrade $http_upgrade; # 透传升级头
proxy_set_header Connection $connection_upgrade;
proxy_read_timeout 3600s; # 见下文
}
}
# map 写在 http 层;这套配置是 WebSocket 代理的标准模板,可直接抄验证方法:DevTools 的 Network 面板过滤 WS,握手成功的标志是状态码 101;命令行用 curl -i --http1.1 -H "Upgrade: websocket" -H "Connection: Upgrade" 探一下,看到 101 才算通。
断连的两类原因
第一类:空闲超时。proxy_read_timeout 默认 60 秒——指"60 秒内没从后端读到任何数据"就断开。WebSocket 建立后若双方一直不说话,60 秒必死,表现就是"每过一分钟掉一次线"。两条路:调大超时(治标,且长超时占用连接资源)或应用层心跳(治本)。第二类:后端重启。发布、扩容、OOM 都会掐断连接,客户端要有重连机制——但重连必须带指数退避加随机抖动(1s、2s、4s……上限 30s),全量客户端同时重连等于自造 DDoS,这正是限流与熔断系列讲过的重试风暴,换个马甲又见面了。
心跳设计
// 客户端每 30 秒发一帧 ping,服务端回 pong
setInterval(() => {
if (ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify({ type: "ping", ts: Date.now() }));
}
}, 30000);
// 为什么选 30 秒:小于默认 60s 超时,留出一倍容错
// 为什么用应用层心跳而不用协议层 ping:
// 能顺带测出"连接通但业务假死",还能捎带业务数据(如未读数)心跳间隔是超时时间的一半左右——偶尔丢一次心跳也能在超时前补上,连接不断。心跳还顺手承担了死链检测:发出去收不到回音,立刻触发重连而不是干等超时。
负载均衡的注意点
长连接 upsteam 下 ip_hash 有新意义:连接建立后流量长期粘在一台后端,轮询的"分摊"效果只在建连那一刻生效。多机部署时广播消息要走 Redis 发布订阅之类的总线(一台收到的消息要广播给连在别的机器上的用户)——这是 WebSocket 集群化的正经门槛,提前设计。出了问题怎么查?下一篇:日志与排错。
评论 (0)