前言

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.系统设置

biao@ubuntu:~$ uname -a
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
biao@ubuntu:~$ cat /proc/sys/net/core/rmem_max
212992
biao@ubuntu:~$ cat /proc/sys/net/core/wmem_max
212992
biao@ubuntu:~$ cat /proc/sys/net/core/rmem_default
212992
biao@ubuntu:~$ cat /proc/sys/net/core/wmem_default
212992
biao@ubuntu:~$
  • rmem_max:一个Socket的读缓冲区可由程序设置的最大值,单位字节;
  • wmem_max:一个Socket的写缓冲区可由程序设置的最大值,单位字节;
  • rmem_default:一个Socket的被创建出来时,默认的读缓冲区大小,单位字节;
  • wmem_default:一个Socket的被创建出来时,默认的写缓冲区大小,单位字节;

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

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

setsockopt.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h> int main(int argc, char **argv)
{
if (argc != 2)
{
printf("Usage: %s $RCFBUFSIZE\n", argv[0]);
goto error;
} int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd < 0)
{
printf("create socket error=%d(%s)!!!\n", errno, strerror(errno));
goto error;
} // 查看系统默认的socket接收缓冲区大小
int defRcvBufSize = -1;
socklen_t optlen = sizeof(defRcvBufSize);
if (getsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, &defRcvBufSize, &optlen) < 0)
{
printf("getsockopt error=%d(%s)!!!\n", errno, strerror(errno));
goto error;
}
printf("OS default udp socket recv buff size is: %d\n", defRcvBufSize); // 按照执行参数设置UDP SOCKET接收缓冲区大小
int rcvBufSize = atoi(argv[1]);
if (rcvBufSize <= 0)
{
printf("rcvBufSize(%d) <= 0, error!!!\n", rcvBufSize);
goto error;
}
printf("you want to set udp socket recv buff size to %d\n", rcvBufSize);
optlen = sizeof(rcvBufSize);
if (setsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, &rcvBufSize, optlen) < 0)
{
printf("setsockopt error=%d(%s)!!!\n", errno, strerror(errno));
goto error;
}
printf("set udp socket(%d) recv buff size to %d OK!!!\n", sockfd, rcvBufSize); // 查看当前UDP SOCKET接收缓冲区大小
int curRcvBufSize = -1;
optlen = sizeof(curRcvBufSize);
if (getsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, &curRcvBufSize, &optlen) < 0)
{
printf("getsockopt error=%d(%s)!!!\n", errno, strerror(errno));
goto error;
}
printf("OS current udp socket(%d) recv buff size is: %d\n",sockfd,curRcvBufSize); close(sockfd); exit(0); error:
if (sockfd >= 0)
close(sockfd);
exit(1);
}

编译 && 运行:

biao@ubuntu:~/test/udp_client/002_udp_rw_buffer$ ./a.out 10240
OS default udp socket recv buff size is: 212992
you want to set udp socket recv buff size to 10240
set udp socket(3) recv buff size to 10240 OK!!!
OS current udp socket(3) recv buff size is: 20480
biao@ubuntu:~/test/udp_client/002_udp_rw_buffer$ ./a.out 40960
OS default udp socket recv buff size is: 212992
you want to set udp socket recv buff size to 40960
set udp socket(3) recv buff size to 40960 OK!!!
OS current udp socket(3) recv buff size is: 81920
biao@ubuntu:~/test/udp_client/002_udp_rw_buffer$ ./a.out 1024
OS default udp socket recv buff size is: 212992
you want to set udp socket recv buff size to 1024
set udp socket(3) recv buff size to 1024 OK!!!
OS current udp socket(3) recv buff size is: 2304
biao@ubuntu:~/test/udp_client/002_udp_rw_buffer$ ./a.out 1024000
OS default udp socket recv buff size is: 212992
you want to set udp socket recv buff size to 1024000
set udp socket(3) recv buff size to 1024000 OK!!!
OS current udp socket(3) recv buff size is: 425984
biao@ubuntu:~/test/udp_client/002_udp_rw_buffer$

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

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

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

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

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

