UDP Socket接收缓冲区与netstat Recv-Q
我们通常使用netstat查看网络的诸多状态,其中包含Send-Q与Recv-Q。
我们知道:
- 每一个Socket对象在系统中都被映射为一个Socket文件;
- 每一个Socket对象在系统中都关联有两个内核缓冲区:一个接收缓冲区(读缓冲区),一个发送缓冲区(写缓冲区);
Send-Q:指代的是内核中Socket对应的发送缓冲区尚未发送完毕的字节数;
Recv-Q:指代的是内核中Socket对应的接收缓冲区尚未被用户收走(read)而滞留在接收缓冲区的字节数;
下面请看示例:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <netdb.h>
#include <signal.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>static int totalMsg = 0;void sigINT(int dwsigno)
{printf("totalMsg: %d\n", totalMsg);exit(0);
}int openServer()
{struct addrinfo hints;memset(&hints, 0, sizeof(hints));hints.ai_flags = AI_PASSIVE;hints.ai_socktype = SOCK_DGRAM;struct addrinfo *res;static char *port = "4020";int e = getaddrinfo(NULL, port, &hints, &res);if (e == EAI_SYSTEM){printf("openServer: getaddrinfo error=%d(%s)!!!\n", errno, strerror(errno));return -1;}else if (e != 0){printf("openServer: getaddrinfo error=%s!!!\n", gai_strerror(e));return -1;}int fd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);if (fd < 0){printf("openServer: create socket error=%d(%s)!!!\n", errno, strerror(errno));freeaddrinfo(res);return -1;}int rcvBufSize = 131071; // 系统默认可设置缓冲区大小socklen_t optlen = sizeof(rcvBufSize);if (setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &rcvBufSize, optlen) < 0){printf("openServer: setsockopt error=%d(%s)!!!\n", errno, strerror(errno));freeaddrinfo(res);return -1;}int rcvRealSize = -1;if (getsockopt(fd, SOL_SOCKET, SO_RCVBUF, &rcvRealSize, &optlen) < 0){printf("openServer: getsockopt error=%d(%s)!!!\n", errno, strerror(errno));freeaddrinfo(res);return -1;}printf("recrive-buff-size: %d\n", rcvRealSize);if (bind(fd, (struct sockaddr *)res->ai_addr, res->ai_addrlen) < 0){printf("openServer: bind error=%d(%s)!!!\n", errno, strerror(errno));freeaddrinfo(res);return -1;}printf("create udp socket(%d) ok!\n", fd);return fd;
}void monUdpSock(int udpSock)
{static fd_set fds;FD_ZERO(&fds);FD_SET(udpSock, &fds);static struct timeval tv = {0, 20000};int readyNum = select(udpSock+1, &fds, NULL, NULL, &tv);if (readyNum < 0){printf("monUdpSock: select error=%d(%s)!!!\n", errno, strerror(errno));// 异常处理return;}else if (readyNum == 0)return; // select超时,do nothingelse; // 存在可读写fdif (!FD_ISSET(udpSock, &fds))return;static char udpMsg[1024*64]; // 64KBint rbytes = read(udpSock, udpMsg, sizeof(udpMsg));if (rbytes <= 0)return;// 处理收到的Udp消息totalMsg++;
}int main()
{if (signal(SIGINT,sigINT) == SIG_ERR){printf("set single handler error!\n");exit(1);}int udpSock = openServer();while (1){monUdpSock(udpSock);usleep(10); // sleep 10 us}
}
上面的代码是一个简单的udp服务器,用以接收来自udp客户端的数据报并且统计总共收到了多少个udp数据报。
我们编译并且运行这个udp服务器:
[udpdriver@eb6347 0329]$ gcc -o main main.c
[udpdriver@eb6347 0329]$ ./main
recrive-buff-size: 262142
create udp socket(3) ok!
结合代码,我们看到当前Socket拥有的接收缓冲区大小为262142字节。
注:为啥设置的缓冲区大小是131071,但实际返回的是262142?
通常来说,setsockopt可以设置的缓冲区是系统设置的Socket最大缓冲区数值大小的一半。
[udpdriver@eb6347 0329]$ cat /proc/sys/net/core/rmem_max
131071
系统内核设置的这个接收缓冲区大小值,就是udp socket默认的最大接收缓冲区值的一半。
实际一个udp socket的接收缓冲区最大为:131071*2=262142.
我们再通过netstat来查看此socket的接收缓冲区中滞留的,尚未读取(到用户态缓冲区)的字节数量。
我们可以写一个简单的shell脚本,每秒调用一次netstat,来观察其运行期的缓冲区滞留数值。
#!/bin/bashwhile [ true ]; dosleep 1netstat -an | grep $1
done
添加权限并且启动脚本:
[udpdriver@eb6347 0329]$ chmod a+x netstat.sh
[udpdriver@eb6347 0329]$ ./netstat.sh 4020
udp 0 0 :::4020 :::*
udp 0 0 :::4020 :::*
udp 0 0 :::4020 :::*
udp 0 0 :::4020 :::*
udp 0 0 :::4020 :::*
第二列即为我们说的Recv-Q,第三列即为我们说的Send-Q。
目前尚未有udp客户端发送数据,所以滞留在udp服务端socket的接收缓冲区,尚未被读取的字节数为0。
我们使用一个压测的小工具压力测试模拟程序(C实现)_test1280的博客-CSDN博客,模拟每条udp 200字节左右,800caps,总量80000,来对服务器进行压测。
[udpdriver@eb6347 pmtest]$ ./main 80000 800
totalUdp: 80000
maxRate: 800
udp data len: 258
loaded 258 Bytes Data
此时netstat脚本的输出中,我们可以观察到接收缓冲区的堆积:
udp 0 0 :::4020 :::*
udp 0 0 :::4020 :::*
udp 0 0 :::4020 :::*
udp 5056 0 :::4020 :::*
udp 2528 0 :::4020 :::*
udp 2528 0 :::4020 :::*
udp 11376 0 :::4020 :::*
udp 9480 0 :::4020 :::*
udp 12008 0 :::4020 :::*
udp 12008 0 :::4020 :::*
udp 9480 0 :::4020 :::*
udp 3160 0 :::4020 :::*
udp 10744 0 :::4020 :::*
udp 43608 0 :::4020 :::*
udp 147888 0 :::4020 :::*
udp 261648 0 :::4020 :::*
udp 261648 0 :::4020 :::*
udp 261016 0 :::4020 :::*
udp 261648 0 :::4020 :::*
udp 261648 0 :::4020 :::*
udp 261648 0 :::4020 :::*
udp 261648 0 :::4020 :::*
udp 261648 0 :::4020 :::*
udp 261016 0 :::4020 :::*
udp 261648 0 :::4020 :::*
udp 261648 0 :::4020 :::*
udp 261648 0 :::4020 :::*
udp 261648 0 :::4020 :::*
udp 261016 0 :::4020 :::*
Recv-Q越来越大,说明udp服务器来不及从内核中的接收缓冲区收取数据,导致大量udp数据包堆积在内核缓冲区。
当内核缓冲区中数据达到设置的上限(262142字节),再有udp包到内核,内核就将其丢弃,也就是常说的:
UDP接收缓冲区溢出,发生丢包现象。
我们客户端实际发送80000个udp数据包,可以通过在服务端Ctrl+C发个信号,查看当前udp服务端已处理的udp包总数:
[udpdriver@eb6347 0329]$ ./main
recrive-buff-size: 262142
create udp socket(3) ok!
totalMsg: 69770
69770<80000,丢失UDP包10230个。
如果你够仔细,你会发现,Recv-Q的最大值,就是我们的udp内核接收缓冲区的实际值。
无论发包多快,多大,在Recv-Q永远不会超过getsockopt得出的实际的udp socket内核接收缓冲区的max值。
到此我们明白:getsockopt获取的接收缓冲区的大小,等价于在netstat中Recv-Q中可滞留在接收缓冲区数据的最大值。如果数据加入不到Recv-Q(内核读缓冲区)中,那内核就将其丢弃(特指udp)。此时,我们应该考虑的是提高服务器的性能,而不是扩大接收缓冲区的大小。
原因:缓冲区大小是防止“突变”的一种机制,防止在某一特殊时刻大量数据到来,导致丢失数据包。
如果接收缓冲区通常为0,偶尔有个波动,来个3k 5k的数据,那是正常的,没关系的,可能由于系统调度等原因导致。
如果接收缓冲区有堆积并且无法归零,说明服务器read慢了,跟不上客户端的发送速率,这样,即使你暂时设置缓冲区为1G(假定能设置这么大),2G,3G也是没有意义的。终究会随着时间的推移,导致接收缓冲区的数据不断累积…你能放多少?
无论何时,应当保证你的消耗速率(从内核中将读缓冲区的数据读到用户态缓冲区)大于你的生产速率(内核收到来自网络的数据包,将其从网卡中写入内核的写缓冲区的速率)。
接收缓冲区有堆积是一定有问题的,增大缓冲区并不能解决问题,要从服务器角度考虑,优化、增强其处理性能,快快地将接收缓冲区滞留的待读数据处理完毕。
原文链接:https://blog.csdn.net/test1280/article/details/79749210
UDP Socket接收缓冲区与netstat Recv-Q相关推荐
- TCP/UDP的接收缓冲区和发送缓冲区
转载自:https://blog.csdn.net/Swallow_he/article/details/84392285 1.TCP. SO_RCVBUF & TCP. SO_SNDBUF ...
- C# socket通信 接收缓冲区大小设置,以及粘包问题的解决
C# socket通信 接收缓冲区大小,以及粘包问题的解决 一. Socket接收缓冲区无论: 1.buffer设置有多大: 2.同步接收还是异步接收: 3.发送超过 43690 也就是 42KB的字 ...
- python socket清空接收缓冲区_Python网络编程——修改套接字发送和接收的缓冲区大小...
很多情况下,默认的套接字缓冲区大小可能不够用.此时,可以将默认的套接字缓冲区大小改成一个更合适的值. 1. 代码 # ! /usr/bin/env python # -*- coding: utf-8 ...
- 获取socket对应的接收缓冲区中的可读数据量
获取socket对应的接收缓冲区中的可读数据量 本文介绍如何获取当前socket对应的接收缓冲区的可读数据量 在Linux上可以使用ioctl函数 #include <sys/ioctl.h&g ...
- tcp socket的发送与接收缓冲区
1)应用程序可通过调用send(write, sendmsg等)利用tcp socket向网络发送应 用数据, 而tcp/ip协议栈 再通 过 网络设备接口把已经组 织成struct sk_buff ...
- c语言 recv_sin,C++_C语言中经socket接收数据的相关函数详解,recv()函数:
头文件:#incl - phpStudy...
C语言中经socket接收数据的相关函数详解 recv()函数:头文件: #include #include 定义函数: int recv(int s, void *buf, int len, uns ...
- Netty UDP 接收缓冲区 报文截取问题
Netty UDP 报文截取问题 问题 最近在写一个 syslog udp 日志接收器,然后发现接收过大的日志数据会被截断,拿到的信息不完整 源码追踪 创建 udp server 的示例代码 def ...
- 【网络协议】转载:关于TCP与UDP的接收recv和recvfrom
关于TCP与UDP的接收recv和recvfrom 技术标签: 网络协议 计算机网络 1.UDP发包的问题 问:udp 发送(sendto)两次数据,第一次 100字节 ,第二次200字节, 接包方一 ...
- Linux Kernel TCP/IP Stack — Socket Layer — TCP/UDP Socket 网络编程
目录 文章目录 目录 TCP/UDP Socket 逻辑架构 创建 Socket 绑定 Socket 请求建立 Socket 连接 监听 Socket 接受请求 关闭连接 数据的发送和接收 send ...
最新文章
- linux mysql 实例详解_MySQL 多实例详解
- 如何移动SQL SERVER的系统数据库
- E. Anfisa the Monkey
- Linux进程列表巧用,Linux下的进程分析–PS
- centos系统安装pycharm编辑器
- 在Linux命令行发送电子邮件附件的两种方法
- Mysql高手系列 - 第20篇:异常捕获及处理详解(实战经验)
- CCF201312-4 有趣的数(100分)
- 13.高性能MySQL --- 云端的MySQL
- java程序员推荐app_Java程序员面试大全app
- 沟通CTBS立白集团远程接入成功案例
- Spring 之 BeanFactory 源码 - 抽象/类 分析
- opencv+VS2005安装说明
- Flutter小说APP
- 利用企业微信/飞书/钉钉扫码认证连接办公WiFi无线网络解决方案
- python制作字符动画
- 概要设计的过程和任务
- python语音识别(语音转文字)
- 《娱乐至死》读书笔记(摘抄)
- 苹果项目关闭服务器,苹果自动续费怎么取消?手把手教你快速关闭