关键在于显式配置Swoole\Server的拆包规则:TCP用setProtocol()设package_length_type(通常选N)、offset等字段,UDP用on('packet')手动处理,WebSocket不支持自定义协议;需注意buffer大小、字节序匹配及调试日志。

如何在 Swoole 服务端注册自定义协议解析器
关键不在“写协议”,而在让 Swoole\Server 知道怎么拆包。Swoole 不会自动识别你设计的帧头帧尾,必须显式绑定 setProtocol() 或用 on('packet') 手动接管。
常见错误是直接在 on('receive') 里拼接字符串做解析——这会导致粘包/半包逻辑混乱,且无法兼容 UDP 和 WebSocket 混合场景。
- 只对 TCP 类型服务器(
SWOOLE_SOCK_TCP)生效;UDP 用on('packet'),WebSocket 则不能用自定义协议(协议已被固定) -
setProtocol()接收一个数组,必须包含package_length_type、package_length_offset、package_body_offset等字段,缺一不可 - 若协议含魔数(如
0x12345678),得配合package_start_symbol使用,但该字段不支持多字节符号(只能是单字节,否则静默失败)
为什么 package_length_type 选 N 而不是 V 容易出错
它决定长度字段按哪种字节序读取: N 是大端(network byte order),V 是小端。绝大多数 C/C++ 客户端默认用 htonl() 写长度,即大端,所以服务端必须配 N。配反了,length 会变成乱码值,导致 package_max_length 触发截断或直接丢弃连接。
- PHP 中可用
pack('N', $len)模拟客户端发包验证 - Wireshark 抓包时看原始十六进制,确认长度字段是否为
00 00 00 xx形式(大端) -
package_length_type只支持c/C/s/S/l/L/N/V,不支持int32这类别名,填错会静默忽略配置
on('packet') 在 UDP 场景下比 setProtocol() 更可控
TCP 的 setProtocol() 是全自动拆包,UDP 则必须手动处理每个 packet。这不是限制,而是必要:UDP 天然无连接、无顺序、单包独立,自动拆包没意义。
典型误用是试图在 UDP 上也配 package_length_type——Swoole 会忽略这些配置,但开发者可能误以为“没生效是因为配置错”,其实根本没走那套逻辑。
-
on('packet')回调中,$data就是完整一包原始二进制,无需拼接 - 如果客户端发的是结构化数据(如 Protocol Buffers),直接
unpack()或用substr()+unpack()提取字段即可 - 注意
$client_info['server_socket']可用来区分监听的多个 UDP 端口(比如同时开 IPv4 和 IPv6)
调试自定义协议时最常被忽略的缓冲区行为
Swoole 的协议解析器底层依赖 buffer,而 buffer 默认大小是 64KB。如果单包超过这个值,且未设置 package_max_length,Swoole 会直接关闭连接,日志里只显示 connection close: protocol error,不提示具体原因。
- 务必设置
package_max_length,值略大于你协议允许的最大包长(比如最大 2MB,就设2097152) - 开启
log_level => SWOOLE_LOG_DEBUG后,可在日志中搜protocol关键字,看到每次拆包的 offset 和 length 计算过程 - 用
strace -e trace=recvfrom,sendto跟客户端交互,确认收发是否真的一一对应,避免把客户端发包逻辑问题当成服务端协议 bug










