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

Posted

技术标签:

【中文标题】在套接字库中调用 recv 时,我的 recv 缓冲区应该有多大【英文标题】:How large should my recv buffer be when calling recv in the socket library 【发布时间】:2011-02-21 04:02:50 【问题描述】:

我有几个关于 C 中的套接字库的问题。这是我将在我的问题中引用的一段 sn-p 代码。

char recv_buffer[3000];
recv(socket, recv_buffer, 3000, 0);
    如何确定recv_buffer 的大小?我使用的是 3000,但它是任意的。 如果recv() 收到比我的缓冲区大的数据包会怎样? 如何知道我是否已收到整条消息而无需再次调用 recv 并在没有收到任何内容时让它永远等待? 有没有一种方法可以使缓冲区没有固定的空间量,这样我就可以继续添加它而不必担心空间不足?也许使用strcat 将最新的recv() 响应连接到缓冲区?

我知道这是很多问题,但我将非常感谢任何回复。

【问题讨论】:

【参考方案1】:

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

你怎么知道传递给recv()的缓冲区有多大?

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

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

如果recv 收到的数据包大于缓冲区会怎样?

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

SOCK_DGRAM:多余的字节被丢弃。

我怎样才能知道我是否收到了整条消息?

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

SOCK_DGRAM:单个recv 调用始终返回单个数据报。

有没有办法让缓冲区没有固定的空间量,这样我就可以继续添加它而不必担心空间不足?

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

【讨论】:

我正在使用的协议中的消息末尾有一个“/r/n/r/n”。而且我有一个do while循环,在我调用recv的内部,我将消息放在recv_buffer的开头。我的 while 语句看起来像这样 while((!(strstr(recv_buffer, "\r\n\r\n")); 我的问题是,一个 recv 是否有可能获得 "\r\n" 并在next recv 得到 "\r\n",这样我的 while 条件就永远不会实现? 是的。如果您没有完整的消息,则可以通过循环解决该问题,并将下一个 recv 中的字节填充到部分消息之后的缓冲区中。您不应该在由recv() 填充的原始缓冲区上使用strstr() - 不能保证它包含一个空终止符,因此它可能会导致strstr() 崩溃。 在 UDP 的情况下,发送 1400 字节以上的 UDP 数据包没有任何问题。分片是完全合法的,并且是 IP 协议的基本部分(即使在 IPv6 中,但始终存在初始发送者必须执行分片)。对于 UDP,如果使用 64 KB 的缓冲区,则始终可以保存,因为任何 IP 数据包(v4 或 v6)的大小都不能超过 64 KB(即使是分段的),这甚至包括标头 IIRC,因此数据将始终是肯定低于 64 KB。 @caf 是否需要在每次调用 recv() 时清空缓冲区?我见过代码循环并收集数据并再次循环它应该收集更多数据。但是,如果缓冲区已满,您是否不需要清空它以避免由于写入传递为缓冲区分配的内存量而导致内存冲突? @Alex_Nabu:你不需要清空它,只要它还有剩余空间,你也不要告诉recv()写比剩余空间更多的字节。跨度> 【参考方案2】:

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

如果数据多于您的缓冲区,它将简单地保存在内核中以供您下次调用recv

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

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

【讨论】:

2 的幂可以在多种方面更有效,强烈建议使用。 在@theatrus 上进行了详细说明,一个显着的效率是模运算符可以用按位和掩码替换(例如 x % 1024 == x & 1023),整数除法可以用移位替换正确的操作(例如 x / 1024 == x / 2^10 == x >> 10)【参考方案3】:

如果您有一个SOCK_STREAM 套接字,recv 只会从流中获取“最多前 3000 个字节”。关于缓冲区的大小没有明确的指导:您唯一知道流有多大的时间是当它全部完成时;-)。

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

要动态增长缓冲区,最初使用malloc 分配它,然后根据需要使用realloc。但这对来自 UDP 源的 recv 没有帮助,唉。

【讨论】:

因为 UDP 总是最多返回一个 UDP 数据包(即使套接字缓冲区中有多个),并且任何 UDP 数据包都不能超过 64 KB(IP 数据包最多可以是 64 KB,即使是分段的),使用 64 KB 缓冲区是绝对安全的,并且可以保证您在 UDP 套接字上的接收期间永远不会丢失任何数据。【参考方案4】:

对于SOCK_STREAM 套接字,缓冲区大小并不重要,因为您只是提取了一些等待字节,并且可以在下一次调用中检索更多。只需选择您能承受的任何缓冲区大小即可。

对于SOCK_DGRAMsocket,你会得到等待消息的合适部分,其余的将被丢弃。您可以使用以下 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()数据报。

【讨论】:

MSG_PEEK|MSG_TRUNC 没有意义。 您希望 MSG_PEEK 查看(不接收)等待消息,以获取其大小(recv 返回实际大小,而不是截断大小),并且您需要 MSG_TRUNC 不会溢出当前缓冲区。获得大小后,您将分配正确的缓冲区并接收(不查看,不截断)等待消息。 IP 协议支持分片,因此数据报可能比单个数据包大 - 它将被分片并以多个数据包的形式传输。 SOCK_DGRAM 不仅是 UDP。 @smokku 在 Linux 上,是的。但不是在大多数其他平台上。 The behavior is platform-dependent @smokku 是的,尽管您最终可能会过度分配,仅此而已。【参考方案5】:

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

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

然而,并非所有大数据包都可以被网络设备正确路由,请参阅现有讨论以获取更多信息:

What is the optimal size of a UDP packet for maximum throughput? What is the largest Safe UDP Packet Size on the Internet

【讨论】:

【参考方案6】:

16kb 差不多;如果您使用的是千兆以太网,每个数据包的大小可能为 9kb。

【讨论】:

TCP 套接字是流,这意味着一个 recv 可能会返回多个数据包累积的数据,因此数据包大小与 TCP 完全无关。在 UDP 的情况下,每个 recv 调用最多返回一个 UDP 数据包,这里数据包大小是相关的,但正确的数据包大小约为 64 KB,因为如果需要,UDP 数据包可能(并且通常会)被分段。但是,任何 IP 数据包都不能超过 64 KB,即使有分片也不行,因此 UDP 套接字上的 recv 最多可以返回 64 KB(对于当前数据包,未返回的将被丢弃!)

以上是关于在套接字库中调用 recv 时,我的 recv 缓冲区应该有多大的主要内容,如果未能解决你的问题,请参考以下文章

recv 问题时套接字关闭

远程主机终止后,Recv() 调用挂起

Ruby在recv时检测套接字关闭

套接字之recv系统调用

Python 2.7.11 Socket 库缺少对象?

什么可能导致非阻塞套接字阻塞“recv”?