workerman 5.2.3处理超大数据包必须采用状态机驱动的流式分块解析,不加载整包进内存,而是边收边存至磁盘或队列,结合连接超时、速率限制与进程保护机制避免oom和阻塞。

Workerman 5.2.3 处理超大数据包(如几十MB甚至更大)不能靠“一次性接收+解包”,必须走分块、流式、状态化处理路径。核心原则是:不把整包加载进内存,不依赖单次 input 返回完整长度,而是用状态机驱动的渐进式解析。
用状态机替代简单 input 判断
标准的 input 方法只适合固定头长或行分隔等轻量协议,遇到超大包(比如含 20MB 文件体),若仍按“读完包头→返回总长→等待全部到达”逻辑,缓冲区会堆积巨量数据,极易触发 OOM 或超时断连。
正确做法是实现带内部状态的协议类,例如:
- 定义状态:
STATE_HEADER(读4字节长度)、STATE_BODY_CHUNK(分批收包体)、STATE_DONE -
input()不再返回总包长,而是每次返回当前可安全消费的字节数(如头阶段返4,体阶段返 min(8192, 剩余待收字节数)) - 在类属性中保存已接收 body 字节数、目标总长、临时文件句柄等上下文
边收边存:流式写入磁盘或临时存储
超大数据包本质是“流”,不是“值”。业务层不需要立刻拿到全部内容,而应尽快落盘或转存。
- 收到包头后,立即创建临时文件(
fopen('/tmp/upload_'.uniqid(), 'wb'))或写入 Redis Stream / 消息队列 - 每收到一个 chunk(如 64KB),直接
fwrite()追加,不拼接字符串 - 全部接收完成后,在
decode()中只返回元数据(如['file_id' => 'xxx', 'size' => 23456789]),而非原始二进制
配合连接级限速与超时控制
防止恶意或异常客户端持续发送无效数据拖垮服务:
- 在
onConnect中设置连接级超时:$connection->sendTimeout = 300;(单位秒) - 对上传类连接启用速率限制(需自行实现):记录每秒接收字节数,超阈值调用
$connection->close() - 在协议类中检测连续多次小包(如 10 次
input都只返回 1 字节),视为协议错误主动断连
避免常见陷阱
这些点看似细节,但在线上高频传输中极易引发故障:
- 不要在
decode()中json_decode(file_get_contents(...))—— 会把整个文件读进内存 - 不要用
substr($buffer, 0, $len)处理大 buffer —— PHP 字符串复制开销高,改用stream_get_contents()或直接操作资源 - 别让一个连接长期占用 worker 进程:超大包传输期间,该进程无法响应其他请求,建议结合 connection pool 或降级为后台任务处理











