没有填满接收缓冲区,UDP缓冲区溢出?

3
如果我发送1000个“Hello World!”UDP消息(12字节+28 IP / UDP开销),我观察到在接收端只缓冲658个(始终是同一个数字,658 * 40 = 26320字节)。我通过在服务器上睡眠(创建套接字后)发送UDP消息来实现这一点。
有趣的是,服务器上的SO_RCVBUF选项为42080字节。那么,我想知道为什么我不能缓冲1000条信息。你知道剩下的15760字节花在哪里了吗?
以下是服务器代码(其中distrib.h包含套接字和信号处理函数的基本错误处理包装器):
#include "distrib.h"

static int count;
static void sigint_handler(int s) {
  printf("\n%d UDP messages received\n",count);
  exit(0);
}

int main(int argc, char **argv) 
{
  struct addrinfo* serverinfo;
  struct addrinfo hints;
  struct sockaddr_storage sender;
  socklen_t len;
  int listenfd,n;
  char buf[MAXLINE+1];

  if (argc != 2) {
    log_error("usage: %s <port>\n", argv[0]);
    exit(1);
  }

  Signal(SIGINT,sigint_handler);

  bzero(&hints,sizeof(hints));
  hints.ai_family = AF_INET;    
  hints.ai_socktype = SOCK_DGRAM; 
  hints.ai_protocol = IPPROTO_UDP;
  Getaddrinfo("127.0.0.1", argv[1], &hints, &serverinfo);

  listenfd = Socket(serverinfo->ai_family, serverinfo->ai_socktype, 
            serverinfo->ai_protocol);
  Bind(listenfd, serverinfo->ai_addr,serverinfo->ai_addrlen);
  freeaddrinfo(serverinfo);

  count =0;
  sleep(20); 
  while(true) {
    bzero(buf,sizeof(buf));
    len = sizeof(sender);
    n = Recvfrom(listenfd, buf, MAXLINE, 0, (struct sockaddr*)&sender,&len);
    buf[n]='\0';
    count++;

  }

  close(listenfd);

  return 0;
}

MAXLINE的值是多少? - thuovila
你确定所有的消息都已经发送了吗?服务器和客户端程序都在同一台电脑/网络上吗? - Some programmer dude
除了套接字接收缓冲区大小之外,可能还有一堆其他设置会影响排队数据包的数量。例如,net.core.netdev_max_backlog 是可以存在的未处理输入数据包的数量。 - thuovila
@thuovilla: MAXLINE = 1024。 - marcmagransdeabril
如果我移除sleep,一切都能按预期工作。 - marcmagransdeabril
显示剩余4条评论
2个回答

5
更有意义的是进行反向计算——您的缓冲区为42080,它在开始丢弃之前缓冲了658个数据包。现在,42080/658=63.95,因此看起来它将每个数据包视为64字节,并且如果到目前为止所缓冲的数据包的总大小等于或超过限制,则丢弃数据包。由于它缓冲整个数据包,因此实际上会缓冲略多于限制。
为什么是64字节而不是40字节呢?也许它考虑到了一些排队开销,或者也许它将其舍入为2的某个幂的倍数以进行对齐,或者可能两者都是一部分的结合。

socket(7)手册还提到,rcvbuf的50%将被保留用于簿记,因此理论上您不应依赖超过skb的50%。 - wick

2

我没有完整的答案,但我在Linux系统上测试过,观察到以下内容。

当我发送一个以空字符'\0'结尾的"Hello World!\n"。我会收到:

客户端:

$./sendto
sent 14 bytes

Socket的“Recv-Q”有768字节(看起来可能是以字节为单位,没有检查ss源代码):

$ ss -ul|grep 55555
UNCONN     768    0               127.0.0.1:55555                    *:*     

当我发送 1000 个数据包时,我会得到:

$ ./sendto 
sent 14000 bytes

接收队列:

$ ss -ul|grep 55555
UNCONN     213504 0               127.0.0.1:55555                    *:*       

您的服务器(执行 ctrl-c 后):

$ ./recvfrom 55555
^C
278 UDP messages received

顺便提一下,213504/768=278。通过快速实验,我无法确定要调整哪个设置来增加缓冲量。此外,我不知道为什么接收到的数据包在此队列中占用了这么多空间。可能有很多元数据吧?就像在你的osX上,丢失的数据包会显示在netstat -su中。

编辑:使用ss -ulm进行附加观察,该命令以更详细的方式打印“套接字内存使用情况”:

UNCONN     213504 0               127.0.0.1:55555                    *:*       
skmem:(r213504,rb212992,t0,tb212992,f3584,w0,o0,bl0)

缓冲的213504字节比rb值高512字节。这可能不是巧合,但需要阅读内核源代码才能找出原因。

你检查了osX上一个UDP数据包占用多少空间吗?

编辑2:虽然这仍不适用于osX,但在Linux上,我发现增加内核为接收缓冲区分配的内存可使我缓冲所有发送的1000个数据包。

有点过度杀伤,但我使用了以下值(免责声明):随意调整缓冲区值可能会严重破坏您的网络和内核。

net.core.rmem_max=1048568
net.core.rmem_default=1048568

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接