workerman tcp默认不处理粘包拆包,因tcp是面向字节流协议,onmessage收到的数据可能不完整或混乱;必须通过自定义协议(含input/decode/encode方法)定义消息边界,否则依赖默认text协议易出错。

Workerman TCP 默认不处理粘包拆包,直接收发原始字节流,所以业务层必须自己定义边界规则,否则 onMessage 收到的 $data 可能是半个包、两个包拼在一起,或带多余头尾——这不是 Workerman 的缺陷,而是 TCP 本身的流式特性决定的。
为什么 TCP 连接下 onMessage 会收到不完整或混乱的数据
TCP 是面向字节流的传输层协议,不保证“一次 send() 对应一次 recv()”。客户端连续发两段 "hello\n" 和 "world\n",服务端可能一次性收到 "hello\nworld\n",也可能只收到 "hel" 然后隔几毫秒再收到 "lo\nworld\n"。Workerman 的 onMessage 回调只在框架认为“一个完整应用消息已就绪”时触发,而判断依据完全来自你提供的协议类。
-
input方法返回 0 → 框架继续攒数据 -
input返回正整数$n→ 框架等缓冲区 ≥$n字节后截取前$n字节交给decode - 没实现协议类?默认用
Workerman\Protocols\Text,它只认换行符"\n"作分隔 —— 一旦业务数据里含\n(比如 JSON 日志、用户输入),立刻错乱
什么时候必须写自定义协议,而不是用 Text 协议
Text 协议只适合纯文本、严格以 \n 分隔、且内容不含 \n 的场景(如 telnet 调试命令)。只要出现以下任一情况,就得自己写:
- 协议体是二进制(如 Unity 客户端用
BitConverter.GetBytes(len)前置包长) - 消息含任意字节(比如 base64 图片、加密 payload、嵌套 JSON)
- 需要校验和、版本号、加密标识等头部字段
- 要兼容多个客户端(C#、Java、JS),需统一封包格式避免各端解析歧义
例如 C# 客户端发 4字节长度 + JSON字符串,PHP 服务端若不写 input 提前读出这 4 字节并返回总长,onMessage 就永远等不到完整包。
input 方法写错是最常见的崩溃点
这个方法在每次收到新数据时被同步调用,必须快速返回,不能阻塞或做耗时操作。常见错误包括:
- 没检查缓冲区长度就直接
unpack('N', $buffer)→$buffer不足 4 字节时 PHP 报 Warning 并返回false,连接被断开 - 返回负数或非整数 → Workerman 直接抛异常终止 worker 进程
- 逻辑写成“等够 4 字节再读长度,再等够长度字节”,但
input本就不该负责“等待”,只负责“告诉框架这次要等多少” - 对变长包(如 TLV)没做状态缓存,每次只看当前缓冲区片段,导致长度字段被截断无法识别
正确做法:用 strlen($buffer) 先兜底返回 0;足够时用 <code>unpack('Nlen', $buffer) 取长度,再返回 $len + 4(假设包长占 4 字节)。
协议类位置和命名不是可选配置,而是硬性约定
Workerman 通过反射自动加载协议类,路径和命名必须严格匹配:
- 文件必须放在
Protocols/目录下(如app/Protocols/MyTcp.php) - 类名必须与文件名一致(
class MyTcp) - 必须声明
namespace Protocols; - 三个方法都得是
public static,签名不能改(input(string $buffer): int|false等)
少一个条件,Workerman 启动时不会报错,但运行时会静默 fallback 到 Text 协议,问题表现为:本地测试正常,上线后高并发下突然大量粘包——因为协议根本没生效。











