帧同步会高频次的上报和下发逻辑帧,所以网络方案非常重要,TCP协议的优势在于有序、可靠、有连接,但是由于较为保守的超时重传方案、过大的包头,会给帧同步带来比较高的延迟和较大的数据流量,而UDP是较为精简的网络协议,不保证时序、不保证可靠性、无连接,但是这些都可以自行实现、定制,并且包头较小,可以说是帧同步较为理想的网络协议。但是直接用UDP的话,肯定是不能满足帧同步的需求的,帧同步要求 :相同的输入一定会有相同的输出,所以网络包是不可丢弃的,网络包的时序也要保证一致,所以UDP必须实现最基础的这两个功能才行。
首先是时序,这个比较好解决,给每个网络包都定义一个自增序号,收消息的时候检查序号是否连续,不连续的话,暂时不处理,等序号连续了才处理。为了实现处理的消息有序,我们将消息包头定义为下面这样的数据结构:
* ReliableData 数据包格式 ps:每个data大小不得超过255 只用8位来表示
* | CRC - 16bit | TYPE - 4BIT | IsLittleEndian - 1BIT | Empty - 3BIT | ACK - 16BIT | SEQ - 16BIT | | DataSize - 8bit | Data |
正常的CRC为32位,这里采用16位算法,节约包头大小,然后是4位的包类型,分为可靠包、Ping包、纯ACK包三种类型,1位的大小端标志,3个预留位,ACK为本地已接收到的有序的最新消息的序号(告诉对方在这个序号以前的消息不用再发给我了),SEQ为当前这条消息的序号,DataSize为消息大小,至于为什么只留8位,后面再说;最后的Data位消息体内容。这样设计下来,整个包头为64bit,相对与TCP的近200bit来说,还是节省了不少。
接收到消息的时候处理算法如下:
if (type == (byte)NXUdp.UDPPackageType.ReliableData)
{
int messageCount = 0;
while (true)
{
ushort seq = stream.ReadUShort();
int dataSize = stream.ReadBy