webman默认不原生支持websocket,因其本质是基于swoole的http框架,未内置协议握手、帧解析、心跳维持及连接生命周期管理等底层逻辑;直接使用webman/websocket插件无法支撑万级连接,因该插件基于独立swoole进程,缺乏连接池、背压控制与跨进程状态同步,且单连接内存占用高、心跳易漏判、广播易阻塞event loop。

Webman 默认不原生支持 WebSocket,它本身是基于 Swoole 的 HTTP 框架,而 WebSocket 需要独立的长连接服务。直接在 Webman 的 HTTP 路由里“启用 WebSocket”会失败——因为协议握手、帧解析、心跳维持、连接生命周期管理这些底层逻辑,Webman 并未内置。
为什么不能直接用 webman/websocket 插件处理万级连接
社区确有 webman/websocket 插件,但它本质是封装了 Swoole 的 WebSocket\Server,运行在独立进程里,并非 Webman 主事件循环的一部分。问题在于:
- 它默认使用
swoole_websocket_server启动,但未做连接池、背压控制或内存复用,单连接内存占用约 120–180 KB(含 PHP 对象、Swoole buffer、opcode cache 引用) - 心跳检测靠
onMessage中手动发pong,容易漏判或堆积未响应连接 - 连接数超 5000 后,
select或epoll回调延迟上升,onOpen延迟可能突破 200ms,客户端感知为“连接慢” - 没有跨进程连接状态同步机制,无法配合 Redis 实现广播或踢人等操作
swoole_websocket_server 必须重配的 4 个关键参数
若坚持用 Swoole 原生 WebSocket Server(推荐),需绕过 Webman 插件,直接初始化并调优。以下配置直接影响万级连接稳定性:
WebSocket 8.18.2 是该协议规范的一个重要迭代版本,主要优化了连接稳定性与数据传输效率。它通过全双工通信机制,允许客户端与服务器在单一长连接上实时交换数据,大幅降低传统 HTTP 轮询的开销。该版本增强了心跳保活、自动重连及二进制帧传输能力,适用于即时通讯、在线游戏及金融行情推送等低延迟场景,为开发者提供更可靠的实时网络交互基础。
-
worker_num:设为 CPU 核心数 × 2,避免单 worker 过载;超过 32 个 worker 时,必须启用task_worker_num处理广播等耗时操作 -
open_tcp_nodelay:设为true,禁用 Nagle 算法,减少小包延迟(对实时消息关键) -
buffer_output_size:设为2 * 1024 * 1024(2MB),防止大消息写入阻塞;同时在业务中主动检查$server->isEstablished($fd)再 send -
heartbeat_check_interval和heartbeat_idle_time:分别设为30和60,即每 30 秒 ping,60 秒无 pong 则 close——比默认值更激进,能更快释放僵尸连接
如何安全广播消息而不阻塞 event loop
直接遍历所有 $server->connections 并 push() 是高危操作:一旦某个客户端网络卡顿,整个广播会阻塞,后续所有连接收不到消息。正确做法是:
- 用
redis pub/sub解耦:业务逻辑只向 Redis channelws:topic:chatpublish,由单独的 Swoole Worker 订阅后异步 push - 对每个连接启用
send_yield模式:$server->sendyield($fd, $data),它会自动把未发送完的数据挂起,下次可写事件再续发 - 限制单次广播最大并发数:
foreach (array_chunk($fds, 50) as $chunk),每批最多推 50 个 fd,中间usleep(1000)避免调度饥饿 - 务必捕获
Swoole\ExitException和InvalidArgumentException(fd 已关闭但未及时清理),否则会导致连接泄漏
连接数超 1w 后必须做的系统层调优
PHP 层配置再好,OS 层瓶颈不解决,照样扛不住。重点不是“能不能跑”,而是“会不会 silently 断连”:
- 文件描述符:
ulimit -n 1048576+echo 'fs.file-max = 2097152' >> /etc/sysctl.conf,否则Too many open files错误会在连接数 1024 或 4096 处突然爆发 - TIME_WAIT 复用:
net.ipv4.tcp_tw_reuse = 1,尤其当服务端主动 close 连接频繁时,能显著减少端口耗尽 - 关闭 swap:
swapoff -a,Swoole 内存模型对 swap 极其敏感,轻微交换就会导致心跳超时批量断连 - 不要用
docker run -p暴露 WebSocket 端口——iptables NAT 在万级连接下成为瓶颈;改用 host 网络模式或 Cilium eBPF 替代
真正卡住大规模 WebSocket 的,从来不是 PHP 代码写得够不够“优雅”,而是连接建立后没人管它的 buffer 是否溢出、心跳是否被丢弃、fd 是否被 OS 回收却没通知到 PHP 层。这些细节不落地,再多的“协程”“异步”都只是幻觉。










