WebSocket自动重连需客户端实现指数退避重连,服务端配合连接去重、心跳保活及Redis映射用户ID与fd;推送前须校验fd有效性,批量推送应分片+协程让渡或投递task进程处理。

WebSocket连接断开后怎么自动重连
客户端主动断开或网络抖动导致 onClose 触发,但服务端不会自动重连——这是常见误解。Swoole 的 WebSocket 服务端本身不管理重连逻辑,必须由客户端控制。
实际做法是:前端监听 onclose,延迟几秒后调用 new WebSocket(url);服务端需配合做连接去重和心跳保活。
- 不要在服务端写“重连代码”,那是客户端的事
- 客户端首次连接失败时,建议指数退避(如 1s → 2s → 4s),避免雪崩式重连
- 服务端若未正确处理重复
onOpen(比如用户快速刷新页面),可能导致fd冲突或 session 错乱 - 务必在
onOpen中校验 token 或 session,否则重连可能绕过鉴权
如何安全地给指定用户推送消息(不是广播)
直接用 $server->push($fd, $data) 是最常用方式,但前提是得知道目标用户的 fd。问题在于:fd 是连接级标识,每次重连都会变,不能当用户 ID 用。
典型错误是把 fd 存数据库或当长期凭证传给前端——这会导致推送失效或信息错发。
- 登录成功后,服务端应将
fd映射到业务用户 ID(如user_id_123),存在 Redis 哈希表里:HSET user_fd_map 123 17 - 用户登出或
onClose时,必须立刻HDEL user_fd_map 123,否则会推送给已断开的连接 - 推送前先
HGET user_fd_map 123查fd,再用$server->exist($fd)确认连接还活着,避免push报invalid fd - 别用数组存
fd映射,高并发下 PHP 数组不是线程安全的,Redis 才可靠
心跳超时设置不对,连接被悄悄断开
Swoole 默认不启用心跳检测,onMessage 不触发不代表连接还活着。很多线上问题表现为“用户突然收不到消息”,其实是 TCP 连接被中间设备(如 Nginx、防火墙)静默关闭了。
根本解法是开启 Swoole 层心跳,并合理设置参数:
- 在
Server配置中加:'heartbeat_idle_time' => 600(单位秒,表示 10 分钟没数据就踢掉) - 同时设
'heartbeat_check_interval' => 60(每 60 秒扫一次,值太小增加 CPU 负担) - 前端必须实现 ping/pong 心跳,且间隔要小于
heartbeat_idle_time,建议设为 30s - 别依赖浏览器
WebSocket自带的底层心跳——它不可控,也收不到通知 - 如果用了 Nginx 反向代理,还得配
proxy_read_timeout 600,否则 Nginx 先断连
并发推送大量消息时卡顿甚至崩溃
直接在一个 foreach 里循环调用 $server->push() 推送几千人,大概率触发协程调度阻塞或内存暴涨。Swoole 的 push 是同步写 socket,不是发完就完。
本质是 I/O 密集型操作没做节流,容易拖垮整个 EventLoop。
- 单次推送不要超过 50 个
fd,用array_chunk拆分批次,每批后co::sleep(0.001)让出协程 - 避免在
onMessage或onReceive中做大批量推送,应投递到 task 进程处理:$server->task($data) - task 进程里推送时,仍要检查
$server->exist($fd),因为fd可能在 task 执行期间已断开 - 如果消息体很大(如 >64KB),考虑压缩或分片,否则可能触发
send buffer full错误
真正难的不是怎么推,是怎么推得稳——连接状态瞬息万变,所有 fd 相关操作都得带存在性校验,漏一次就可能引发连锁异常。










