关于通讯协议的定义与封包解包处理技术的计论 ( 积分: 50 )

  • 主题发起人 主题发起人 QSmile
  • 开始时间 开始时间
Q

QSmile

Unregistered / Unconfirmed
GUEST, unregistred user!
关于通讯协议的定义与封包解包处理技术的计论

在做通讯处理时通常要自己定义通讯协议.

近来正在做一个东东,也做了个协议.

协议定义如下:
(数据低位在前)

head cmd len Paramter CheckSum
开始头 命令 总长度 参数 CRC16 (前面所有字节)
word word word 可变0-4096 word (低位在前)

请求与回复都用相同的包结构. word 都是低位在前
回复时把 cmd 最高位置 1.
而实际使用时, cmd 最高四位用着它用. 这样也就 4096 个命令

参数部分的内容,根据 命令的不同而不同.

通讯程序中,封包都来得容易, 问题多在解包时
我已基本实现该协议, 与大家分析讨论一下

1.帧的定义比较完善. 有帧头, 总长度, 与 CRC ,能确认帧的完整性, 通讯中可能会错过一两个包,但不会因为错了几个包, 通讯完全中断

2. 粘包与断包的处理
这个问题相当的麻烦. 我发一个命令, 对方可能分别发N个回复. 而这几个回复,有时会粘到一起,分别回来我这里. 还有可能中间有几个包是个断开了的.分几次收到. 而且我的帧头也是两个字节的. 帧头本身也可能会被断开. 而且我的第5,6个字节才表示包的总长度.也就是说至少我要读到一个包的前6个字节,才能判断.

不知道这里有多少人在自己做通讯协议,
如果有我希望能看看你的协议框架是如何定义的, 也许有比我更好的方法, 大家讨论一下, 共同进步
 
很多可以参考的,tcp/ip/http等等,甚至xml/soap都可以参考!
 
看看这个,串口通讯协议:

STX (1 byte)
Application ID (1 byte)
Total Application Message Length
(2 bytes)
Application Message
(Total Application Message Length bytes)
FCS (1 byte)
ETX (1 byte)

最重要的只有四个:
STX (1 byte):帧头;
Total Application Message Length:长度;
FCS (1 byte):校验;
ETX (1 byte):帧尾;

对比你的定义,只要加上帧尾就可以很好的解决你的问题,不过建议帧头用一个byte,长度放在帧头后。
1的要求完全满足;
2.粘包与断包的处理:顺序的读缓存中的字节;遇到帧头,读长度;读完长度读帧尾,如果正确,进行校验,如果正确,那么恭喜你,这个帧对了。如果任何错误,从刚才遇到帧头的下一个字节开始读,重复进行,直到缓存中的字节读完。
 
感觉帧尾的用处不大
 
很多网游都是 长度+CMD+数据 的格式的。 长度和CMD的长度都是固定的,长度指的是这个数据包中 CMD+数据 的长度。

接收的时间,首先接收接收 X字节的长度,因为TCP的原因,这个过程有可能会分2次接收,所以你要设置一个计数器, 接收了多少个字节,并在接收事件中,调用recv接收剩余长度的字节, 直到接完 长度 后, 申请长度为“长度”的缓冲区, 之后接收长度为“长度”的数据。 这样就完成一次接收一个数据包过程。之后传递给数据包处理过程处理之。

之后重置一些变量值, 用以接收下一个数据包。
 
正巧前2天写了一套协议 :) 主要用于udp
头|程序标示|标志|命令号|总长度|<*n 二进制数据>|数据
头是固定的 如果是tcp则可以不用。
程序标示 标示是哪个程序发出的数据
标志 让接收段知道这此接收的数据 是否需要解压/解密
命令号 是什么命令
总长度 本包的总数据量
*+数字用来标示二进制数据 (使用一个函数可以非常容易的根据这个标致获取 比如对象,数组、结构等二进制的数据)
数据 普通简单数据用字符串方式表示

优点 可以有通用的发送和接收类,程序只关心逻辑处理,可以非常容易的发送和还原对象等复杂信息。发送的信息由|号分割 可以省去定义很多Packed record.
 
楼上的兄弟,能不能给些代码提示下.不是很明白
 
代码在公司里没在家 ,大概说下功能和设计思想。写了一套 基类 用来发送和接受数据,几个接口函数 用来添加数据头 自动除掉判断和数据头,当然也包括自动压缩/解压或加密解密数据。 最后函数会把处理过后的数据流以需要的部分返回出来。
还有几个函数就是用来给添加二进制数据参数 和获取二进制数据参数。用来方便的传输和定位不同机器/不同程序内的同类对象/结构体。而不用为他们定义很多结构,接收后在分别付值。
有了这套东东,写任何一种通讯程序都可以很快速的编写代码,专心考虑逻辑处理,而不是数据的发送接收与分解 :)
 
很多网游都是 长度+CMD+数据 的格式的。

--------------
??? 好象我在别处也看到了这个说法.

但我有点问题. 如果通讯过程中出现了点问题. 有数据丢失.那整个通讯就完全中断了. 这样不太好吧?
 
只是倒是 TCP 层已经保证了数据的完整性. 如果真有网游在用这样的协方那也证明这种方法是可行的. 存在就是全理吧

