必须使用swoole协程websocket服务器+redis pub/sub方案:通过swoole\websocket\server监听9502端口,配置心跳检测与worker数,用swoole\table绑定fd-uid,redis pub/sub实现跨节点广播,并配合supervisor进程守护与nginx反向代理正确配置。

要让一个PHP后端支持万级并发在线聊天、多端消息实时同步、不丢不重、断线自动恢复,必须绕开传统HTTP轮询和FPM模型——Swoole的协程WebSocket服务器+Redis Pub/Sub是当前最轻量且可控的落地路径。
初始化Swoole WebSocket服务并配置关键参数
新建websocket.php,用Swoole\WebSocket\Server启动监听,端口设为9502,避免与Nginx默认端口冲突。
调用$server->set()启用协程、设置worker数为CPU核心数、开启心跳检测(heartbeat_check_interval=30,heartbeat_idle_time=300),否则空闲连接5分钟后会被内核强制回收,用户无感知掉线。
【必须禁用daemonize】开发阶段保持前台运行,便于实时看到var_dump和错误日志;上线后才交由Supervisor管理进程。
建立用户连接与身份绑定
客户端首次连接成功触发open事件,此时不能直接存fd到Redis——Swoole的fd在不同worker间不通用,跨worker推送会失败。
改用Swoole\Table在本节点内存中维护fd → uid映射:$table = new Swoole\Table(1 column('uid', Swoole\Table::TYPE_STRING, 32); $table->create();
收到客户端发来的{"type":"login","uid":"U1001"}消息后,执行$table->set($frame->fd, ['uid' => $data['uid']]);完成绑定。
这一步不可跳过:若只依赖Redis存uid→fd,当用户多端登录(手机+PC),同一uid对应多个fd,而Swoole\Table仅存本节点数据,配合后续Redis Pub/Sub才能精准广播到所有设备。
实现跨节点消息广播
方法一:Redis Pub/Sub中转
在workerStart回调中,仅让workerId === 0的进程订阅Redis频道im,防止多worker重复消费同一条消息。
客户端发送聊天消息时,构造数组['from'=>'U1001','to'=>'U2002','msg'=>'hello','time'=>time()],用$redis->publish('im', json_encode(...))推送到频道。
Swoole 6.1.1 是一个专为 PHP 设计的高性能事件驱动并发网络引擎。作为稳定版,它修复了编译时对 zlib 依赖的缺失及 curl 模块的内存安全风险。该版本支持协程、多线程与多进程架构,内置 TCP/HTTP/WebSocket 服务器,能够显著提升 PHP 在微服务、实时通信等场景下的执行效率与并发能力。
订阅回调里遍历$table,对每个uid匹配的fd调用$server->push($fd, $raw)——这里$server->isEstablished($fd)校验必不可少,否则向已关闭的fd推送会触发警告并中断协程。
方法二:直连Redis Hash存全局映射(适用于中小规模)
用hSet("user:online:U1001", $fd, $ip)存所有在线fd,发消息时hGetAll("user:online:U2002")取全部fd列表,逐个$server->push()。但此方式在10万连接时Hash操作变慢,需搭配Pipeline批量推送。
处理连接异常与资源回收
第一步:监听close事件,立即从$table中del($fd),释放内存。
第二步:补充disconnect兜底逻辑——某些弱网环境下客户端静默断开,close不触发。需在onMessage中对超时未发心跳的连接主动close。
第三步:在onClose里清除Redis中该fd对应的临时键,例如del "socket:fd:{$fd}",否则残留键堆积导致内存泄漏。
【fd复用陷阱】Swoole会复用已关闭连接的fd编号,若不清除Redis旧记录,新连接可能继承前一个用户的权限或消息路由关系。
部署与进程守护
使用Supervisor管理进程:编写/etc/supervisor/conf.d/websocket.conf,设置autostart=true、autorestart=true、startretries=3。
执行supervisorctl reread && supervisorctl update && supervisorctl start websocket:启动服务。
Nginx反向代理配置需添加proxy_http_version 1.1;和proxy_set_header Upgrade $http_upgrade;,否则WebSocket握手失败返回400。










