我为什么在 WebSocket 上坚持用二进制帧 Protobuf而不是直接传 JSON前段时间我在翻aq-chat-server的消息链路时又一次确认了一件事做实时聊天这类高频交互场景协议层真的不该图省事。很多项目一上来就选 WebSocket JSON因为调试方便、前后端都熟。但这个项目没有这么做而是在aq-chat-im/src/main/java/com/howcode/aqchat/codec/MessageDecoder.java和aq-chat-im/src/main/java/com/howcode/aqchat/codec/MessageEncoder.java里明确用了[消息体长度 4 字节][指令号 2 字节][消息体 Protobuf 二进制]这套格式。我现在反而越来越认同这种“看起来麻烦一点”的设计因为它把消息边界、消息类型和业务负载拆得很干净后面的 Netty 分发链路也因此简单很多。先把协议边界固定住MessageDecoder的处理方式很直接先检查可读字节数够不够 6不够就继续等够了以后先读长度再读指令号如果当前帧里的消息体还没到齐就resetReaderIndex()回退等下次继续。这一段很关键因为它实际上把“半包怎么办”这个问题封在了解码器里而不是让后面的业务处理器感知一个“不完整消息”。如果这里走 JSON常见写法往往会变成“拿到一段字符串再想办法判断是不是完整对象”。这在 HTTP 请求里问题不大但在长连接的高频双向通信里其实很容易把协议边界和业务格式搅在一起。长度头的价值就在这里消息有没有收完整不靠猜不靠扫括号不靠找分隔符直接按字节数判断。指令号让分发成本保持稳定这个项目还有一个我很喜欢的点是它没有把“消息类型”塞到正文里而是单独给了 2 字节的command。MessageRecognizer会在启动时扫描AQChatMsgProtocol里的内部类把 Protobuf 消息类和枚举里的指令号关联起来位置在aq-chat-im/src/main/java/com/howcode/aqchat/message/MessageRecognizer.java。这样一来解码器拿到command之后可以直接找到对应的 Builder完成反序列化。这一步看似只是少写几个if-else但工程意义其实挺大。因为到了aq-chat-im/src/main/java/com/howcode/aqchat/handler/AQChatCommandHandler.java入站对象已经是明确的GeneratedMessageV3子类了后面只需要根据消息类去找ICmdHandler对应实现不需要先 parse 一遍 JSON再读type字段再做一次路由。协议层、识别层、业务层的职责边界是清楚的。我做这类服务时特别怕一种写法网络层收到一坨 JSON业务层自己判断action再从 Map 里掏字段最后每个 handler 都偷偷做一遍校验和转换。这样前期写起来快后期加命令、查线上问题、做兼容时都会越来越乱。这个项目至少在消息入口这件事上是把地基先打稳了。二进制协议不只是“更快”而是更可控很多人提到 Protobuf第一反应都是性能。这个当然没错二进制更省流量序列化反序列化也更直接。但如果只把它理解成“比 JSON 快”我觉得有点低估它了。对一个 IM 服务来说更重要的是可控性。比如指令号固定之后协议演进的入口就很明确比如消息体是强类型的服务端不会在运行时才发现某个字段名拼错再比如MessageEncoder出站时也是同一套格式意味着客户端和服务端在协议层天然对齐而不是各写各的字符串约定。这套设计当然不是没有代价。调试门槛会高一点肉眼抓包不像 JSON 那么友好新增命令也要同步改 proto、生成类、注册映射、补 handler流程比“加个字段”重一些。但如果业务本身就是聊天、广播、房间同步、离线消息这些高频场景这种代价我认为值得。因为它买来的不是某次 benchmark 上的几毫秒而是协议不会随着功能变多逐渐失控。我现在更在意的是链路稳定性回头看aq-chat-server这条链路其实最有价值的不是单个类写得多花而是几个点串起来以后很顺MessageDecoder负责切包和识别入口MessageRecognizer负责把指令号映射到具体消息类型AQChatCommandHandler负责把强类型消息交给真正的命令处理器。每一层都只做一件事。这也是我现在做实时系统时越来越看重的一个标准协议层是否能把复杂度挡在前面。只要入口是稳定的后面的房间、离线、MQ、AI 扩展都还有整理空间但如果入口本身就是松散文本协议后面基本只会越来越难收拾。所以这篇文章如果只留一个结论那就是我坚持二进制帧 Protobuf不是因为它“更高级”而是因为它让长连接消息系统在规模变大之后依然能维持比较低的认知成本。对聊天系统来说这比一开始省下来的那点开发时间重要得多。