第 11 / 20 篇

第 10 章 从行协议走向自定义协议

10.1 先写一份人能读懂的协议说明

在写编解码器前,定义字段与约束。例如:

字段 大小 约定
magic 2 字节 固定值,帮助拒绝明显错误输入
version 1 字节 协议版本
type 1 字节 请求、应答、心跳等
requestId 8 字节 关联请求与应答
bodyLength 4 字节 正文字节数,大端序
body 可变 UTF-8 JSON 或约定的二进制格式

以上是设计练习,主线工程没有实现这个二进制协议。不要把这张表误认为可以直接与行协议客户端通信。

10.2 从字段表推出帧参数

头部总长为 16 字节,bodyLength 位于偏移 12,宽度 4,表示正文长度。若希望保留整个头部供后续解码器读取,可以使用:

new LengthFieldBasedFrameDecoder(1048576, 12, 4, 0, 0)

长度字段末端正好是头部末端,因此调整值为 0;最后一个 0 表示不剥离头部。最大整帧为 1 MiB,正文最多为 1 MiB 减 16 字节。

之后的业务消息解码器应校验 magic、版本、type 和长度,再解析正文。字段值不可信,不能因为它来自已建立的 TCP 连接就跳过校验。

10.3 请求应答关系

客户端为请求分配 requestId,保存待完成结果;服务端应答携带相同编号。应答到达后找到对应请求并完成它;超时后删除记录,避免内存一直增长。

请求编号必须在尚未完成的请求范围内保持唯一。断线时要统一失败化尚未完成的请求,否则调用方可能永远等待。若发生重连,还要考虑旧连接的应答与新连接请求之间的隔离。

10.4 序列化与安全边界

JSON 便于初学和调试,但仍须限制正文大小、嵌套深度和字段范围。不要为了省事,把不可信网络输入交给 Java 原生对象反序列化。

公网传输敏感信息时,应使用 TLS,并在客户端验证证书和目标主机身份。加密、认证、授权是不同的问题:建立加密连接,不等于请求者有操作数据的权限。

小练习: 为什么只有 requestId,还不足以保证断线重试不会重复扣款?