前言

UDP协议是User Datagram Protocol的缩写,它是无连接,不可靠的网络协议。一般使用它进行实时性数据的传输,主要是因为它快,但因为它是不可靠的一种传输协议,所以不可避免的会出现丢包现象。本文就具体讨论导致UDP传输数据包丢失的原因以及一些基本的规避方法:

  • 路由器转发造成的数据包丢失
  • 数据链路层MTU造成的数据包丢失
  • 缺少滑动窗口导致的数据包丢失
  • 收发缓存区大小造成的数据包丢失

(一)数据链路层MTU造成的数据包丢失

1.数据链路层的以太网帧结构

以太网帧结构由四个字段组成,各字段含义为:

  • 目的地址:该地址指的是MAC地址,指该数据要发送至哪里
  • 源地址:MAC地址,填本地MAC地址,指该数据从哪里来
  • 类型:值该数据要交给上层(网络层)的那个协议(IP协议,ARP协议…)
  • 数据:要传输的数据,不过该数据有长度的要求,是在46–1500字节之间,该长度称为最大传输单元即MTU
  • 若数据长度不够46字节,则需要填充内容;若数据长度超过1500字节,则需要分片传输。

2.MTU

MTU maximum transmission unit,最大传输单元,由硬件规定,如以太网的MTU为1500字节,是指在传输数据过程中允许报文的最大长度。

3.MTU对IP协议的影响

  • IP报文在超过MTU后需要分片,接收端需要组装;
  • 一旦分片后的IP报文有一部分丢失,则接收端组装会失败,对于整个IP报文而言相当于传输失败,而IP协议不会负责重新传输数据;
  • 由于MTU影响的IP报文的分片和组装会加大报文丢失的可能性;
  • 报文的分片和组装由IP层自己做,会加大传输的成本,降低性能。

4.MTU对UDP协议的影响

  • UDP协议的报头为固定的20字节;
  • 若UDP数据的长度超过(1500-20)1480字节,则数据在网络层会分片;
  • 数据的分片会加大数据丢失的可能性。

5.MTU对TCP协议的影响

  • TCP协议的报头长度为20–60字节;
  • 若TCP报文的总长度超过1500字节,则数据同样在网络层会分片;
  • TCP单个数据报的最大长度称为最大段尺寸MSS;
  • 在TCP三次握手建立连接的时候,双方会商量传输中MSS的大小;
  • 与UDP相同的是,分片越多数据丢包的可能性越大,可靠性也就越差。

6.实际测试结果

我们可以发现由于MTU的存在,对于传输的报文长度有限制而导致的分片,会增加数据丢包的可能性,也会降低数据传输的性能;所以在网络中传输数据时尽量将数据的大小控制在不造成分片的最大长度。

(二)收发缓存区大小造成的数据截断

每个Socket在Linux中都映射为一个文件,并与内核中两个缓冲区(读缓冲区、写缓冲区)相关联。

或者说,每个Socket拥有两个内核缓冲区。

有时,我们需要修改缓冲区的内核限制的最大值,使其符合我们的实际需求。

1.系统设置

  1. biao@ubuntu:~$ uname -a
  2. Linux ubuntu 4.8.0-36-generic #36~16.04.1-Ubuntu SMP Sun Feb 5 09:39:57 UTC 2017 x86_64 x86_64 x86_64 GNU/Linux
  3. biao@ubuntu:~$ cat /proc/sys/net/core/rmem_max
  4. 212992
  5. biao@ubuntu:~$ cat /proc/sys/net/core/wmem_max
  6. 212992
  7. biao@ubuntu:~$ cat /proc/sys/net/core/rmem_default
  8. 212992
  9. biao@ubuntu:~$ cat /proc/sys/net/core/wmem_default
  10. 212992
  11. biao@ubuntu:~$
  • rmem_max:一个Socket的读缓冲区可由程序设置的最大值,单位字节;
  • wmem_max:一个Socket的写缓冲区可由程序设置的最大值,单位字节;
  • rmem_default:一个Socket的被创建出来时,默认的读缓冲区大小,单位字节;
  • wmem_default:一个Socket的被创建出来时,默认的写缓冲区大小,单位字节;

2.应用程序级修改缓冲区大小

我们可以在程序中动态地修改(通过setsockopt系统调用)持有的有效Socket的读写缓冲区大小。

