第 33 章 / 共 51 章
卷五 · 扩展、运维与实战
XTP2 扩展:在 TCP 上说二进制
TCP 给你字节流,XTP2 给你消息边界。本章把帧库(ch23 的伏笔)装进 EventData,得到一个能防粘包、防恶意帧的协议服务。
本章导读
站位:xtp2 是"编解码原语"(API 参考),本章是"组装工艺"——会话状态放哪、粘包怎么留、坏帧怎么断。完整成品在 实战二。
33.1 引入
二进制协议的痛点从来不是"设计帧格式"——是实现纪律:边界处理(粘包/半包)、长度校验(恶意超大帧)、类型安全(视图生命周期)。xtp2 把这三样做成了库:32 字节定长头 + 四型消息(REQUEST/RESPONSE/PUSH/EVENT)+ 流式解析器;你只需要给它喂字节、处理产出的消息。前置:build.bat xtp 变体。
33.2 帧与会话缓冲
组装的第一件事是会话结构:每连接一个处理器 + 半包残留缓冲(ch21 注册表模式的 TCP 版):
会话结构(注册表 + 处理器)
typedef struct XtpSession {
xtp2_processor* pRx; /* 每连接一个解析器 */
xbuffer* pPending; /* 半包残留(feed 消化不完的尾巴)*/
} XtpSession;
/* Open 建(入注册表)· Close 删(销毁处理器与缓冲)*/
为什么需要 pPending:xtp2_feed 一次至多产出一条消息——TCP 一次送达可能含"一条半"甚至多条;消化到 XTP2_MORE 时剩余字节要留到下一次 EventData 继续。
33.3 EventData 组装协议
协议组装核心(官方示例的会话化版本)签名出处 lib/xtp2.h
static void EventData(XS_HostInfo* pHost, XS_StreamConn* pConn, xnetbuf* pBuffer)
{
XtpSession* pS = SessionFind(pConn); /* 注册表取会话 */
xbuffer* pIn = pS->pPending;
BufferAppendNetBuf(pIn, pBuffer); /* 缓冲链 → 连续缓冲 */
for ( ;; ) {
size_t iUsed = 0;
xtp2_message tMsg;
xtp2_result r = xtp2_feed(pS->pRx,
BufferData(pIn), BufferSize(pIn), &iUsed, &tMsg);
BufferConsume(pIn, iUsed); /* 吃掉已消费部分 */
if ( r < 0 ) { /* 坏帧:粘性错误 */
SessionDrop(pConn); /* 断开:见 33.4 决策 */
return;
}
if ( r == XTP2_MESSAGE ) {
DispatchMessage(pConn, &tMsg); /* 用毕即弃的视图 */
continue; /* 缓冲里可能还有整条 */
}
break; /* XTP2_MORE:留尾巴等下一轮 */
}
}
刚才发生了什么——组装工艺的四个要点都在里面:残留缓冲衔接轮次、consumed 精确消费、消息视图当轮用毕、错误即断(粘性错误没有"再试试")。响应方向用 xtp2_measure + encode 打包后 xrtNetStreamSend——REQUEST 的 id 原样放进 RESPONSE 即可实现关联。
33.4 limits 与安全
- 长度防线:max_packet(默认 1MiB)在头解析期就生效——恶意超大帧在读 body 之前被拒,内存不会被"声明 4GB"的包头骗走
- 结构防线:参数表长度一致性复核、头字段越界检查、status 的无符号转换避开实现定义行为
- 断连策略:协议粘性错误意味着流已不可信——fuzz 验证的回环不变量保证"能 decode 的都是合法帧",反之坏帧后继续喂只会继续错。断开 + 日志是对二进制协议最诚实的处理
- 时限防线:TCP idle_timeout 兜住"连接建立后只发半个头"的慢速攻击
33.5 契约
- 会话线:每连接一个处理器;残留缓冲跨轮衔接;Close 终态统一销毁。
- 消息线:feed 产出的视图当轮用毕;需要留存先拷贝。
- 错误线:粘性错误 = 断开;limits 收紧到业务实际需要。
常见错误
忽略 consumed,整轮重喂
- 症状
- 消息重复处理或解析错位。
- 原因
- feed 的 consumed 告诉你本轮吃了几字节——不消费掉它们,下一轮会从头重解。缓冲操作必须严格按 consumed 前进。
坏帧后 reset 重试
- 症状
- 用 reset 清错误后继续喂同一条流,消息内容错乱。
- 原因
- reset 只重置解析器状态,不修复已经错位的字节流——半条坏消息的后半截会被当新消息头。reset 的正确用途是"复用处理器对象"(新连接/新流)。
编程练习
基础echo 帧
实现 PING REQUEST → PONG RESPONSE 的最小服务(客户端可用 xtp2_encode 造帧)。
验收:id 正确回带;一次发 3 条帧全部响应(粘包)。
进阶分片轰炸
客户端把一帧拆成 1 字节一片发送,验证半包缓冲的正确性。
验收:逐字节发完一条消息后服务端正确产出一次。
挑战恶意帧测试
构造超限长度头、截断帧、非法 magic 三类攻击,确认服务端内存平稳且断连干净。
验收:三类全部一次断连;RSS 全程平稳;正常客户端不受影响。
小结
| 要点 | 结论 |
|---|---|
| 定位 | 编解码原语:帧格式+解析纪律做成了库 |
| 组装 | 会话=处理器+残留缓冲;按 consumed 精确消费 |
| 消息 | feed 视图当轮用毕;REQUEST id 回带关联 |
| 安全 | limits 头期生效;坏帧即断;idle 兜底 |
本章涉及的 API 参考
xrt 延伸阅读
- xrt 参考 · buffer — 会话残留缓冲的 consume/append 模型