webman长连接需通过继承support\process的自定义进程实现,不可直接extends workerman\worker;onworkerstart中创建worker实例并调用listen(),由webman主循环托管;连接状态、粘包、心跳、数据推送等均需手动管理。

Webman 本身不提供“长连接管理器”或“数据监听抽象层”,所有长连接(TCP/WS)和实时数据监听逻辑,都必须通过自定义进程 + Workerman 原生 API 实现。你不能在 app/controller 或中间件里写 onMessage,也不能靠路由转发二进制帧。
自定义进程类必须继承 support\Process,不是 Workerman\Worker
这是最常踩的坑:很多人照着 Workerman 文档直接 extends Workerman\Worker,结果进程启动后无日志、onWorkerStart 不执行、ps aux 看不到子进程。
-
support\Process是 Webman 封装的基类,它负责将进程注册进主循环、处理信号转发(如SIGTERM)、兼容config/process.php配置项(如count、user) - 若手动 extends
Workerman\Worker,就绕过了 Webman 的进程生命周期管理,onWorkerStart可能被忽略,onWorkerStop根本不会触发 - 正确写法:
class TcpMonitorProcess extends support\Process,且必须实现public function onWorkerStart($worker) - 类文件路径必须是
app/Process/TcpMonitorProcess.php(注意大小写与命名空间匹配),否则自动加载失败报Class not found
onWorkerStart 里不能阻塞,但可以启动 Worker 实例
Webman 自定义进程的 onWorkerStart 是一个回调入口,不是长期运行的主循环。你想监听 TCP,就得在里面 new 一个 Workerman\Worker 实例并 runAll() —— 但这会阻塞当前进程,导致 Webman 主循环卡死。正确做法是把 Worker 当作子服务启动,由它自己管理事件循环。
- 在
onWorkerStart中创建Workerman\Worker实例,例如:$tcp_worker = new Worker('tcp://0.0.0.0:9502'); - 设置
$tcp_worker->count = 1;(避免多进程冲突端口) - 绑定
onConnect/onMessage回调,但不要调用Worker::runAll()—— Webman 主进程已托管事件循环 - 最后执行
$tcp_worker->listen();,Workerman 内部会自动将其纳入全局事件循环 - 若漏掉
listen(),进程看似启动,实则不监听任何连接,客户端 connect 后立刻 EOF
长连接状态必须自己维护,没有内置连接池或上下文
HTTP 请求有 Request/Response 对象封装生命周期,而 TCP 连接没有“请求上下文”。每个 TcpConnection 是裸对象,onMessage 收到的数据是原始字节流,断连后连接对象即销毁 —— 你要存用户 ID、会话状态、心跳时间,全得自己来。
- 用
static $connections = [];或swoole_table(需扩展)缓存活跃连接,key 为$connection->id - 在
onConnect中记录连接时间、IP、初始化 session 字段;在onClose中 unset 对应条目 - 粘包/半包必须手动处理:不能直接
json_decode($data),要先解析协议头(如前4字节为包长度),再循环$connection->recv()直到收满 - 心跳超时检测不能依赖 HTTP 中间件,得用
Workerman\Timer::add()定期遍历$connections,对超过 60 秒无onMessage的连接主动close() - 数据库操作要用
support\Db::静态门面,但注意事务不跨连接 —— 每个 TCP 连接的操作都是独立会话,别指望Db::transaction()能覆盖多个onMessage调用
监听数据变更不能靠“事件总线”,得轮询或订阅底层存储
Webman 没有类似 Laravel 的 Event 系统广播到所有长连接。你想让某个 TCP 连接实时收到“订单状态更新”,不能发个 event(new OrderUpdated) 就完事。
- 常见做法是:在业务代码(如控制器)中,更新数据库后,显式调用一个推送函数,遍历目标连接列表并
$connection->send() - 更高效的方式是接入消息队列(如 RabbitMQ),在
RabbitMQProcess中消费消息,再根据 routing key 或 payload 决定推送给哪些 TCP 连接 - Redis Pub/Sub 也可行,但要注意:PHP Redis 连接默认非持久,
subscribe()会阻塞,必须用set_timeout(0)+ 循环read(),且无法在onMessage中直接调用(会冲突事件循环) - 切勿在
onMessage回调里做耗时同步操作(如 file_get_contents、curl_exec),会阻塞整个 Worker 进程,影响其他连接
真正的难点不在启动监听,而在连接生命周期管理与数据一致性 —— 每个 TcpConnection 是孤立的,没有共享内存、没有自动 GC、没有请求 ID 追踪。你写的每一行 onClose 和 Timer::add(),都在补 HTTP 模型缺失的那一块地基。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