setsockopt.c

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <unistd.h>
  4. #include <errno.h>
  5. #include <sys/types.h>
  6. #include <sys/socket.h>
  7. int main(int argc, char **argv)
  8. {
  9. if (argc != 2)
  10. {
  11. printf("Usage: %s $RCFBUFSIZE\n", argv[0]);
  12. goto error;
  13. }
  14. int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
  15. if (sockfd < 0)
  16. {
  17. printf("create socket error=%d(%s)!!!\n", errno, strerror(errno));
  18. goto error;
  19. }
  20. // 查看系统默认的socket接收缓冲区大小
  21. int defRcvBufSize = -1;
  22. socklen_t optlen = sizeof(defRcvBufSize);
  23. if (getsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, &defRcvBufSize, &optlen) < 0)
  24. {
  25. printf("getsockopt error=%d(%s)!!!\n", errno, strerror(errno));
  26. goto error;
  27. }
  28. printf("OS default udp socket recv buff size is: %d\n", defRcvBufSize);
  29. // 按照执行参数设置UDP SOCKET接收缓冲区大小
  30. int rcvBufSize = atoi(argv[1]);
  31. if (rcvBufSize <= 0)
  32. {
  33. printf("rcvBufSize(%d) <= 0, error!!!\n", rcvBufSize);
  34. goto error;
  35. }
  36. printf("you want to set udp socket recv buff size to %d\n", rcvBufSize);
  37. optlen = sizeof(rcvBufSize);
  38. if (setsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, &rcvBufSize, optlen) < 0)
  39. {
  40. printf("setsockopt error=%d(%s)!!!\n", errno, strerror(errno));
  41. goto error;
  42. }
  43. printf("set udp socket(%d) recv buff size to %d OK!!!\n", sockfd, rcvBufSize);
  44. // 查看当前UDP SOCKET接收缓冲区大小
  45. int curRcvBufSize = -1;
  46. optlen = sizeof(curRcvBufSize);
  47. if (getsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, &curRcvBufSize, &optlen) < 0)
  48. {
  49. printf("getsockopt error=%d(%s)!!!\n", errno, strerror(errno));
  50. goto error;
  51. }
  52. printf("OS current udp socket(%d) recv buff size is: %d\n",sockfd,curRcvBufSize);
  53. close(sockfd);
  54. exit(0);
  55. error:
  56. if (sockfd >= 0)
  57. close(sockfd);
  58. exit(1);
  59. }

编译 && 运行:

  1. biao@ubuntu:~/test/udp_client/002_udp_rw_buffer$ ./a.out 10240
  2. OS default udp socket recv buff size is: 212992
  3. you want to set udp socket recv buff size to 10240
  4. set udp socket(3) recv buff size to 10240 OK!!!
  5. OS current udp socket(3) recv buff size is: 20480
  6. biao@ubuntu:~/test/udp_client/002_udp_rw_buffer$ ./a.out 40960
  7. OS default udp socket recv buff size is: 212992
  8. you want to set udp socket recv buff size to 40960
  9. set udp socket(3) recv buff size to 40960 OK!!!
  10. OS current udp socket(3) recv buff size is: 81920
  11. biao@ubuntu:~/test/udp_client/002_udp_rw_buffer$ ./a.out 1024
  12. OS default udp socket recv buff size is: 212992
  13. you want to set udp socket recv buff size to 1024
  14. set udp socket(3) recv buff size to 1024 OK!!!
  15. OS current udp socket(3) recv buff size is: 2304
  16. biao@ubuntu:~/test/udp_client/002_udp_rw_buffer$ ./a.out 1024000
  17. OS default udp socket recv buff size is: 212992
  18. you want to set udp socket recv buff size to 1024000
  19. set udp socket(3) recv buff size to 1024000 OK!!!
  20. OS current udp socket(3) recv buff size is: 425984
  21. biao@ubuntu:~/test/udp_client/002_udp_rw_buffer$

我们通过setsockopt系统调用成功地修改了sock的接收缓冲区大小。

但是,代码级的修改缓冲区大小,不是万能的,其受限于系统配置。

可见,我们希望设置接收缓冲区大小为1024*1024B(1MB),但实际并未达到我们的效果,虽然setsockopt成功了!

我们可以通过修改系统运行时的配置(/proc),来动态地“释放权限”,让应用程序可以设置更大的内核读写缓冲区。

3.系统配置级修改缓冲区大小

  1. biao@ubuntu:~/test/udp_client/002_udp_rw_buffer$ su
  2. Password:
  3. root@ubuntu:/home/biao/test/udp_client/002_udp_rw_buffer#
  4. root@ubuntu:/home/biao/test/udp_client/002_udp_rw_buffer# echo 262144 > /proc/sys/net/core/rmem_default
  5. root@ubuntu:/home/biao/test/udp_client/002_udp_rw_buffer# echo 1048576 > /proc/sys/net/core/rmem_max
  6. root@ubuntu:/home/biao/test/udp_client/002_udp_rw_buffer# cat /proc/sys/net/core/rmem_default
  7. 262144
  8. root@ubuntu:/home/biao/test/udp_client/002_udp_rw_buffer# cat /proc/sys/net/core/rmem_max
  9. 1048576
  10. root@ubuntu:/home/biao/test/udp_client/002_udp_rw_buffer# ls
  11. a.out setsockopt.c
  12. root@ubuntu:/home/biao/test/udp_client/002_udp_rw_buffer# ./a.out 1048576
  13. OS default udp socket recv buff size is: 262144
  14. you want to set udp socket recv buff size to 1048576
  15. set udp socket(3) recv buff size to 1048576 OK!!!
  16. OS current udp socket(3) recv buff size is: 2097152

