根本原因是Swoole Server启动后缺少阻塞逻辑,$server->start()返回即退出;需确保无exit/return、用nohup或systemd守护,并手动实现TCP长连接保活与客户端fd映射管理。

为什么 swoole_server 启动后立刻退出?
根本原因不是代码写错了,而是没加 start() 之后的阻塞逻辑 —— Swoole 的 Server 默认是异步事件循环,start() 调用完就返回,主进程若无其他阻塞操作(比如 sleep 或等待信号),直接结束,整个服务就没了。
实操建议:
立即学习“PHP免费学习笔记(深入)”;
- 确保调用
$server->start()后不再有exit、return或脚本自然结束 - 不要在 CLI 脚本末尾写
echo "done";就完事,那会导致进程立即退出 - 开发时可加
echo "Server started, PID: " . posix_getpid() . "\n";然后用ps aux | grep your_script.php确认进程是否存活 - 线上务必用
nohup php server.php &或systemd管理,否则终端关闭即失联
怎么让客户端连上就保持长连接不中断?
TCP 层本身没有“心跳”概念,连接空闲时可能被中间防火墙、NAT 设备或云厂商 SLB 主动断开。Swoole 不会自动帮你保活,得自己动手。
实操建议:
立即学习“PHP免费学习笔记(深入)”;
- 在
onReceive回调里记录每个$fd的最后通信时间,用tick定时器每 30 秒扫一遍,对超时(比如 60 秒无数据)的$fd主动close - 更稳妥的做法:客户端定时发
PING,服务端收到后回PONG,双方都重置超时计时器 - 别依赖
setsockopt($sock, SOL_SOCKET, SO_KEEPALIVE, 1)—— PHP 的 stream 或 Swoole 底层默认已启用,但 keepalive 参数(如tcp_keepidle)系统级默认是 2 小时,远大于你实际需要的保活间隔 - 注意:Swoole 的
heartbeat_idle_time和heartbeat_check_interval只对 WebSocket Server 生效,TCP Server 需手动实现
swoole_server 的 onConnect 里能直接 send 吗?
可以,但有前提:必须等 TCP 三次握手完成、内核协议栈确认连接建立后才能发数据。Swoole 的 onConnect 回调正是在这个时机触发的,所以此时 send 是安全的。
实操建议:
立即学习“PHP免费学习笔记(深入)”;
- 适合发欢迎语、协议头、认证挑战(如随机 token)等初始化消息
- 避免在
onConnect里做耗时操作(如查 DB、调远程 API),会阻塞整个事件循环;真要查,得用协程go+Co::MySQL或异步客户端 - 如果客户端还没准备好接收(比如启动慢),你发太快可能被对方内核缓冲区丢弃,建议搭配简单握手机制:你发
HELLO→ 客户端回READY→ 你再进业务逻辑 - 注意:
onConnect没有$data参数,只传$server和$fd;收数据得等onReceive
推送消息时怎么找到指定客户端的 $fd?
Swoole 的 $fd 是连接生命周期内的唯一标识,但它不跨进程、不持久化,重启服务就全失效。所以不能靠它做长期用户绑定。
实操建议:
立即学习“PHP免费学习笔记(深入)”;
- 连接建立后,立刻让客户端发送身份信息(如
{"type":"auth","uid":12345}),服务端解析后存到$_SESSION或 Redis:$redis->hSet("fd_map", $uid, $fd) - 推送时先查
$fd = $redis->hGet("fd_map", $uid),再判断$server->exist($fd)是否还活着,避免向已断连的 fd 发送导致 warning - 断连时务必在
onClose里清理映射:$redis->hDel("fd_map", $uid),否则会积累脏数据 - 别用全局数组存
fd → uid映射 —— Swoole 多进程模式下,每个 worker 进程内存隔离,数组无法共享
真正难的不是发一次消息,而是维持住“谁在哪条连接上”这个映射关系,并在各种异常(断网、闪退、服务重启)后还能收敛。这部分逻辑漏掉一环,推送就变成概率事件。











