基于KCP的实时游戏通信案例研究
在实时在线游戏中,玩家输入传送到服务器以及返回结果的延迟时间至关重要。TCP虽然保证了可靠性和传输顺序,但在等待丢失数据的同时,后到达的数据的传递也可能会延迟。UDP不保证传输顺序和到达性,因此要处理需要可靠性的消息,就需要有补充机制。
KCP主要是在UDP之上提供重传和顺序控制的协议。本文将探讨KCP减少延迟的原理,并基于游戏通信案例,梳理连接与认证过程以及消息结构。认证流程和应用程序消息格式可能会根据实现而有所不同。
KCP是如何减少延迟的?
KCP通过使用确认接收和重传的ARQ方式实现可靠传输。通过调整重传策略和更新周期,可以减少丢失后的等待时间,但根据设置的不同,带宽和CPU的使用量可能会增加。
实际性能取决于网络状态和设置。
- 选择性重传: 基于接收确认信息挑选丢失的数据进行重传。TCP在使用SACK时也可以进行选择性重传,因此不能将其视为KCP的独有功能。
- 重传等待时间调整: 在快速模式下,当丢失重复发生时,减小重传等待时间的增长幅度,从而提前恢复。
- 快速重传: 发送方通过观察后续数据包的ACK到达情况来推测前面数据包的丢失。如果满足设定的条件,则在超时前进行重传。
- 短更新周期: 减少更新和传输周期,可以缩短等待ACK和重传数据发出的时间。实际传输时机也会受到应用程序调用周期的影响。
KCP同样按顺序传递数据,因此如果前面的数据丢失,后面的数据仍然会发生等待的现象。其核心不在于消除这种等待,而在于更快地恢复丢失的数据。
连接与认证过程
在与游戏服务器通信之前,需要进行账号认证以及选择要连接的服务器。以下是将通过Web认证获取的Token在游戏服务器中进行验证的简化流程示例。
在该示例中,Web服务器负责认证账号,游戏服务器负责验证Token并启动游戏会话。区分这两个阶段的角色,有助于理解登录之后的通信流程。
KCP本身没有加密或用户认证功能。如果需要安全性,必须配置单独的安全层。Protobuf同样只是序列化数据的格式,仅通过Protobuf表示密钥或Token并不能保证安全传输。
KCP头部与应用程序消息
KCP段由24字节的头部和数据区域组成。下表中从 conv 到 len 的部分是KCP头部,而数据区域内的 PackLen、MsgType、CmdId 等是本案例中涉及的应用程序消息格式。此内部格式并非KCP的通用规范。
表中一格表示1字节。如果应用程序消息很大,可能会被拆分为多个KCP段,因此内部表可以作为展示重组后消息结构的示例来阅读。
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | ||||||||||||||||||||||||||||||||
| conv | cmd | frg | wnd | ||||||||||||||||||||||||||||||||||||
| ts | sn | ||||||||||||||||||||||||||||||||||||||
| una | len | ||||||||||||||||||||||||||||||||||||||
Data: 应用程序消息示例
|
|||||||||||||||||||||||||||||||||||||||
在KCP头部中,conv 是会话标识符,cmd 是命令类型,frg 是分片信息,wnd 是接收窗口大小。ts 表示时间戳,sn 表示段序号,una 表示下一个要接收的序号,len 表示数据区域的长度。
应用程序消息的字段可以按如下方式解析。长度和校验和的计算范围必须确认各自实现的规则。
PackLen:表示消息长度的字段。MsgType:消息类型和标志信息。seqNo:应用程序消息的序号。rpcId:连接远程过程调用的请求与响应的标识符。CmdId:表示用于解析Payload的消息类型的标识符。crc32:用于错误检测的校验和。它不能代替密码学认证。Payload:序列化的消息数据。如果应用了加密,则解密后再进行解析。
使用Protobuf解析Payload
本案例中的Payload包含通过Protocol Buffers (Protobuf)序列化的数据。Protobuf是将结构化数据表示为二进制的格式,使用编号代替字段名称。虽然可以简洁地表示游戏消息,但其大小和处理性能取决于数据结构和实现。
首先,在 .proto 文件中定义消息结构,并使用 protoc 生成符合目标语言的代码。发送方使用此代码序列化数据,接收方则使用相同的消息定义进行反序列化。
例如,可以定义一个包含两个字符串的消息,如下所示:
假设在应用程序中,将 CmdId 与消息类型的对应关系规定如下。该编号不是Protobuf自动分配的值,而是应用程序制定的规则。
将 message 为 Hello World!、name 为 AZA.GG 的消息序列化后,会得到以下字节序列。下面的Payload是加密前的数据。
每个字符串前面都有包含字段编号和数据类型的Tag,以及字符串的字节长度。0a 和 12 分别是第1字段和第2字段的Tag,而 0c 和 06 分别表示字符串的长度12和6。
接收方确认 CmdId 为 11 后,将Payload解析为 HelloWorld。像这样,KCP负责传输和重传,应用程序头部负责区分消息,而Protobuf则负责数据表达。