workerman中需手动实现文本协议的decode和encode方法:decode要处理粘包半包并维护tmp_buffer,返回null/数组/false;encode须严格匹配格式并补全分隔符;协议类通过$worker->protocol='classname'注册且须实现protocolinterface接口。

Workerman里怎么定义一个文本协议
Workerman本身不内置文本协议解析,必须手动实现 decode 和 encode 方法。核心逻辑是:收到原始 $buffer 后,按约定规则切出完整消息;发数据前,把业务数据拼成符合协议格式的字符串。
常见错误是直接用 explode("\n", $buffer) 粗暴分割——网络传输中换行符可能被拆开、粘包,导致解包失败。
- 必须维护未处理完的残留缓冲区(
$connection->tmp_buffer或类似字段) - 协议分隔符不能只依赖换行符,建议用固定结尾(如
"\r\n")或长度头 + 内容 - 文本协议推荐用
"{json}\r\n"或"CMD|key=value|key=value\r\n"这类易读、易调试的格式
decode 怎么写才不丢数据
decode 函数要能处理粘包和半包,返回 null 表示数据不完整,返回数组表示一条完整消息,返回 false 表示协议错误。
典型文本协议(以 "\r\n" 结尾)的解包逻辑:
public function decode($connection, $buffer)
{
// 累积到临时缓冲区
$connection->tmp_buffer .= $buffer;
// 查找完整消息结尾
$pos = strpos($connection->tmp_buffer, "\r\n");
if ($pos === false) {
return null; // 还没收到完整一行
}
$line = substr($connection->tmp_buffer, 0, $pos);
$connection->tmp_buffer = substr($connection->tmp_buffer, $pos + 2); // 剥离 \r\n
// 解析内容,比如 JSON 格式
$data = json_decode($line, true);
if ($data === null && json_last_error() !== JSON_ERROR_NONE) {
return false; // 协议错误
}
return $data;
}
- 不要在
decode中做耗时操作(如数据库查询),它在事件循环中同步执行 - 必须清空已处理部分,否则下次调用会重复解析
- 如果协议允许空行或注释行,需在
substr后加trim()判断
encode 怎么保证对方能正确 decode
encode 只负责把 PHP 数据转成字节流,重点是格式严格匹配 decode 的预期。
对应上面的 "{json}\r\n" 协议,encode 应该:
public function encode($connection, $data)
{
$json = json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
if ($json === false) {
return ''; // 或抛异常,但 Workerman 会静默丢弃
}
return $json . "\r\n";
}
- 必须补上结尾符(
"\r\n"),缺一个字节,对方就永远等不到完整包 - 避免使用
JSON_PRETTY_PRINT,缩进和换行会破坏单行协议结构 - 如果业务要求二进制安全(比如含 \0 字符),文本协议就不合适,得换二进制协议
Workerman启动时怎么注册自定义协议
协议类写好后,要传给 Worker 构造函数,而不是通过配置文件或全局注册。
示例(TCP 服务):
use Workerman\Worker;
require_once __DIR__ . '/vendor/autoload.php';
// 自定义协议类需提前加载
require_once __DIR__ . '/MyTextProtocol.php';
$worker = new Worker('text://0.0.0.0:1234');
$worker->protocol = 'MyTextProtocol'; // 类名,非实例
$worker->onMessage = function($connection, $data) {
var_dump($data); // $data 就是 decode 返回的数组
$connection->send(['status' => 'ok', 'time' => time()]);
};
Worker::runAll();
- 类名必须可自动加载,或显式
require_once,否则报Class 'MyTextProtocol' not found - 协议类必须继承
Workerman\Protocols\ProtocolInterface,至少实现decode和encode - 别误写成
$worker->protocol = new MyTextProtocol(),Workerman 要的是类名字符串
文本协议看着简单,但边界情况多:空包、超长行、非法 JSON、连接断开时残留缓冲区……真正稳定运行,得在 decode 里反复验证 $connection->tmp_buffer 长度和内容形态。