我们在root下,修改了系统运行时的配置:

/proc/sys/net/core/rmem_default

/proc/sys/net/core/rmem_max

    我们设置读缓冲区默认值为256KB,最大值为1MB。程序运行时,我们希望设置读缓冲区为1MB。通过输出信息,我们可以验证,修改/proc中的配置文件,我们使得一个socket默认的读缓冲区为256KB,读缓冲区最大值为1MB。

setsockopt系统调用级设置受限于系统运行时配置,可以通过修改系统配置,使得程序设置更大的读写缓冲区。

4.需要注意的两点:

  • 当系统关机重启时,对/proc的修改,是否依然存在?

    不会。这就比较重要,若服务器由于异常宕机,重启后失去了原有的设置,就有可能导致接收缓冲区过小,出现UDP丢包的可能。

  • 为什么我通过setsockopt设置读缓冲区值为rcvBufSize,但实际getsockopt获取的读缓冲区大小是2*rcvBufSize?

    这个是和源码有关系:

  1. case SO_SNDBUF:
  2. if (val > sysctl_wmem_max)
  3. val = sysctl_wmem_max;
  4. if ((val * 2 ) < SOCK_MIN_SNDBUF)
  5. sk->sk_sndbuf = SOCK_MIN_SNDBUF;
  6. else
  7. sk->sk_sndbuf = val * 2 ;

系统这么做,猜测可能是由于UDP解包封包需要的额外的空间。所以,我称r/wmem_max为:可由程序设置的缓冲区最大值。

5.缓存大小不一致导致UDP数据包丢失分析

《linux 网络编程》书中说,当发送端的缓存大于接收端的缓存时,发送端发送的数据包长度大于接收端缓存时,接收端会造成数据截断的情况,也就是说它只能接收接收端缓存大小的数据,其余会自动丢弃。

但是,我在即在两台Ubuntu设备设备上测试的时候发现,结果并不是这样,如果发送端发送的数据包大于接收端缓存大小的时候,接收端的应用层根本就接收不到数据,一个字节的数据也接收不到。

出现这种情况,我的个人分析是:但发送端的数据包大于接收端的缓存时,这个数据包是通过分片的方式发送到接收端,接收端进行分片包组装的时候,由于空间不够,不能成功组包数据报,最终导致应用层接收不到数据。从这个结果上来也可以看出,发送一个大于MTU的数据包,在接收端应用层只有接收整个数据包和一个字节也接收不到    ,不存在只接收一个分片的数据包和数据截断的可能。

(三)缺少滑动窗口导致的数据包丢失

未完.......

致谢:

博文内容大部分引用自下面文章,真诚感谢~

  1. 《数据链路层——最大传输单元MTU_HXiaoFan的博客-CSDN博客_最大传输单元mtu》
  2. 《UDP:Socket缓冲区大小修改与系统设置_test1280-CSDN博客_udp发送缓冲区大小设置》

​​

---------------------------End---------------------------
长按识别二维码
关注 liwen01 公众号