biao@ubuntu:~/test/udp_client/002_udp_rw_buffer$ su
Password:
root@ubuntu:/home/biao/test/udp_client/002_udp_rw_buffer#
root@ubuntu:/home/biao/test/udp_client/002_udp_rw_buffer# echo 262144 > /proc/sys/net/core/rmem_default
root@ubuntu:/home/biao/test/udp_client/002_udp_rw_buffer# echo 1048576 > /proc/sys/net/core/rmem_max
root@ubuntu:/home/biao/test/udp_client/002_udp_rw_buffer# cat /proc/sys/net/core/rmem_default
262144
root@ubuntu:/home/biao/test/udp_client/002_udp_rw_buffer# cat /proc/sys/net/core/rmem_max
1048576
root@ubuntu:/home/biao/test/udp_client/002_udp_rw_buffer# ls
a.out setsockopt.c
root@ubuntu:/home/biao/test/udp_client/002_udp_rw_buffer# ./a.out 1048576
OS default udp socket recv buff size is: 262144
you want to set udp socket recv buff size to 1048576
set udp socket(3) recv buff size to 1048576 OK!!!
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?

    这个是和源码有关系:

case SO_SNDBUF:
if (val > sysctl_wmem_max)
val = sysctl_wmem_max;
if ((val * 2 ) < SOCK_MIN_SNDBUF)
sk->sk_sndbuf = SOCK_MIN_SNDBUF;
else
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. OpenEuler22.03安装最新版本Docker

    一.环境及问题 操作系统环境如下: 操作系统:OpenEuler 22.03 LTS 安装方式:最小化安装 在操作系统安装完毕如果直接采用dnf或者yum方式安装docker: sudo dnf in ...

  2. CTFHub SSRF Redis协议 WriteUp

    CTFHub SSRF Redis协议 进入环境,可以看到url格式为: http://challenge-2c082607df3fa433.sandbox.ctfhub.com:10800/?url ...

  3. ubuntu安装mysql8,debian安装mysql8,linux安装mysql8,x86_64架构,deb包

    作者主页:https://www.cnblogs.com/milkbox 参考: 修改大小写:MySQL8.0安装后更改不区分大小写!包你必生效!_mysql8.0不区分大小写-CSDN博客 整个安装 ...

  4. 2021-01-16:我截获了登录token的话,是不是就获得了登录状态,这样就不安全了。如何保证安全?

    福哥答案2021-01-06: 知乎答案: 首先,Token 一般放在 Header 或者 Cookies 中,Http 是明文传输,Https 是密文传输.可以一定程度防止Token 截获. 第二, ...

  5. Spark Core快速入门

    Spark-core快速入门 一.简介 Apache spark是专门为大规模数据处理而设计的快速通用的计算模型,是一种类似于Mapreduce通用并行计算框架,与mapreduce不同的是,spar ...

  6. 遥居前列!华为云GaussDB再获行业权威验证

    摘要:北京国家金融科技认证中心正式公布了2022年通过"分布式数据库金融标准验证"的数据库产品名单.华为云GaussDB金融级分布式数据库以突出的技术优势通过验证,跃然榜上,且测试 ...

  7. 云图说|初识数据库和应用迁移UGO

    阅识风云是华为云信息大咖,擅长将复杂信息多元化呈现,其出品的一张图(云图说).深入浅出的博文(云小课)或短视频(云视厅)总有一款能让您快速上手华为云.更多精彩内容请单击此处. 摘要:数据库和应用迁移 ...

  8. bat cmd 无效参数/选项 - deleting

    Window bat expdp 数据库定时任务逻辑备份数据库 定时删除N天前的旧文件 Linux shell crontab expdp 定时任务逻辑备份数据库 定时删除旧文件 错误配置 forfi ...

  9. module 'numpy' has no attribute 'int'.

    原因:np.int 在 NumPy 1.20 中已弃用,在 NumPy 1.24 中已删除 AttributeError: module 'numpy' has no attribute 'int'. ...

  10. 【Java爬虫】如何通过 API 递归分页爬取网页数据

    前言 在最近的互联网项目开发中,需要获取用户的访问ip信息进行统计的需求,用户的访问方式可能会从微信内置浏览器.Windows浏览器等方式对产品进行访问. 当然,获取这些关于ip的信息是合法的.但是, ...