自定义通信协议的问题
- 一、串口
- 1、通信分为网络通信和串口通信
- 2、协议格式
- 3、协议设计
- 4、代码实现
- 二、网口
- 1、TCP粘包与拆包
- * 包的划分
- * 出现TCP粘包的原因
- * 粘包与拆包的几种情况
- * 常见的粘包与拆包解决方案
- 2、为什么UDP没有粘包?
- 3、TCP、UDP数据发送区别
- 4、为什么要等待2MSL
- 5、TCP为什么会丢包?
- 6、如何解决TCP丢包问题?
- 7、UDP丢包的原因
- 8、解决UDP丢包的问题
一、串口
1、通信分为网络通信和串口通信
a) 网络通信只用于文件数据的传输,网络连接中,PC为服务端,控制器为客户端;只有一个包接收处理完成命令:“0xFF 0x55”
b) 串口通信主要完成命令、状态、数据等传输,后续说的协议主要是针对串口协议
2、协议格式
【注】:所有多字节数据,协议中在低字节位置的字节为该数据的低位,如2字节数据,在协议中有Data[0]、Data[1]组成,那么,该数据value为:
Value = (((u16) Data[1]) << 8) + Data[0];
【注】:文档中所有数据,如开头为0x的都为16进制数据,否则为十进制数据。
3、协议设计
主功能ID说明:
- 操作控制类的主命令范围:0x01~0x3F
- 参数设置或获取类的主命令范围:0x40~0x7F
- 系统状态类的主命令范围:0x80~0xBF
- 调试类的主命令范围:0xE0~0xEF
子命令ID说明:
- MCU上传的子命令的最高位为1
- MCU上传每个主命令下的子命令取值范围为:0x81~0xFE
- PC下发的子命令的最高位为0
- PC下发每个主命令下的子命令取值范围为:0x01~0x7E
应答命令说明:
- 应答的主命令跟接收到的命令一致
- 应答的子命令的低七位跟接收到的一致,最高位跟根据上MCU应答还是PC应答决定,MCU应答则最高位为1,PC应答则最高位为0
- 需要由超时应答机制,一旦超时需要重新发送一次,最多重复发送3次;超时时间为500ms
4、代码实现
1、消息数据发送
- 通过串口直接发送每个字节
- 通过消息队列发送,用一个buf装下消息,然后“打包”到消息队列,通过FIFO发送出去
- 用“结构体”代替“数组SendBuf”方式
2、消息数据接收(中断、轮询)
- 常规中断接收
- 增加超时检测:用多余的MCU定时器做一个超时计数的处理,接收到一个数据,开始计时,超过1ms没有接收到下一个数据,就丢掉这一包(前面接收的)数据。
- 接收也可以用结构体的方式
二、网口
1、TCP粘包与拆包
* 包的划分
TCP是面向字节流的协议,就是没有界限的一串数据,本没有“包”的概念,“粘包”和“拆包”一说是为了有助于形象地理解这两种现象。
操作系统在发送TCP数据时,会通过缓冲区来进行优化,例如缓冲区为1024个字节大小。
- 如果一次请求发送的数据量比较小,没达到缓冲区大小,TCP则会将多个请求合并为同一个请求进行发送,这就形成了粘包问题。
- 如果一次请求发送的数据量比较大,超过了缓冲区大小,TCP就会将其拆分为多次发送,这就是拆包。
* 出现TCP粘包的原因
* 粘包与拆包的几种情况
1、正常的理想情况,两个包恰好满足TCP缓冲区的大小或达到TCP等待时长,分别发送两个包;
2、粘包:两个包较小,间隔时间短,发生粘包,合并成一个包发送;
3、拆包:一个包过大,超过缓存区大小,拆分成两个或多个包发送;
4、拆包和粘包:Packet1过大,进行了拆包处理,而拆出去的一部分又与Packet2进行粘包处理。
* 常见的粘包与拆包解决方案
(1)发送方
关闭Nagle算法,使用TCP_NODELAY选项来关闭算法。
(2)接收方
接收方没有办法来处理粘包现象,只能将问题交给应用层来处理。
(3)应用层
1、发送端将每个包都封装成固定的长度,比如100字节大小。如果不足100字节,可通过补0来进行填充到指定长度。对于高并发、大流量的系统来说,每个数据包都不应该传输多余的数据(所以补齐的方式不可取)
2、发送端在每个包的末尾使用固定的分隔符,例如\r\n。如果发生拆包需要等待多个包发送过来之后再找到其中的\r\n进行合并;例如,FTP协议
3、将消息分为头部和消息体,头部中保存整个消息的长度,只有读到足够长的消息之后才算读到了一个完整的消息;(最常用)
2、为什么UDP没有粘包?
粘包与拆包问题在数据链路层、网络层以及传输层都有可能发生。一般发生在传输层,由于UDP有消息保护边界,接收方一次只接受一条独立的信息,不会发生粘包拆包问题,因此粘包拆包问题只发生在TCP协议中。
在UDP协议的接收端,采用了链式结构来记录每一个到达的UDP包,这样接收端应用程序一次recv只能从socket接收缓冲区中读出一个数据包。也就是说,发送端send了几次,接收端必须recv几次(无论recv时指定了多大的缓冲区)。
3、TCP、UDP数据发送区别
举个例子:有三个数据包,大小分别为2k、4k、6k
- 采用UDP发送的话,不管接受方的接收缓存有多大,我们必须要进行至少三次以上的发送才能把数据包发送完,
- 使用TCP协议发送的话,我们只需要接受方的接收缓存有12k的大小,就可以一次把这3个数据包全部发送完毕。
4、为什么要等待2MSL
1、一定出现在【主动关闭连接请求端】----TIME_WAIT
2、保证最后一个ACK能成功被对端接收。(等待期间,对端没有收到我的ACK,对端会再次发送FIN请求)
3、保证这次连接的重复数据段从网络中消失,实现TCP全双工连接的可靠释放
5、TCP为什么会丢包?
1、TCP是基于不可靠的网络实现可靠传输,肯定会存在丢包问题
2、如果在通信过程中,发现缺少数据或者丢包,那么最大的可能性是程序发送过程或者接收过程出现问题。
6、如何解决TCP丢包问题?
为了满足TCP协议不丢包,TCP协议有如下规定:
1、数据分片:发送端对数据进行分片,接收端要对数据进行重组,由TCP确定分片的大小并控制分片和重组。
2、到达确认:接收端接收到分片数据时,根据分片数据号向发送端发送一个确认
3、超时重发:发送方在发送分片时设置超时定时器,如果在定时器超时之后没有收到相应的确认,重发分片数据。
4、滑动窗口:TCP连接的每一方的接收缓冲空间固定,接收端只允许另一端发送接收端缓冲区所能接纳的数据,TCP在滑动窗口的基础上提供流量控制,防止较快主机致使较慢主机的缓冲区溢出。
5、失序处理:作为IP数据报来传输的TCP分片到达时可能会失序,TCP将对收到的数据进行重新排序,将收到的数据以正确的顺序交给应用层。
6、重复处理:作为IP数据报来传输的TCP分片会发生重复,TCP的接收端必须丢弃重复的数据。
7、数据校验:TCP将保持它首部和数据的校验和,这是一个端到端的校验和,目的是检测数据在传输过程中的任何变化。如果收到分片的校验或有差错,TCP将丢弃这个分片,并不确认收到此报文段导致对端超时并重发。
7、UDP丢包的原因
- 接收端处理时间过长导致丢包
调用recv方法接收端收到数据后,处理数据花了一些时间,处理完后再次调用recv方法,在这二次调用间隔里,发过来的包可能丢失。对于这种情况可以修改接收端,将包接收后存入一个缓冲区,然后迅速返回继续recv。 - 发送的包巨大丢包
虽然send方法会帮你做大包切割成小包发送的事情,但包太大也不行。例如超过50K的一个udp包,不切割直接通过send方法发送也会导致这个包丢失。这种情况需要切割成小包再逐个send。 - 发送的包较大,超过接收者缓存导致丢包
包超过mtu size数倍,几个大的udp包可能会超过接收者的缓冲,导致丢包。这种情况可以设置socket接收缓冲。以前遇到过这种问题,我把接收缓冲设置成64K就解决了。 - 发送的包的频率太快
虽然每个包的大小都小于mtu size 但是频率太快,例如40多个mut size的包连续发送中间不sleep,也有可能导致丢包。这种情况也有时可以通过设置socket接收缓冲解决,但有时解决不了。所以在发送频率过快的时候还是考虑sleep一下吧。 - 局域网内不丢包,公网上丢包
这个问题我也是通过切割小包并sleep发送解决的。如果流量太大,这个办法也不灵了。总之udp丢包总是会有的,如果出现了用我的方法解决不了,还有这个几个方法: 要么减小流量,要么换tcp协议传输,要么做丢包重传的工作。
8、解决UDP丢包的问题
-
发送频率过高导致丢包
很多人会不理解发送速度过快为什么会产生丢包,原因就是UDP的SendTo不会造成线程阻塞,也就是说,UDP的SentTo不会像TCP中的SendTo那样,直到数据完全发送才会return回调用函数,UDP不保证当执行下一条语句时数据是否被发送。(SendTo方法是异步的)这样,如果要发送的数据过多或者过大,那么在缓冲区满的那个瞬间要发送的报文就很有可能被丢失。至于对“过快”的解释,作者这样说:“A few packets a second are not an issue; hundreds or thousands may be an issue.”(一秒钟几个数据包不算什么,但是一秒钟成百上千的数据包就不好办了)。 要解决接收方丢包的问题很简单,首先要保证程序执行后马上开始监听(如果数据包不确定什么时候发过来的话),其次,要在收到一个数据包后最短的时间内重新回到监听状态,其间要尽量避免复杂的操作(比较好的解决办法是使用多线程回调机制)。
-
报文过大丢包
至于报文过大的问题,可以通过控制报文大小来解决,使得每个报文的长度小于MTU。以太网的MTU通常是1500 bytes,其他一些诸如拨号连接的网络MTU值为1280 bytes,如果使用speaking这样很难得到MTU的网络,那么最好将报文长度控制在1280 bytes以下。
-
发送方丢包
发送方丢包:内部缓冲区(internal buffers)已满,并且发送速度过快(即发送两个报文之间的间隔过短);
-
接收方丢包:Socket未开始监听; 虽然UDP的报文长度最大可以达到64 kb,但是当报文过大时,稳定性会大大减弱。这是因为当报文过大时会被分割,使得每个分割块(翻译可能有误差,原文是fragmentation)的长度小于MTU,然后分别发送,并在接收方重新组合(reassemble),但是如果其中一个报文丢失,那么其他已收到的报文都无法返回给程序,也就无法得到完整的数据了。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)