自定义TCP应用层传输协议
什么是分包与黏包?
分包:指接受方没有接受到一个完整的包,只接受了部分。
黏包:指发送方发送的若干包数据到接收方接收时粘成一包,从接收缓冲区看,后一包数据的头紧接着前一包数据的尾。
PS:因为TCP是面向字节流的,是没有边界的概念的,严格意义上来说,是没有分包和黏包的概念的,但是为了更好理解,也更好来描述现象,我在这里就接着采用这两个名词来解释现象了。我觉得大家知道这个概念就行了,不必细扣,能解决问题就行。
产生分包与黏包现象的原因是什么?
产生分包原因:
可能是IP分片传输导致的,也可能是传输过程中丢失部分包导致出现的半包,还有可能就是一个包可能被分成了两次传输,在取数据的时候,先取到了一部分(还可能与接收的缓冲区大小有关系),总之就是一个数据包被分成了多次接收。
产生黏包的原因:
由于TCP协议本身的机制(面向连接的可靠地协议-三次握手机制)客户端与服务器会维持一个连接(Channel),数据在连接不断开的情况下,可以持续不断地将多个数据包发往服务器,但是如果发送的网络数据包太小,那么他本身会启用Nagle算法(可配置是否启用)对较小的数据包进行合并(基于此,TCP的网络延迟要UDP的高些)然后再发送(超时或者包大小足够)。那么这样的话,服务器在接收到消息(数据流)的时候就无法区分哪些数据包是客户端自己分开发送的,这样产生了粘包;服务器在接收到数据后,放到缓冲区中,如果消息没有被及时从缓存区取走,下次在取数据的时候可能就会出现一次取出多个数据包的情况,造成粘包现象
什么是封包与解包?
TCP/IP 网络数据以流的方式传输,数据流是由包组成,如何判定接收方收到的包是否是一个完整的包就要在发送时对包进行处理,这就是封包技术,将包处理成包头,包体。
包头是包的开始标记,整个包的大小就是包的结束标。
如何自定义协议?
发送时数据包是由包头+数据 组成的:其中包头内容分为包类型+包长度。
接收时,只需要先保证将数据包的包头读完整,通过收到的数据包包头里的数据长度和数据包类型,判断出我们将要收到一个带有什么样类型的多少长度的数据。然后循环接收直到接收的数据大小等于数据长度停止,此时我们完成接收一个完整数据包。
消息头部(包含消息长度)
消息头部不一定只能是一个字节比如0xAA
什么的,也可以包含协议版本号,指令等,当然也可以把消息长度合并到消息消息头部(包含消息长度)
消息头部不一定只能是一个字节比如0xAA
什么的,也可以包含协议版本号,指令等,当然也可以把消息长度合并到消息头部里,唯一的要求是包头长度要固定的,包体则可变长。头部里,唯一的要求是包头长度要固定的,包体则可变长。自定义包头:版本号、消息长度、指令
版本号、消息长度、指令数据类型都是无符号32位整数变量,于是这个消息长度固定4*3=12字节。python没有类型定义,所以一般是使用struct模块生成包头。
1 | import struct |
关于不用自定义结束符分割数据包
有的人会想用自定义的结束符分割每一个数据包,这样传输数据包时就不需要指定长度甚至也不需要包头了。但是如果这样做,网络传输性能损失非常大,因为每一读取一个字节都要做一次if判断是否是结束符。所以建议还是选择消息头部+消息长度+消息正文这种方式。
而且,使用自定义结束符的时候,如果消息正文中出现这个符号,就会把后面的数据截止,这个时候还需要处理符号转义,类比于\r\n的反斜杠。所以非常不建议使用结束符分割数据包。消息正文
消息正文
消息正文的数据格式可以使用Json格式,这里一般是用来存放独特信息的数据。在下面代码中,我使用{"hello","world"}
数据来测试。在Python使用json模块来生成json数据