Swoole需关注“分包”而非“粘包”,因其onMessage收到的必为完整WebSocket帧,协议层粘包已被自动处理;真正需开发者介入的是超大数据(如大文件、AI流)主动分片发送与客户端重组。

WebSocket 协议本身已定义帧边界(FIN 位、opcode、payload length),Netty 等框架能自动处理分片与粘包;但 Swoole 的 WebSocket Server 默认不提供应用层消息聚合机制——它把每个 WebSocketFrame 当作独立事件投递,大报文若被底层 TCP 拆成多个 TCP 包,Swoole 仍会按帧完整接收并触发 onMessage;但如果业务层主动发送超长数据(如 > 1MB JSON),需自行控制分片逻辑,否则可能触发客户端或服务端帧长度限制(如浏览器默认 128MB,Swoole 默认无硬限但受内存与 timeout 影响)。
为什么 Swoole 需要关注“分包”而非“粘包”?
Swoole 的 onMessage 回调收到的一定是完整的 WebSocket 帧(文本或二进制),协议层粘包已被内核和 Swoole 自动剥离。真正需要开发者介入的场景是:
- 发送单条超大数据(如文件切片、大模型响应流),超出客户端接收缓冲或 WebSocket 实现的帧大小容忍上限
- 需要支持断点续传、进度反馈、优先级调度等高级语义
- 跨语言/跨平台兼容性要求:某些轻量客户端(嵌入式、小程序)对单帧大小敏感
服务端主动分包发送(带序号+结束标识)
核心思路:将大 payload 拆为固定大小 chunk(如 64KB),每帧携带序号、总片数、是否末片标志,客户端按序缓存合并。
示例代码(Swoole 4.8+,协程风格):
WebSocket 8.18.2 是该协议规范的一个重要迭代版本,主要优化了连接稳定性与数据传输效率。它通过全双工通信机制,允许客户端与服务器在单一长连接上实时交换数据,大幅降低传统 HTTP 轮询的开销。该版本增强了心跳保活、自动重连及二进制帧传输能力,适用于即时通讯、在线游戏及金融行情推送等低延迟场景,为开发者提供更可靠的实时网络交互基础。
// $fd: 客户端连接 ID, $data: 待发送的原始字符串或二进制
function sendLargeMessage($server, $fd, $data) {
$chunkSize = 65536;
$total = strlen($data);
$chunks = str_split($data, $chunkSize);
$count = count($chunks);
<pre class="brush:php;toolbar:false;">foreach ($chunks as $index => $chunk) {
$isLast = ($index === $count - 1);
$header = pack('N2C', $index, $count, (int)$isLast); // uint32_t index, uint32_t total, uint8_t is_last
$frame = $header . $chunk;
// 使用二进制帧发送(避免 UTF-8 解码问题)
$server->push($fd, $frame, WEBSOCKET_OPCODE_BINARY);
}}
说明:
- 头部用
pack('N2C')写入网络字节序的 4+4+1 字节,紧凑且跨平台可解析 - 使用
WEBSOCKET_OPCODE_BINARY避免文本帧的 UTF-8 校验开销与失败风险 - 不依赖 FIN 位做业务分片——因为 Swoole 不暴露 WebSocket 帧的 FIN 控制权给 PHP 层
客户端接收端重组逻辑(JS 示例)
前端需监听 binary 类型 message,并按 header 解包合并:
const chunks = new Map();
let totalExpected = 0;
<p>socket.addEventListener('message', (event) => {
if (event.data instanceof ArrayBuffer) {
const view = new DataView(event.data);
const index = view.getUint32(0, false); // offset 0, big-endian
const count = view.getUint32(4, false); // offset 4
const isLast = view.getUint8(8) === 1;</p><pre class="brush:php;toolbar:false;">if (count > 0 && !totalExpected) totalExpected = count;
const payload = event.data.slice(9); // skip header
chunks.set(index, payload);
if (isLast && chunks.size === totalExpected) {
// 按 index 排序拼接
const sorted = Array.from(chunks.entries()).sort(([a],[b]) => a-b).map(([,v]) => v);
const full = new Blob(sorted);
handleCompleteMessage(full);
chunks.clear();
totalExpected = 0;
}} });
替代方案:启用 Swoole 的 HTTP2 + 流式响应(适合服务端流式输出)
若场景是后端生成长内容(如 AI 推理流),可绕过 WebSocket 分片,改用 HTTP/2 Server Push 或 SSE:
- 启动 Swoole HTTP 服务器,启用
http2 => true - 客户端用
fetch()+ReadableStream持续读取 chunked body - 服务端用
$response->write("data: ...\n\n")分段推送,天然支持流控与 cancel - 比 WebSocket 分片更轻量,无帧头解析负担,且浏览器兼容性更好

















