一个UDP包的固定分包丢包问题

  1. 现象

    发送一个8K的UDP包,由于MTU为1500,那么包被分为6个包,但是出现OFFSET在4440的第四个分包会丢包,并不是所有的8K UDP包会丢包,只有在那个包会稳定丢分包。

1.两台服务器直连并不会出现,经过交换机出现。说明是交换机有问题。
2.将出问题的分包切片放到不同的位置,不同的位置开始丢包。说明和报内容相关。
和报文内容强相关,可能是报文的内容被当做特定字节进行了解析,推测是网络芯片或交换机芯片的问题。
因为直连测试没有出现,所以推断是交换机的芯片BUG。
然后调测调测交换机的输入和输出包数量,发现是交换机层面丢包了。
继续跟踪,在思科官方BUGLIST中寻找线索。
最后确认是交换机的芯片。

https://bst.cloudapps.cisco.com/bugsearch/bug/CSCvc03558/?referring_site=bugquickviewclick
芯片误将IP HEADER后面中的第三、四个字节变成了端口3784(对应16进制0X0EC8),将包给吃掉了。
下面以网络相关的原理和工具。下面将本次涉及的一些原理处理后推送奉上。

  1. UDP接收原理

    操作系统的UDP接收流程如下:收到一个UDP包后,验证没有错误后,放入一个包队列中,队列中的每一个元素就是一个完整的UDP包。当应用程序通过recvfrom()读取时,OS把相应的一个完整UDP包取出,然后拷贝到用户提供的内存中,物理用户提供的内存大小是多少,OS都会完整取出一个UDP包。如果用户提供的内存小于这个UDP包的大小,那么在填充完内存后,UDP包剩余的部分就会被丢弃,以后再也无法取回。

  2. 关于网络交换机

    有核心层交换机、分部层交换机和接入层交换机。
    核心层交换机一般与路由器连接,放在管理间,核心层交换机可以说是交换机的网关 。分布层交换机与核心层交换机连接,一般放在设备间。而接入层交换机是连接终端设备的。三者之间是一种级联的关系。

  3. TCP分包

    TCP是以段(Segment)为单位发送数据的,建立TCP链接后,有一个最大消息长度(MSS)。如果应用层数据包超过MSS,就会把应用层数据包拆分,分成两个段来发送。这个时候接收端的应用层就要拼接这两个TCP包,才能正确处理数据。
    相关的,路由器有一个MTU( 最大传输单元),一般是1500字节,除去IP头部20字节,留给TCP的就只有MTU-20字节(20个自己而给IP报头)。所以一般TCP的MSS为MTU-20-20(TCP报头20个字节)=1460字节。
    当应用层数据超过1460字节时,TCP会分多个数据包来发送。

  4. MTU

    以太网(Ethernet)数据帧的长度必须在46-1500字节之间,这是由以太网的物理特性决定的。这个1500字节被称为链路层的MTU(最大传输单元).并不包括链路层的首部和尾部的18个字节.
    这个1500字节是网络层IP数据报的长度限制。因为IP数据报的首部为20字节,所以IP数据报的数据区长度最大为1480字节。
    这个1480字节就是用来放TCP传来的TCP报文段或UDP传来的UDP数据报的。因为UDP数据报的首部8字节,所以UDP数据报的数据区最大长度为1472字节。
    只有第一个分包包含udp首部,ip分包对于ip首部后面的数据都是透明的。如果flag为0说明后面没有分包了
    payload数据(加上udp首部)必须是8字节的整数倍。
    如果MTU设置成1600,那么UDP包大小超过1600后,第一个分包大小并不是1600而是1596。因为1600-20-8=1572不能被8整除,而1596-20-8=1568是可以的。
    MTU是链路层中的网络对数据帧的一个限制,在以太网中,MTU为1500个字节。一个IP数据报在以太网中传输,如果长度大于该MTU值,就要进行分片传输,使得每片数据报的长度小于MTU。分片传输的IP数据报不一定按序到达,但IP首部中的信息能让这些数据报片按序组装。IP数据报的分片与重组是在网络层进完成的。
    UDP数据报不会自己进行分段,当长度超过了MTU时,会在网络层进行IP分片。同样,ICMP(在网络层中)同样会出现IP分片情况。TCP会分段,不用IP来分。

  5. UDP主要丢包原因

    1、接收端处理时间过长导致丢包:调用recv方法接收端收到数据后,处理数据花了一些时间,处理完后再次调用recv方法,在这二次调用间隔里,发过来的包可能丢失。对于这种情况可以修改接收端,将包接收后存入一个缓冲区,然后迅速返回继续recv。
    2、发送的包巨大丢包:虽然send方法会帮你做大包切割成小包发送的事情,但包太大也不行。例如超过50K的一个udp包,不切割直接通过send方法发送也会导致这个包丢失。这种情况需要切割成小包再逐个send。
    3、发送的包较大,超过接受者缓存导致丢包:包超过mtu size数倍,几个大的udp包可能会超过接收者的缓冲,导致丢包。这种情况可以设置socket接收缓冲。以前遇到过这种问题,我把接收缓冲设置成64K就解决了。
    int nRecvBuf=321024;//设置为32K
    setsockopt(s,SOL_SOCKET,SO_RCVBUF,(const char
    )&nRecvBuf,sizeof(int));
    4、发送的包频率太快:虽然每个包的大小都小于mtu size 但是频率太快,例如40多个mut size的包连续发送中间不sleep,也有可能导致丢包。这种情况也有时可以通过设置socket接收缓冲解决,但有时解决不了。所以在发送频率过快的时候还是考虑sleep一下吧。
    5、局域网内不丢包,公网上丢包。这个问题我也是通过切割小包并sleep发送解决的。如果流量太大,这个办法也不灵了。总之udp丢包总是会有的,如果出现了用我的方法解决不了,还有这个几个方法: 要么减小流量,要么换tcp协议传输,要么做丢包重传的工作。

标签: none

添加新评论