server->heartbeat() 是服务端手动触发的一次性连接空闲检测,返回超时连接的 $fd 列表,不自动关闭、不触发 onclose;它不能替代 heartbeat_check_interval 的周期性自动清理,仅适用于同步模式 swoole\server。

Server->heartbeat() 是手动触发心跳检测,不是发心跳包
很多人看到 Server->heartbeat() 就以为是“让服务端主动给客户端发 ping”,其实完全相反:它只是让服务端立刻扫描一遍所有连接,把空闲超时的(即 last_time + heartbeat_idle_time )全部标记为待关闭,并返回这些 <code>$fd 列表。它不发送任何数据,也不触发 onClose —— 关闭动作仍需你手动调用 $server->close($fd)。
常见错误现象:
- 调用了
$server->heartbeat()却没看到连接断开 → 忘了后续close() - 误以为它能替代
heartbeat_check_interval定时器 → 实际上它只是“快照式检查”,不能取代周期性自动清理 - 在协程 Server 中调用 →
Server->heartbeat()仅适用于同步模式的Swoole\Server,协程 Server(如Swoole\Http\Server启用enable_coroutine)不支持该方法
和自动心跳配置(heartbeat_idle_time/heartbeat_check_interval)的关系
heartbeat_idle_time 和 heartbeat_check_interval 是服务端内置的后台线程机制:每 heartbeat_check_interval 秒自动遍历一次连接,对空闲超时的直接 close 并触发 onClose。而 Server->heartbeat() 是你在任意时刻主动调用的一次性检查,返回值是超时连接的 $fd 数组,不自动关、不触发回调。
使用场景:
- 需要在特定时机(比如运维指令、内存告警后)紧急清理疑似僵尸连接
- 想在关闭前先发个通知(例如广播“即将下线”),再调用
close() - 做灰度检测:只查不关,统计当前有多少连接已空闲超时但尚未被自动机制捕获
参数差异:
-
Server->heartbeat()无参数,返回array(超时$fd列表) - 自动心跳靠
set(['heartbeat_idle_time' => 60, 'heartbeat_check_interval' => 30])驱动,底层启动独立线程
为什么不该依赖 Server->heartbeat() 做常规保活
它解决的是“服务端清理死连接”的问题,不是“维持活连接”的问题。客户端连接断开的根本原因,往往不是服务端没扫,而是客户端根本没发数据——服务端等不到心跳,自然判定超时。这时候你手动调用 Server->heartbeat() 只会让超时连接更快被发现,但无法阻止它们超时。
Swoole 6.1.1 是一个专为 PHP 设计的高性能事件驱动并发网络引擎。作为稳定版,它修复了编译时对 zlib 依赖的缺失及 curl 模块的内存安全风险。该版本支持协程、多线程与多进程架构,内置 TCP/HTTP/WebSocket 服务器,能够显著提升 PHP 在微服务、实时通信等场景下的执行效率与并发能力。
真正要保活,必须由客户端主动发符合协议的数据(哪怕只是 "\x00" 或带长度头的包),且频率 严格小于 heartbeat_idle_time(建议 ≤ heartbeat_idle_time * 0.75)。
容易踩的坑:
- 在客户端写了
tick(60000, fn() => $client->send("ping")),但服务端heartbeat_idle_time设成了 60 → 刚好卡在边界,网络抖动就丢包,连接被秒杀 - 服务端开了
heartbeat_check_interval => 5,但忘了设heartbeat_idle_time→ 底层不启动心跳线程,Server->heartbeat()返回永远为空 - 用协程客户端(
Co\Socket)连服务端,却指望服务端Server->heartbeat()能“唤醒”它 → 协程客户端不发数据,服务端只能等,等不到就关
Client 端没有对应的 heartbeat() 方法
Swoole 客户端(包括 Co\Socket、Co\Http\Client、swoole_client)都没有类似 Server->heartbeat() 的手动检测接口。客户端保活唯一可靠方式就是自己定时 send(),且内容和服务端解析逻辑一致(比如服务端启用了 open_length_check,你就不能只发 "ping",得发 \x00\x00\x00\x04ping 这类带包头的格式)。
性能影响很小,但兼容性极关键:如果服务端用 onReceive 直接 substr($data, 4) 解包,客户端发纯字符串就会被截断或丢弃,看似发了,实则无效。
复杂点在于:心跳包格式、发送时机、重连兜底这三者必须耦合设计。比如 tick 发送后,得立刻 recv() 看是否还通,失败就 close() + 重连,而不是等下一次 tick 才发现连不上。










