swoole服务端心跳需同时配置heartbeat_idle_time(超时阈值)和heartbeat_check_interval(扫描周期),后者为正整数且建议≤前者1/2,否则不启动检测线程;仅设前者无效。

服务端心跳靠配置,客户端心跳必须手写;设了 heartbeat_idle_time 却没断连?大概率是漏配 heartbeat_check_interval。
为什么只设 heartbeat_idle_time 没用
这个参数本身不触发任何检查动作——它只是个“超时阈值”。Swoole 内部需要一个独立的定时扫描线程来比对每个连接的最后通信时间戳和当前时间差,而该线程的启动条件就是 heartbeat_check_interval 被显式设置。
- 若只写
'heartbeat_idle_time' => 60,底层根本不会创建心跳检测线程,连接永远不被自动清理 -
heartbeat_check_interval必须是正整数(单位:秒),建议 ≤heartbeat_idle_time的 1/2,比如设为30配合60 - 检查不是实时的:即使某个连接刚空闲满 60 秒,也要等到下一次扫描周期(如第 30、60、90 秒)才被发现并关闭
onClose 触发但客户端没调 close()?那是心跳踢的
很多开发者看到 onClose 回调被触发,第一反应是“客户端崩了”或“网络断了”,其实更大概率是服务端主动踢掉的空闲连接。这时 $fd 对应的连接在服务端已销毁,但客户端可能还懵然不知。
Swoole 6.1.1 是一个专为 PHP 设计的高性能事件驱动并发网络引擎。作为稳定版,它修复了编译时对 zlib 依赖的缺失及 curl 模块的内存安全风险。该版本支持协程、多线程与多进程架构,内置 TCP/HTTP/WebSocket 服务器,能够显著提升 PHP 在微服务、实时通信等场景下的执行效率与并发能力。
- 触发
onClose时,$reactor_id参数为 -1,且$server->connection_info($fd)返回false,可据此区分是主动断开还是心跳清理 - 不要在
onClose里再调$server->close($fd)—— 此时连接已不存在,会报Invalid fd - 如果业务需记录“被动下线”,应在
onClose中查$server->getClientInfo($fd)获取 IP 和最后活跃时间
协程客户端不能用 set(['heartbeat' => x])
文档没明说,但实测 Co\Socket 和 Co\Http\Client 完全忽略 heartbeat 配置项。所谓“自动心跳”只存在于同步 swoole_client 中,且发的是单字节 \x00,兼容性极差。
- 协程客户端必须自己用
tick+send()实现心跳,例如:$client->tick(45000, fn() => $client->send("ping")) - 心跳内容必须和服务端协议对齐:若服务端启用了
open_length_check,发纯字符串"ping"会被丢弃,得构造带包头的二进制帧,如\x00\x00\x00\x04ping -
send()返回false不代表失败,可能是缓冲区满或连接已断,务必加if (!$client->isConnected())判断再重连
心跳包收不到?先看这三处
客户端发了心跳,服务端 onReceive 却没收到,问题通常不在网络,而在协议层或时机。
- 服务端是否开了
open_length_check?开了就必须按长度头解析,发"ping"这种无头数据直接进黑洞 - 客户端是否在
onConnect后立刻tick?若连接尚未完全建立(比如 TLS 握手未完成),send()可能静默失败 - 服务端
onReceive回调里有没有做return或提前exit?一旦中断执行,后续数据包可能被丢弃,心跳就“石沉大海”
真正容易被忽略的是:服务端心跳检测只看“是否有数据到达”,不管内容是什么;但应用层心跳必须双方约定格式、响应逻辑和超时重试,否则单边发包等于白忙。