udp编程及udp常见问题处理的更多相关文章

  1. [C# 网络编程系列]专题七:UDP编程补充——UDP广播程序的实现

    转自:http://www.cnblogs.com/zhili/archive/2012/09/03/2666974.html 上次因为时间的关系,所以把上一个专题遗留下的一个问题在本专题中和大家分享 ...

  2. 转:【专题七】UDP编程补充——UDP广播程序的实现

    上次因为时间的关系,所以把上一个专题遗留下的一个问题在本专题中和大家分享下,本专题主要介绍下如何实现UDP广播的程序,下面就直接介绍实现过程和代码以及运行的结果. 一.程序实现 UDP广播程序的实现代 ...

  3. 专题七:UDP编程补充——UDP广播程序的实现

    一.程序实现 UDP广播程序的实现代码: using System; using System.Net; using System.Net.Sockets; using System.Text; us ...

  4. 【网络编程1】网络编程基础-TCP、UDP编程

    网络基础知识 网络模型知识 OSI七层模型:(Open Systems Interconnection Reference Model)开放式通信系统互联参考模型,是国际标准化组织(ISO)提出的一个 ...

  5. 37 - 网络编程-UDP编程

    目录 1 UDP协议 2 UDP通信流程 3 UDP编程 3.1 构建服务端 3.3 常用方法 4 聊天室 5 UDP协议应用 1 UDP协议 UDP是面向无连接的协议,使用UDP协议时,不需要建立连 ...

  6. Linux学习四:UDP编程(上)

    关于UDP和TCP对比优缺,这里就不说了. 使用UDP代码所掉用的函数和用于TCP的函数非常类似,这主要因为套接口库在底层的TCP和UDP的函数上加了一层抽象,通过这层抽象使得编程更容易,但失去了一些 ...

  7. JAVA--网络编程(UDP)

    上午给大家简单介绍了一下TCP网络通信的知识,现在就为大家补充完整网络编程的知识,关于UDP的通信知识. UDP是一种不可靠的网络协议,那么还有什么使用价值或必要呢?其实不然,在有些情况下UDP协议可 ...

  8. [C# 网络编程系列]专题六:UDP编程

    转自:http://www.cnblogs.com/zhili/archive/2012/09/01/2659167.html 引用: 前一个专题简单介绍了TCP编程的一些知识,UDP与TCP地位相当 ...

  9. Socket编程实践(12) --UDP编程基础

    UDP特点 无连接,面向数据报(基于消息,不会粘包)的传输数据服务; 不可靠(可能会丢包, 乱序, 反复), 但因此普通情况下UDP更加高效; UDP客户/服务器模型 UDP-API使用 #inclu ...

  10. 【Socket编程】通过Socket实现UDP编程

    通过Socket实现UDP编程 UDP通信: 1.UDP协议(用户数据报协议)是无连接.不可靠.无序的. 2.UDP协议以数据报作为数据传输的载体. 3.使用UDP进行数据传输时,首先需要将要传输的数 ...

随机推荐

  1. idea2020下载、安装、破解、配置

    idea2020下载.安装.破解.配置 idea2020下载 [推荐]官方下载地址:https://www.jetbrains.com/idea/download/other.html 进入后往下找, ...

  2. 浏览器层面优化前端性能(2):Reader引擎线程与模块分析优化点

    Reader 引擎线程与模块分析 首先是网页内容,加载完输入到HTML解释器,解释后构成DOM树,这期间如果遇到JavaScript代码就交给JavaScript引擎去处理,如果网页中包含CSS,就交 ...

  3. 火山引擎DataLeap的Data Catalog系统搜索实践 (上)

    更多技术交流.求职机会,欢迎关注字节跳动数据平台微信公众号,回复[1]进入官方交流群 摘要 火山引擎大数据研发治理套件 DataLeap的Data Catalog系统通过汇总和组织各种元数据,解决了数 ...

  4. 年搜索量超 7 亿次背后:这款 APP 用火山引擎 DataTester 完成“数据驱动”

    更多技术交流.求职机会,欢迎关注字节跳动数据平台微信公众号,回复[1]进入官方交流群 双十一刚过,双十二在即,随着线上营销玩法的层出不穷,各平台之间的价格逐渐"内卷".消费者对跨平 ...

  5. 火山引擎 VeDI 推出这款产品 助力企业实现以“人”为中心的数据洞察

    更多技术交流.求职机会,欢迎关注字节跳动数据平台微信公众号,回复[1]进入官方交流群 CDP(Customer Data Platform,客户数据平台)市场将迎来新一轮的高速增长. 国际数据公司(以 ...

  6. 聚焦企业数据生命周期全链路 火山引擎数智平台 VeDI 发布《数据智能知识图谱》

    更多技术交流.求职机会,欢迎关注字节跳动数据平台微信公众号,回复[1]进入官方交流群 近日,火山引擎数智平台(VeDI)正式发布<数据智能知识图谱>(以下简称「图谱」),内容覆盖了包括数据 ...

  7. Kubernetes(K8S) yaml 介绍

    使用空格做为缩进 缩进的空格数目不重要, 只要相同层级的元素左侧对齐即可 低版本缩进时不允许使用 Tab 键, 只允许使用空格 使用#标识注释, 从这个字符一直到行尾, 都会被解释器忽略 --- 使用 ...

  8. 【快速阅读二】从OpenCv的代码中扣取泊松融合算子(Poisson Image Editing)并稍作优化

    泊松融合我自己写的第一版程序大概是2016年在某个小房间里折腾出来的,当时是用的迭代的方式,记得似乎效果不怎么样,没有达到论文的效果.前段时间又有网友问我有没有这方面的程序,我说Opencv已经有了, ...

  9. 用 Python 开发了一个 PDF 抽取Excel表格的小工具

    大家好哇 从 PDF 里 copy 表格时,粘贴出来后格式都是错乱的.这麻烦事交给 Python 再合适不过里,我开发了一个从 PDF 抽取表格另存为 Excel 文件的应用,我把它部到 huggin ...

  10. 【JAVA基础】加密算法

    加密算法 MD5三次加密 package com.cy.store.service.impl; import com.cy.store.entity.User; import com.cy.store ...