mmzmagic,倒是做通讯的高手.

只是你这个协议, 能不能表 字节长度来表示一下?
头|程序标示|标志|命令号|总长度|<*n 二进制数据>|数据

如果我的理解没错的话, 这里的 | 是指字符 &quot;|&quot; 吧. 如果这样.我想你的数据都是用 ASCII 来传的吧. 那如果你的数据里有字符 &quot;|&quot; 呢? 如何处理?
 
QSmile 理解錯了吧。
 
to QSmile:
你说的对 &quot;|&quot; 是分割符号,至于传输的数据以二进制的方式或者是字符串传都可以。
如果数据里包含&quot;|&quot; 就向写delphi代码字符串里包含 '一样 可以通过转义字符的方法来解决,当然也可以避免传输的数据里包含&quot;|&quot;
数据结构如此,无论怎么传输,目的只是以字符串形式来处理数据更容易。
这个协议是变长的没有固定字节长度,各个段的大小是根据数据而变动的,但是必须保证各个段是必须存在,这样接收端才能根据前段数据头接收后段所需的数据,这也是使用&quot;|&quot;分割符的一个优势。我写过很多通讯程序,刚开始时几乎为每一步通讯定义1个或者2个结构体用来传输。哎 那个累呀 呵呵。现在的这个协议是我写了那么几年通讯程序积累出来的一点经验。已经正式用于项目内了,虽然还不是那么完善但用起来感觉很不错,等完善到一定程度了,我把它发布供大家使用吧:)
 
如果你用的是TCP协议那么你的数据包协议有些有问题的:
协议定义如下:
(数据低位在前)

head cmd len Paramter CheckSum
开始头 命令 总长度 参数 CRC16 (前面所有字节)
word word word 可变0-4096 word (低位在前)
你的问题在于,如果你使用的是TCP协议,那么后面的效验就没有必要存在,在TCP数据包的包头中本身就含有CRC数据效验。

我觉得修改成这样比较好:
len Data
长度 数据
char(4) char
其中lan就是你需要发送的数据的长度,这个字段的功能就是用在你以后处理数据粘包时使用。Data数据就是你需要发送的数据,其中长度你不需要限制,因为已经有前面的Lan字段给你处理了。
下来给你说怎么使用这个数据包。
1:接受到数据以后首先分解出前面的char(4)数据,将其转换成一个Integer数据。这样你就可以知道这次发送数据的长度。
2:使用这个得到的数据长度,截取相应的数据长度。这就是你的一个数据整包。

其它的数据包一次循环。
注意:由于粘包的关系,数据长度这4个char有可能被分解成两个数据包中。所以你要对这每个数据包进行数据粘包处理。
 
我用的是 ID+长度+Data
先读取这个ID 判断是不是我们自己定义的数据
是->读取长度(DWORD)->再去读指定长度的数据->直到数据长度够了->成功返回
 
我也是按楼上的办法处理的。补充一点,有时候系统会给你比你长度多一点字节的数据(后面的包粘联了),这点多出来的字节数也要保存,用在后来的包获取上[:)]
 
拖个板凳坐起听。对协议这个很感兴趣,可以提高自己的网络方面的开发能力哦。
 
to :fxh7622,

TCP 头里是有一个 CheckSum ,但不是 CRC 的,我前段时间才看了 TCP 的 RFC.不相信,你可以看看 TCP 协议说明。

那个 CheckSum 的问题我也注意到了。 对于 TCP 而言也许作用不大。

关于你说的那个方式, 我也提示了我的看法,就是如果中途有数据丢失了,那整个通讯就完全的中断了。或是错误的结果。

我是想做个通用点的协议。

综合以上的建议与意见,我也认为可以把它再改简单点
如:
帧头 1 Byte
长度 2 Byte
命令 2 Byte
Data n Byte
CheckSum 2 Byte
帧尾 1 Byte


Data 的内容以 命令的不同而不同。

这样差不多完整了。
 
n年来,我都是以记录包方式处理的,如下:
TNetPkt=packed record // 1024 bytes
VerifyCode:DWORD;//校验码 =$AAAAAAAA
PktSize:DWORD;
MsgID:DWORD;//消息类型
Value:integer;//属性或返回值
Flag:integer;//其它存储信息
case integer of
0:(____FillBuf:array[0..1023-4*5] of char);//无用,填充包长
1:(LogPkt:TLogPkt);//日志信息包
2:(aaa:Taaa);
3:(bbb:Tbbb);
end;
方便,快捷,好处理,可读性强,想怎么读就怎么读,想怎么发就怎么发,想发定长的就发定长的,想发变长的就发变长的
 
哈哈,不就是我一开始所说的吗
 
同意,粘包的最简单的处理就是用个缓存,将收到的内容先缓存起来,收到数据包,检查是否一个完整的包,并返回,否则等待下次数据的到来。连接断开后,需要将缓冲包Clear,不然缓冲包的数据就会有问题。这是很简单有效的处理方法。
另:TCP中,一般自己不弄checksum吧。反正我是不用。
一般TCP定义协议就是: 包头+包体,包头定义长度,命令,包体是根据包头的命令变长。
这样处理就简单。
 

Similar threads

后退
顶部