首页 > 编程语言 >udp编程及udp常见问题处理

udp编程及udp常见问题处理

时间:2023-04-20 19:48:37浏览次数:41  
标签:udp 常见问题 socket 编程 缓冲区 buff recv size

前言

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,常见问题,socket,编程,缓冲区,buff,recv,size
From: https://www.cnblogs.com/liwen01/p/17337846.html

相关文章

  • GUI编程
    GUI编程组件:窗口弹窗面板文本框列表框按钮图片监听事件鼠标键盘事件破解工具1.简介GUI的核心技术:SwingAWT,因为界面不美观。​ 1.需要GRE环境​ 2.需要界面不美观为什么要学习?​ 1.可以写出自己心中想要的一些小工具​ 2.工作时候,也可能需要维护到Swing界......
  • 并发编程(四)
    1、多线程情况下为了避免多个线程同时进入临界区(访问某一块代码),对数据进行修改,产生竞态条件必须要采用同步原语1.1、锁,利用上下文管理器自动获取释放锁。更容易理解1.2、信号量,资源消耗进行递减;资源释放进行递增,可以理解为一个计数器2、线程间通信队列-que......
  • QUIC协议 对比 TCP/UDP 协议
    QUIC协议是HTTP3引入的,所以需要了解HTTP的版本迭代。HTTP1.x队头阻塞:下个请求必须在前一个请求返回后才能发出,导致带宽无法被充分利用,后续请求被阻塞(HTTP1.1尝试使用流水线(Pipelining)技术,但先天FIFO(先进先出)机制导致当前请求的执行依赖于上一个请求执行的完成,容易引起队头阻......
  • 高新技术企业申报有哪些常见问题
    高新技术企业高新技术企业是指在中国(不包括香港、澳门、台湾)注册一年以上的居民企业,在国家重点支持的高新技术领域不断开展研发和技术成果转化,形成企业核心自主知识产权,并在此基础上开展经营活动。高新技术企业作为国家重点发展对象,不仅享受国家政策,还享受地方政策支持。那么,企业如......
  • 编程打卡
    #include<iostream>usingnamespacestd;#include<vector>#include<algorithm>typedefpair<int,int>PIIconstintN=300010;inta[N],s[N];vector<int>alls;//存储所有待离散化的值vector<PII>adds,query;//二分求出x对应的离散化的值intfind(in......
  • day 01 1.1 Python基础之编程语言介绍
    Python基础之编程语言介绍1.1、什么是编程语言编程语言是用来控制计算机的一系列指令(Instruction),它有固定的格式和词汇(不同编程语言的格式和词汇不一样)。就像我们中国人之间沟通需要汉语,英国人沟通需要英语一样,人与计算机之间进行沟通需要一门语言作为介质,即编程语言。编程语言......
  • MySQL使用过程中常见问题的解决
    问题1:root用户密码忘记,重置的操作、1:通过任务管理器或者服务管理,关掉mysqld(服务进程)2:通过命令行+特殊参数开启mysqldmysqld--defaults-file="D:\ProgramFiles\mysql\MySQLServer5.7Data\my.ini"--skip-grant-tables3:此时,mysqld服务进程已经打开。并且不需......
  • C++ - UDP通信
    UDPUDP就比较简单了,步骤比tcp要少一些。连接过程图:  1).服务器1.初始化套接字库WORDwVersion;WSADATAwsaData;interr;​wVersion=MAKEWORD(1,1);2.创建套接字SOCKETsockSrv=socket(AF_INET,SOCK_DGRAM,0);3.绑定//SOCKADDR_INaddrSrv;省略了定......
  • 第六章 面向对象编程
    6.1初识面向对象6.1.1面向过程&面向对象面向过程思想步骤清楚,第一步做什么,第二步做什么面向过程处理一些较为简单的问题面向对象思想物以类聚,分类的思维模式,思考问题首先会解决问题需要哪些分类,然后对这些分类进行单独的思考。最后,才对某个分类下的细节进行面......
  • 基于UDP协议的Socket通信
    TCP和UDP最大的区别在于是否需要客户端与服务端建立连接后才能进行数据传输,如果你学习前面的TCP,传输前先开服务端,accept,等客户端接入,然后获得客户端socket然后进行IO操作,而UDP则不用,UDP以数据报作为数据的传输载体,在进行传输时首先要把传输的数据定义成数据报(Datagram),在数据报中......