通信协议选型
概述
游戏服务器与客户端的通信协议直接影响性能、实时性与跨端兼容性。传输层选 TCP/UDP/WebSocket,数据序列化选 Protobuf/FlatBuffers/MessagePack,最后落到自定义协议(消息头 + 消息体)设计。本文讲透三个层次。
一、传输层协议对比
1.1 TCP
| 特性 | 说明 |
|---|---|
| 可靠 | 保证到达、有序 |
| 面向字节流 | 需要自己处理粘包拆包 |
| 有连接 | 状态维护、心跳 |
| 适合 | 回合制、状态同步、一般对战 |
TCP 优势:
可靠有序(消息不丢不乱)
连接复用(长连接)
实现简单(成熟可靠)
TCP 劣势:
头阻塞(队头阻塞)
慢启动(连接初期慢)
重传延迟1.2 UDP
| 特性 | 说明 |
|---|---|
| 不可靠 | 可能丢包、乱序 |
| 无连接 | 无需建立连接 |
| 低延迟 | 无重传、无阻塞 |
| 适合 | 帧同步、实时动作、语音 |
UDP 优势:
低延迟(无重传等待)
无队头阻塞
适合实时输入
UDP 劣势:
丢包乱序
需要自建可靠性(ACK/序号)
防火墙/NAT 穿透难1.3 WebSocket
| 特性 | 说明 |
|---|---|
| 基于 TCP | 全双工长连接 |
| 兼容 HTTP | 浏览器/小程序天然支持 |
| 消息帧 | 自带帧边界(无粘包) |
| 适合 | H5、微信小游戏、网页端 |
WebSocket 优势:
浏览器/小程序直接可用
消息帧格式(方便编解码)
可过防火墙(80/443 端口)
WebSocket 劣势:
基于 TCP(有队头阻塞)
性能略低于裸 TCP1.4 三协议对比表
| 维度 | TCP | UDP | WebSocket |
|---|---|---|---|
| 可靠性 | 可靠 | 不可靠 | 可靠 |
| 延迟 | 中 | 低 | 中 |
| 粘包 | 有 | 无(报文) | 无(帧) |
| 跨端 | 原生端 | 原生端 | 浏览器/小程序 |
| 实时性 | 中 | 高 | 中 |
| 复杂度 | 中 | 高(自建可靠) | 低 |
选型结论:
休闲/回合制/状态同步 → TCP 或 WebSocket
帧同步/动作类实时 → UDP(可带可靠子层)
微信小游戏/H5 → WebSocket(必选)
混合:核心对战 UDP + 大厅业务 WebSocket二、序列化方案对比
2.1 主流序列化方案
| 方案 | 类型 | 特点 |
|---|---|---|
| Protobuf | 二进制 | 高效、跨语言、需 .proto 定义 |
| FlatBuffers | 二进制 | 零拷贝反序列化、适合频繁读取 |
| MessagePack | 二进制 | JSON 友好、简单 |
| JSON | 文本 | 可读、体积大、慢 |
| Kryo | 二进制 | Java 专属、快 |
为什么二进制优于 JSON:
体积小(减少带宽)
编解码快(性能)
类型安全(强类型)2.2 Protobuf 深入
Protobuf 特点:
需要 .proto 定义消息结构
跨语言生成代码(Java/C++/C#/JS)
字段编号编码(tag-value)
变长整数(Varint)压缩小数字
向后兼容(字段添加/删除友好)
缺点:
反序列化需完整消息
.proto 版本管理成本proto 示例:
message C2SLogin {
string account = 1;
string token = 2;
}
规则:
字段编号 1-15 用 1 字节 tag
小数字用 Varint 压缩
常用字段用小编号2.3 FlatBuffers 深入
FlatBuffers 特点:
零拷贝读取(不反序列化直接访问)
适合需要频繁读取部分字段的场景
支持 schema evolution
适用:
频繁读大对象
服务端广播频繁字段
游戏客户端属性同步
缺点:
生成代码较复杂
写数据稍复杂2.4 MessagePack
MessagePack 特点:
JSON 风格但二进制
动态类型(无需预定义)
简单易用
适用:
动态结构、快速接入
与 JSON 生态互通
缺点:
体积/性能略逊 Protobuf2.5 序列化选型表
| 场景 | 推荐 |
|---|---|
| 通用游戏协议 | Protobuf(首选) |
| 频繁读取大对象 | FlatBuffers |
| 快速原型 | MessagePack |
| 调试/日志 | JSON |
结论:
休闲游戏首选 Protobuf
(体积小、跨端、社区成熟)三、自定义协议设计
3.1 消息结构总览
游戏协议 = 传输层 + 消息头 + 消息体
常用消息头结构:
Magic Number + Version + Serializer
+ Opcode + Length + Body
或简化:
Length + Opcode + Body3.2 字段设计详解
| 字段 | 说明 |
|---|---|
| Magic Number | 魔数校验(0x1234 等) |
| Version | 协议版本号 |
| Serializer | 序列化方式标识 |
| Opcode | 消息类型(操作码) |
| Length | 消息体长度 |
| Body | 消息体(Protobuf 等) |
消息头设计示例(二进制):
2 字节 Magic(0xABCD)
1 字节 Version
1 字节 Serializer
2 字节 Opcode
4 字节 Length
变长 Body
→ 头部固定 10 字节 + Body3.3 Opcode 设计
Opcode 规划:
按模块分段:
0x1000-0x1FFF 登录模块
0x2000-0x2FFF 房间模块
0x3000-0x3FFF 对战模块
0x4000-0x4FFF 社交模块
消息方向:
客户端→服务端(C2S)
服务端→客户端(S2C)
约定:
奇偶区分 C2S/S2C
或单独字段标识3.4 自定义协议要点
设计要点:
魔数:防错误数据进入
版本:升级兼容
长度:防粘包拆包
序号:去重/重排(可靠传输)
时间戳:防重放(安全)
错误处理:
未知 Opcode → 忽略/报错
长度非法 → 断开连接
版本不符 → 提示升级四、粘包与拆包
4.1 问题来源
TCP 是字节流:
多条消息可能粘在一起(粘包)
一条消息可能被拆开(拆包)
原因:
发送方批量写
接收方缓冲积累
解决:
消息头带长度 → 按长度拆4.2 解决方式
| 方式 | 说明 |
|---|---|
| 固定长度 | 每条定长(浪费) |
| 长度前缀 | 头带长度(主流) |
| 分隔符 | 特殊字符分隔(简单) |
长度前缀法:
读 4 字节长度 → 读满 length 字节 → 解析
处理完再读下一条
框架支持:
Netty LengthFieldBasedFrameDecoder五、WebSocket 协议细节
5.1 帧格式
WebSocket 帧:
FIN + Opcode(0x1 Text / 0x2 Binary)
+ Mask + Payload Len + 数据
Opcode:
0x1 文本帧
0x2 二进制帧
0x8 关闭帧
0x9/0xA 心跳 Ping/Pong5.2 游戏中使用
实践:
游戏消息用二进制帧(BinaryFrame)
内部再包自定义协议头
心跳:
客户端定期 Ping
服务端回 Pong
(WebSocket 自带保活)
微信小游戏:
使用 wx.connectSocket
天然 WebSocket 协议六、传输可靠性补充
6.1 需要可靠性时(TCP 之外)
自建可靠 UDP(KCP/自研):
序号 + ACK + 重传
适用:帧同步丢包重传关键帧
注意:
可靠子层增加复杂度
评估是否真需要6.2 心跳与超时
心跳机制:
客户端定时发心跳
服务端超时未收到 → 判定离线
参数:
心跳间隔(30s 常见)
超时倍数(3 次未收到)
作用:
检测死连接
释放资源
玩家上下线感知七、常见问题
7.1 到底用 TCP 还是 UDP
决策:
回合制/状态同步 → TCP(可靠省心)
帧同步动作类 → UDP + 可靠子层
不确定 → 先 TCP,性能不足再改7.2 WebSocket 性能够吗
休闲游戏足够:
单房间广播(10 人内)WebSocket 完全胜任
微信小游戏/H5 是唯一选择
PC 原生端可用裸 TCP 更极致7.3 协议升级怎么兼容
兼容策略:
版本号分段处理
新增字段(Protobuf 兼容)
老客户端保持旧协议
新旧并存过渡期
注意:
破坏性变更走新 Opcode
避免改动既有字段编号八、小结
协议选型三层递进:传输层(TCP/UDP/WebSocket)决定可靠性与实时性,序列化(Protobuf 首选)决定效率与兼容,消息头设计(Magic/Version/Opcode/Length)决定协议的健壮性与可扩展性。休闲游戏的标准组合是 WebSocket(或 TCP)+ Protobuf + 长度前缀自定义消息头,兼顾实时、跨端与开发效率。