WebSocket 服务端调试实录:从连接超时到稳定传输
约 1 分钟· 665 字 75 次阅读
最近维护一套 LAN 聊天系统,Web 前端 + Python WebSocket 服务端。上线前测试时反复出现连接超时和消息丢失,排查过程挺有代表性,记录如下。
症状
- 局域网内部分客户端连接失败:
WebSocket connection to 'ws://192.168.x.x:8765' failed - 连接成功后偶发消息不送达,服务端日志却显示已发送
- 服务端 CPU 占用异常高,日志里大量重连
排查一:端口没监听在预期地址
用 ss 看监听状态:
ss -ltnp | grep 8765
# 结果是 0.0.0.0:8765 —— 正常
问题出在客户端连的是 192.168.x.x,而有些客户端走了 VPN 网关,路由到了别的网卡。修法:服务端绑定所有网卡没问题,但客户端要确认目标 IP 是本机局域网地址,排除多网卡干扰。
排查二:心跳缺失导致连接假死
这是最隐蔽的一个。客户端连着但长时间不通信,NAT 或路由器会静默断开 TCP 连接,而两端都不知道——表现为"看起来还连着,其实消息早丢光了"。
服务端要主动发心跳:
import asyncio
async def heartbeat(ws, interval=30):
while True:
await asyncio.sleep(interval)
try:
await ws.send('{"type":"ping"}')
except Exception:
break # 连接已死,让上层处理
配合客户端的 pong 检测,才能及时发现死链。
排查三:消息顺序与并发写
发现高并发下消息偶尔丢失,根源是多个协程同时往同一个 WebSocket 写,底层连接被竞争。
修法:每个连接一个发送队列,写操作串行化:
class Connection:
def __init__(self, ws):
self.ws = ws
self._send_lock = asyncio.Lock()
async def send(self, data):
async with self._send_lock:
await self.ws.send(data)
排查四:内存与文件描述符
服务端长期运行后文件描述符耗尽(Too many open files),导致新连接全部失败。检查:
ulimit -n # 当前限制
cat /proc/sys/fs/file-nr # 系统已用/可用
生产环境把 ulimit -n 调到 65535,并确保连接关闭后 await ws.close() 被调用、资源及时释放。
小结
这轮调试总结了四条经验:
- 确认网络拓扑,别被多网卡/VPN 误导;
- WebSocket 必须有心跳机制,否则"假死连接"会积累;
- 并发写同一个连接要加锁或用队列串行化;
- 长连接服务要关注文件描述符上限和连接清理。
调试网络问题最难的不是技术,而是"现象不可复现"。把这些细节都补上后,系统在局域网里跑了一整天零断连。