在套接字库中调用 recv 时,我的 recv 缓冲区应该有多大

关于 C 语言中的套接字库,我有几个问题。下面是我将在提问中引用的代码片段。

char recv_buffer[3000];
recv(socket, recv_buffer, 3000, 0);
  1. 如何确定 recv _ buffer 的大小? 我使用的是3000,但它是任意的。
  2. 如果 recv()接收到比我的缓冲区大的数据包会发生什么?
  3. 我怎样才能知道我是否已经收到了整个消息而没有再次调用 recv,并让它永远等待时,没有收到任何东西?
  4. 有没有一种方法可以让缓冲区没有固定的空间量,这样我就可以不断增加它而不用担心空间耗尽?也许使用 strcat连接最新的 recv()响应到缓冲区?

我知道一个问题里有很多问题,但如果你们能回答我,我会非常感激。

129685 次浏览

对于 TCP 之类的流协议,您几乎可以将缓冲区设置为任意大小。也就是说,建议使用2的幂的公共值,例如4096或8192。

如果有更多的数据比什么你的缓冲区,它将简单地保存在内核为您的下一个调用 recv

是的,你可以继续增加你的缓冲。你可以从偏移量 idx开始在缓冲区的中间做一个 recv,你可以这样做:

recv(socket, recv_buffer + idx, recv_buffer_size - idx, 0);

如果您有一个 SOCK_STREAM套接字,那么 recv只能从流中获取“最多3000字节”。对于缓冲区的大小没有明确的指导: 您只有在完成所有操作之后才能知道流有多大; ——)。

如果您有一个 SOCK_DGRAM套接字,并且数据报大于缓冲区,那么 recv将用数据报的第一部分填充缓冲区,返回 -1,并将 errno 设置为 EMSGSIZE。不幸的是,如果协议是 UDP,这意味着数据报的其余部分丢失了——这就是为什么 UDP 被称为 不可靠协议的部分原因(我知道有可靠的数据报协议,但是它们不是很流行——我不能说出 TCP/IP 家族中的一个,尽管我对后者非常了解; ——)。

要动态增长缓冲区,请首先使用 malloc分配它,并根据需要使用 realloc。但是,唉,这对 UDP 源代码中的 recv没有帮助。

16kb 就差不多了,如果你使用的是吉比特以太网,每个数据包的大小可以是9kb。

这些问题的答案取决于您使用的是流套接字(SOCK_STREAM)还是数据报套接字(SOCK_DGRAM)——在 TCP/IP 中,前者对应于 TCP,后者对应于 UDP。

如何知道传递给 recv()的缓冲区有多大?

  • 其实也没什么大不了的。如果您的协议是事务/交互式的,那么只需选择一个能够容纳您合理期望的最大单个消息/命令的大小(3000可能没问题)。如果您的协议正在传输批量数据,那么更大的缓冲区可以更有效率-一个好的经验法则是大约相同的内核接收套接字缓冲区大小(通常是256kB 左右)。

  • SOCK_DGRAM: 使用一个足够大的缓冲区来保存应用程序级协议所发送的最大数据包。如果您正在使用 UDP,那么通常您的应用程序级协议不应该发送大于1400字节的数据包,因为它们肯定需要被分割和重新组装。

如果 recv得到的数据包大于缓冲区,会发生什么情况?

  • SOCK_STREAM: 这个问题没有实际意义,因为流套接字没有数据包的概念——它们只是一个连续的字节流。如果可读取的字节数超过了缓冲区的空间,那么它们将由操作系统排队,并可用于下一次对 recv的调用。

  • SOCK_DGRAM: 丢弃多余的字节。

我如何知道我是否已经收到了整个信息?

  • SOCK_STREAM: 您需要在应用程序级协议中构建某种确定消息结束的方法。通常,这是一个长度前缀(以消息的长度开始每条消息)或消息结束分隔符(例如,在基于文本的协议中,它可能只是一个换行符)。第三种方法是为每条消息指定一个固定的大小,这种方法使用较少。这些选项的组合也是可能的——例如,包含长度值的固定大小标头。

  • SOCK_DGRAM: 单个 recv调用总是返回单个数据报。

有没有一种方法可以让缓冲区没有固定的空间量,这样我就可以不断地添加到缓冲区中,而不用担心空间耗尽?

没有。但是,您可以尝试使用 realloc()调整缓冲区的大小(如果最初是用 malloc()calloc()分配的话)。

您的问题没有绝对的答案,因为技术总是特定于实现的。我假设您使用 UDP 进行通信,因为传入的缓冲区大小不会给 TCP 通信带来问题。

根据 RFC 768,UDP 的数据包大小(包含头部)可以在8到65515字节之间。因此,传入缓冲区的防故障大小为65507字节(~ 64KB)

然而,并非所有的大型数据包都能被网络设备正确地路由,更多信息请参考现有的讨论:

最大吞吐量的 UDP 包的最佳大小是多少
什么是互联网上最大的安全 UDP 数据包大小

对于 SOCK_STREAM套接字,缓冲区大小实际上并不重要,因为您只是提取了一些等待的字节,并且可以在下一次调用中检索更多内容。只要选择任何你能负担得起的缓冲区大小。

对于 SOCK_DGRAM套接字,您将获得等待消息的合适部分,其余部分将被丢弃。您可以使用下面的 ioctl 获得等待的数据报大小:

#include <sys/ioctl.h>
int size;
ioctl(sockfd, FIONREAD, &size);

或者,您可以使用 recv()调用的 MSG_PEEKMSG_TRUNC标志来获得等待的数据报大小。

ssize_t size = recv(sockfd, buf, len, MSG_PEEK | MSG_TRUNC);

您需要 MSG_PEEK来查看(而不是接收)等待的消息-recv 返回实际的,而不是截断的大小; 并且您需要 MSG_TRUNC不溢出您的当前缓冲区。

然后您可以只 malloc(size)的实际缓冲区和 recv()数据报。