Skip to content

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.tspackages/protocol/src/framing.ts:28,58

理解要点

  • 黏包:一包含多帧,while 循环连续切出。
  • 半包:一帧跨多包,状态机记住 expectedpayloadLen 续攒。
  • 帧自描述:解出顺序 = 编码顺序,不依赖 chunk 边界。

下一步:4.1 服务端与会话管理