前言

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. 【玩转腾讯混元大模型】怎么说?我用混元AI大模型开发了个IDEA插件

    前言 halo 我是杨不易呀,在混元大模型内测阶段就已经体验了一番当时打开页面的时候灵感模块让我大吃一惊这么多角色模型真的太屌了,随后我立马进行了代码处理水平和上下文的效果结果一般般但是到如今混元大模 ...

  2. Python中的协程、线程和进程

    一.协程与多线程和多进程一起使用有什么不同   协程.多线程和多进程都是实现程序并发执行的方法,不过它们在工作方式和适合的应用场景上存在一些区别. 1.协程(Coroutine)   协程是在单一线程 ...

  3. 用Spring Boot 3.2虚拟线程搭建静态文件服务器有多快?

    Spring Boot 3.2 于 2023 年 11 月大张旗鼓地发布,标志着 Java 开发领域的一个关键时刻.这一突破性的版本引入了一系列革命性的功能,包括: 虚拟线程:利用 Project L ...

  4. 文心一言 VS 讯飞星火 VS chatgpt (57)-- 算法导论6.4 1题

    文心一言 VS 讯飞星火 VS chatgpt (57)-- 算法导论6.4 1题 一.参照图 6-4 的方法,说明 HEAPSORT 在数组 A=(5,13,2,25,7,17,20,8,4)上的操 ...

  5. CUDA C编程权威指南:1.2-CUDA基础知识点梳理

      主要整理了N多年前(2013年)学习CUDA的时候开始总结的知识点,好长时间不写CUDA代码了,现在LLM推理需要重新学习CUDA编程,看来出来混迟早要还的. 1.闭扫描和开扫描   对于一个二元 ...

  6. 开发老人笔记:Git 常用命令清单

    摘要:git是目前世界上最先进的分布式版本控制系统. 多人协作 master:此分支用来发布稳定的代码,合并一般是由管理员合并 dev:此分支用于团队开发,团队成员向此分支提交代码 bug:此分支用于 ...

  7. 云小课|云小课带你玩转可视化分析ELB日志

    阅识风云是华为云信息大咖,擅长将复杂信息多元化呈现,其出品的一张图(云图说).深入浅出的博文(云小课)或短视频(云视厅)总有一款能让您快速上手华为云.更多精彩内容请单击此处. 云日志服务支持可视化查看 ...

  8. SyntaxError: Non-ASCII character #-*- coding:utf-8 -*-

    执行python报错 /usr/bin/python2.7 /root/demo.py File "/root/demo.py", line 2 SyntaxError: Non- ...

  9. PPT 配色方法

    https://www.bilibili.com/video/BV1ha411g7f5/?p=10 https://dribbble.com/ 问题1:配色太多,主题色混乱 一个主色,两辅色 问题2: ...

  10. 【Git使用】代码拉取及用户名初始化

    代码拉取及用户名初始化