Appearance
Demo:最小定帧协议
工程化复现第三部分的定帧 + 增量解码。源码在 pi-principles/src/protocol/framing.ts,入口在 pi-principles/play/protocol.ts。
工程化结构
text
pi-principles/src/protocol/
└── framing.ts # encodeFrame + FrameDecoder关键实现(framing.ts)
ts
/** 编码:给 payload 加 4 字节大端长度头 */
export function encodeFrame(payload: Uint8Array): Uint8Array {
const frame = new Uint8Array(4 + payload.byteLength);
const len = payload.byteLength;
frame[0] = len >>> 24; frame[1] = len >>> 16; frame[2] = len >>> 8; frame[3] = len;
frame.set(payload, 4);
return frame;
}
/** 解码:增量状态机,正确处理黏包/半包 */
export class FrameDecoder {
push(chunk: Uint8Array): Uint8Array[] {
// 阶段一:攒够 4 字节 header → 解析出本帧长度
// 阶段二:按长度攒 payload → 攒够弹出一条完整帧
}
end(): void { /* 若还有未读完的帧 → 抛"截断帧" */ }
}入口与运行
play/protocol.ts 把两条消息编码后故意错乱切分(a 被切在中间、a 尾巴和 b 拼进同一 chunk),喂给解码器验证能正确解出:
bash
cd pi-principles
bun play/protocol.ts # 或 npm run protocol输出:
解出: {"type":"hello"}
解出: {"type":"request"}
解码正常结束(无截断报错)对应到真实源码
| 工程化 Demo | 真实 pi 源码 |
|---|---|
protocol/framing.ts | packages/protocol/src/framing.ts:28,58 |
理解要点
- 黏包:一包含多帧,
while循环连续切出。 - 半包:一帧跨多包,状态机记住
expected和payloadLen续攒。 - 帧自描述:解出顺序 = 编码顺序,不依赖 chunk 边界。
下一步:4.1 服务端与会话管理。