最稳通用解法是open_length_check配合包头长度字段;open_eof_check仅适用于可控纯文本场景,因tcp流式特性及eof限制易出错,生产环境应默认启用open_length_check并严格配置参数。

直接用 open_length_check 配合包头长度字段,是最稳、最通用的解法;open_eof_check 仅适合纯文本且能严格规避结束符的简单场景,线上服务慎用。
为什么不能靠 onReceive 回调里手动 explode() 分割?
因为 TCP 是流式协议,$data 参数不保证是“一条完整消息”——它可能是半条、两条拼一起、甚至三条叠着来。你用 explode("\r\n", $data) 只能处理“刚好完整到达且没跨包”的情况,一旦出现拆包(比如 "hel\r\nlo_world\r\n" 被切成两段传),就彻底失效。
- 客户端发
"a\r\nb\r\nc\r\n",服务端可能一次收到全部,也可能分两次:第一次"a\r\nb",第二次"\rc\r\n"(注意换行符被截断) -
explode()在第二次调用时会把"\rc\r\n"拆成["", "c"],漏掉前面的b,还多出空字符串 - 底层没有缓冲区管理,你得自己维护一个连接级的
$buffer并反复strpos/substr,代码易错、性能差、边界难测
open_eof_check 的真实限制和坑点
启用这个选项看似简单,但实际约束极强:
-
package_eof最大只支持 8 字节,且必须是 ASCII 字符;二进制数据里随便一个\x00就可能撞上,导致提前截断 - 业务数据若含
\r\n(比如用户发了一段带换行的 JSON),open_eof_split会把它当成两个包切开,数据错乱 - 性能上要逐字节扫描找 EOF,大数据包时 CPU 开销明显高于长度方案
- 它无法处理“包体中间含 EOF”的合法场景(比如传输 base64 编码的图片)
所以它只适合日志推送、命令行协议这类你能 100% 控制内容格式的轻量交互。
Swoole 6.1.1 是一个专为 PHP 设计的高性能事件驱动并发网络引擎。作为稳定版,它修复了编译时对 zlib 依赖的缺失及 curl 模块的内存安全风险。该版本支持协程、多线程与多进程架构,内置 TCP/HTTP/WebSocket 服务器,能够显著提升 PHP 在微服务、实时通信等场景下的执行效率与并发能力。
必须用 open_length_check 的核心配置项
这才是生产环境该默认打开的方式。关键不是“开了就行”,而是参数必须对齐:
-
'open_length_check' => true:开关,必开 -
'package_length_type' => 'N':表示包头是 4 字节大端无符号整数(pack('N', $len)对应);若用'n'就是 2 字节,错配直接解析失败 -
'package_length_offset' => 0:长度字段从包首开始算,别写成 1 或 -1 -
'package_body_start' => 4:包体从第 5 字节起(即跳过 4 字节长度头),和package_length_type的字节数必须一致 -
'package_max_length' => 2 * 1024 * 1024:设个合理上限,防恶意超长包耗尽内存
客户端打包示例:$head = pack('N', strlen($body)); $client->send($head . $body); ——少一个 pack 或字节序写反,服务端就收不到完整包。
自定义协议时,onReceive 里仍需缓冲区管理
当你的协议是 TLV、变长包头、或需要校验和/加密时,Swoole 内置机制不够用,就得自己动手。此时必须为每个 $fd 维护独立缓冲区:
- 每次
onReceive收到$data,先$buffer[$fd] .= $data - 循环检查
$buffer[$fd]是否够读取包头 → 解出长度 → 判断是否够读完整包体 - 够了就截取、处理、再从缓冲区删掉已处理部分;不够就等下次回调
- 务必加
package_max_length校验,避免缓冲区无限增长
最容易忽略的是:不同连接的缓冲区绝不能共用变量,也别用全局数组硬存——worker 进程间不共享内存,要用 swoole_table 或 redis 做跨进程缓冲,否则一重启就丢状态。










