我们通常使用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相关推荐

  1. TCP/UDP的接收缓冲区和发送缓冲区

    转载自:https://blog.csdn.net/Swallow_he/article/details/84392285 1.TCP. SO_RCVBUF & TCP. SO_SNDBUF ...

  2. C# socket通信 接收缓冲区大小设置,以及粘包问题的解决

    C# socket通信 接收缓冲区大小,以及粘包问题的解决 一. Socket接收缓冲区无论: 1.buffer设置有多大: 2.同步接收还是异步接收: 3.发送超过 43690 也就是 42KB的字 ...

  3. python socket清空接收缓冲区_Python网络编程——修改套接字发送和接收的缓冲区大小...

    很多情况下,默认的套接字缓冲区大小可能不够用.此时,可以将默认的套接字缓冲区大小改成一个更合适的值. 1. 代码 # ! /usr/bin/env python # -*- coding: utf-8 ...

  4. 获取socket对应的接收缓冲区中的可读数据量

    获取socket对应的接收缓冲区中的可读数据量 本文介绍如何获取当前socket对应的接收缓冲区的可读数据量 在Linux上可以使用ioctl函数 #include <sys/ioctl.h&g ...

  5. tcp socket的发送与接收缓冲区

    1)应用程序可通过调用send(write, sendmsg等)利用tcp socket向网络发送应 用数据, 而tcp/ip协议栈 再通 过 网络设备接口把已经组 织成struct  sk_buff ...

  6. c语言 recv_sin,C++_C语言中经socket接收数据的相关函数详解,recv()函数: 头文件:#incl - phpStudy...

    C语言中经socket接收数据的相关函数详解 recv()函数:头文件: #include #include 定义函数: int recv(int s, void *buf, int len, uns ...

  7. Netty UDP 接收缓冲区 报文截取问题

    Netty UDP 报文截取问题 问题 最近在写一个 syslog udp 日志接收器,然后发现接收过大的日志数据会被截断,拿到的信息不完整 源码追踪 创建 udp server 的示例代码 def ...

  8. 【网络协议】转载:关于TCP与UDP的接收recv和recvfrom

    关于TCP与UDP的接收recv和recvfrom 技术标签: 网络协议 计算机网络 1.UDP发包的问题 问:udp 发送(sendto)两次数据,第一次 100字节 ,第二次200字节, 接包方一 ...

  9. Linux Kernel TCP/IP Stack — Socket Layer — TCP/UDP Socket 网络编程

    目录 文章目录 目录 TCP/UDP Socket 逻辑架构 创建 Socket 绑定 Socket 请求建立 Socket 连接 监听 Socket 接受请求 关闭连接 数据的发送和接收 send ...

最新文章

  1. linux mysql 实例详解_MySQL 多实例详解
  2. 如何移动SQL SERVER的系统数据库
  3. E. Anfisa the Monkey
  4. Linux进程列表巧用,Linux下的进程分析–PS
  5. centos系统安装pycharm编辑器
  6. 在Linux命令行发送电子邮件附件的两种方法
  7. Mysql高手系列 - 第20篇:异常捕获及处理详解(实战经验)
  8. CCF201312-4 有趣的数(100分)
  9. 13.高性能MySQL --- 云端的MySQL
  10. java程序员推荐app_Java程序员面试大全app
  11. 沟通CTBS立白集团远程接入成功案例
  12. Spring 之 BeanFactory 源码 - 抽象/类 分析
  13. opencv+VS2005安装说明
  14. Flutter小说APP
  15. 利用企业微信/飞书/钉钉扫码认证连接办公WiFi无线网络解决方案
  16. python制作字符动画
  17. 概要设计的过程和任务
  18. python语音识别(语音转文字)
  19. 《娱乐至死》读书笔记(摘抄)
  20. 苹果项目关闭服务器,苹果自动续费怎么取消?手把手教你快速关闭

热门文章

  1. JavaB试题 基础练习 Fibonacci数列
  2. 【PTA】到底是不是太胖了
  3. 一维中值、均值、高斯滤波的MATLBA实现
  4. 平稳信号、非平稳信号
  5. 解除excel工作表密码保护方法
  6. EntityUtils MapStruct BeanCopier 数据实体类转换工具 DO BO VO DTO 附视频
  7. NYOJ127 星际之门(一)【定理】
  8. 路径和(cdq分治)
  9. pcs增加mysql资源_pcs命令配置示例(详细版)
  10. ramdisk.img