SO_SNDBUF和SO_RCVBUF是什么?

57
请问什么是SO_SNDBUFSO_RCVBUF选项?
这两个选项通常用于设置套接字(socket)的发送缓冲区和接收缓冲区大小。缓冲区是操作系统用来存储传输数据的内存区域,发送缓冲区用于存储等待发送的数据,而接收缓冲区则用于存储等待应用程序读取的数据。
每个套接字都有自己的发送缓冲区和接收缓冲区,可以使用setsockopt()函数来设置它们的大小。与传输层的缓冲区(如TCP缓冲区)相比,这两个缓冲区的作用范围更为具体,只涉及到套接字本身。
在使用流式套接字(如TCP)和无连接套接字(如UDP)时,这两个缓冲区的行为和作用可能会有所不同。关于这方面的详细信息,建议参考相关的文献。
如果您需要更多的信息,可以查看这篇好的文章。
5个回答

89
"SO_"前缀代表"socket option",因此这些是针对每个套接字缓冲区的套接字设置。通常有系统范围的默认值和最大值。
"SO_RCVBUF"更容易理解:它是内核分配给给定套接字的缓冲区大小,用于在数据到达网络并由拥有此套接字的程序读取之间的时间内保存。 对于TCP,如果数据到达并且您没有读取它,则缓冲区将填满,并且发送方将被告知减慢速度(使用TCP窗口调整机制)。 对于UDP,一旦缓冲区已满,新数据包将被丢弃。
我认为“SO_SNDBUF”仅对TCP有影响(在UDP中,无论您发送什么都会直接发送到网络)。 对于TCP,如果远程端未读取,则可以填充缓冲区(因此远程缓冲区变满,然后TCP将此事实传达给您的内核,您的内核停止发送数据,而是在本地缓冲区中累积数据直到其填满)。 或者,如果存在网络问题并且内核未收到发送的数据的确认,则缓冲区可能会填满。 然后,它将减慢在网络上发送数据的速度,直到最终传出缓冲区填满。 如果是这样,应用程序对此套接字的未来“write()”调用将阻塞(或者如果您设置了“O_NONBLOCK”选项,则返回“EAGAIN”)。
这一切最好在Unix Network Programming书中描述。

6
UDP 看起来是直接进入网络,但由于基本安全原因、性能、流量控制等原因,并非如此,因此 SO_SNDBUF 对 UDP 也很重要。请参见 https://dev59.com/ym855IYBdhLWcg3wvnKE#4165711。 - MarcH
2
在UDP中,SO_SNDBUF可以确定最大数据报大小。它不会直接发送到网络。 - user207421
2
SO_SNDBUF很重要,即使是对于UDP也是如此。如果发送缓冲区有空间,则可以在短时间内比NIC的线速率更快地“发送”。一旦缓冲区已满,则您的调用将因等待NIC上的空间而减慢到UDP的输出速率。这确保了您的应用程序不会在例如100 Mb/s NIC上毫无意义地自我销毁。O_NONBLOCK也可能在此起作用,我需要重新检查细节。 - jschultz410

23
他们的角色是什么(一般来说)?
当你想要通过套接字发送数据时,数据会被复制到套接字的发送缓冲区,因此你的代码不必等待数据真正从网络中发送出去才能继续执行。当发送调用成功返回时,这仅意味着数据已被放置到发送缓冲区中,协议实现将在准备好发送该数据时从中读取它。
请记住,多个进程的多个套接字可能同时想要发送数据,但在任何时候只能发送一个数据包。当发送正在进行时,所有其他发送者都必须等待,一旦线路空闲,实现每次只能处理一个发送请求。
从网络上到达的数据由协议实现写入套接字的接收缓冲区中,在那里等待你的代码从那里读取它。否则,所有的接收都必须停止,直到你的代码处理了传入的数据包,然而,你的代码可能在后台处理一个数据包到达的同时做其他事情,而且接口是共享的,所以系统必须避免其他进程无法接收其网络数据,因为你的进程拒绝处理自己的传入数据。
它们是每个套接字独有的缓冲区吗?
是的。每个套接字都有自己的一组缓冲区。

Transport层的缓冲区(例如TCP缓冲区)和这些缓冲区之间是否有连接?

如果您指的是TCP接收和发送窗口,那么答案是肯定的。

TCP会定期告诉另一端接收缓冲区还剩多少空间,这样另一端就不会发送超过接收缓冲区容量的数据。如果接收缓冲区已满,则另一端将完全停止发送,直到有空间为止,只要您读取了部分数据。

因此,如果您无法频繁地读取数据以防止套接字缓冲区被填满,则增加接收缓冲区大小可以避免TCP连接暂停发送数据。

另一方面,如果发送缓冲区已满,套接字将不会接受来自代码的更多数据。任何尝试发送的操作都会在没有空间时被阻塞或失败(非阻塞套接字),直到有空间为止。

TCP 只能使用当前在发送缓冲区中的数据进行工作,因此发送缓冲区的大小也会影响 TCP 的发送行为。TCP 发送策略可能取决于各种因素,其中之一是已知要发送的数据量。例如,如果您的发送缓冲区只有 2 KB,那么 TCP 将永远不会看到超过 2 KB 的发送数据,尽管您的应用程序可能知道还有更多数据要跟随。如果您的发送缓冲区为 256 KB,并且您将 128 KB 的数据放入其中,则 TCP 将知道必须为该连接发送 128 KB 的数据,这可能会(并且极有可能)影响 TCP 使用的发送策略。

当使用流式套接字(TCP)和使用无连接套接字(UDP)时,它们是否有不同的行为/角色?

是的。因为对于 TCP,您发送的数据只是一串字节流。字节与被发送的数据包之间没有关系。发送 80 字节可能意味着发送一个具有 80 字节的数据包,也可能意味着发送 10 个每个 8 字节的数据包。TCP 将自行决定。传入的数据也同理。如果您的接收缓冲区中有 200 字节,您无法知道这些字节是如何到达的,从 TCP 套接字中读取的字节数可能使用任意数量的数据包进行传输。因此,尽管通过基于数据包的网络以块传输数据,TCP 连接的行为就像是串行线路链接。

另一方面,UDP发送数据报。如果您将80字节放入UDP套接字的发送缓冲区中,则这80字节一定会以单个包发送出去,其中包含80字节的有效负载数据。数据发送的方式与将其写入发送缓冲区的方式完全相同。如果您一个接一个地写入80个字节,则会发送80个包,每个包都包含一个字节。 如果您告诉TCP套接字要发送200字节但发送缓冲区中只有100字节的空间,那么TCP将向缓冲区添加100字节,并让您知道添加了100个字节中的100个字节。 另一方面,UDP会阻塞或失败并显示错误,因为所有的200字节要么全部适合要么全部不适合; UDP没有部分适合。

此外,当接收时,数据报存储在UDP接收缓冲区中,而不是字节。如果TCP套接字首先接收到80字节数据,然后再接收到200字节数据,您可以执行读取调用来一次性读取所有280字节。如果UDP套接字首先接收到带有80字节的数据报,然后是带有200字节的数据报,并且您请求从中读取280字节,则您只能得到80字节,因为由读取调用返回的所有数据必须来自同一数据报。 您无法跨越数据报边界读取。 还要注意,如果您仅请求读取20个字节,则会接收到数据报的前20个字节,另外60个字节将被丢弃。下一次读取数据时,它将来自大小为200字节的下一个数据报。

因此,两个句子的区别在于:TCP套接字将字节存储在套接字缓冲区中,而UDP套接字将数据报存储在套接字缓冲区中。并且数据报必须完全适合缓冲区,无法完全适合套接字缓冲区的传入数据报会被静默丢弃,即使缓冲区还有一些空间可用。


这是一个非常好的答案。谢谢! - xpepermint
非常好的回答!非常感谢你。这是一堂优秀的课程。 - Bruno Lebtag

8
在Windows中,发送缓冲区确实会影响UDP。如果你以比网络传输速度更快的速度发送数据包,最终你会填满套接字输出缓冲区,并且SendTo操作将失败并返回 "would block" 错误。增加SO_SNDBUF可以解决这个问题。为了找到在Windows和Linux之间可以发送的最大数据包速率,我不得不同时增加发送和接收缓冲区大小进行测试。我本可以通过检测 "would block" 错误代码、等待一段时间然后重试来处理发送大小。但是增加发送缓冲区大小更简单。在Windows中,默认值为8K,在现在有GB内存的PC时代看起来过于小气!

3
大缓冲区在负载下会引入延迟(滞后),请参考“缓冲膨胀”(bufferbloat)的研究。可用内存大小并不相关。 - MarcH

7

在谷歌上搜索“SO_RECVBUF msdn”给我带来了这个链接:

http://msdn.microsoft.com/en-us/library/ms740476(VS.85).aspx

从选项表中可以看到以下内容,回答了你的“它们是每个套接字”的问题:

SO_RCVBUF int Specifies the total per-socket buffer space reserved for receives.
SO_SNDBUF int Specifies the total per-socket buffer space reserved for sends.

稍后将提供更详细的信息:

SO_RCVBUF和SO_SNDBUF

当Windows套接字实现支持SO_RCVBUF和SO_SNDBUF选项时,应用程序可以请求不同的缓冲区大小(较大或较小)。即使实现没有提供完整请求的数量,setsockopt调用也可能成功。应用程序必须使用相同的选项调用getsockopt来检查实际提供的缓冲区大小。


0

以上的回答并没有回答所有问题,特别是关于Socket缓冲区和TCP缓冲区之间的关系。

我认为它们在不同的层次上是不同的东西。TCP缓冲区是Socket缓冲区的消费者。

Socket缓冲区(输入和输出)是一种IO缓冲区,由用户空间中应用程序代码的系统调用访问。例如,使用输出缓冲区,应用程序代码可以

  • 在缓冲区满之前立即发送数据,并在缓冲区满时被阻塞。
  • 设置缓冲区大小。
  • 将缓冲区中的数据刷新到底层存储(TCP发送缓冲区)。
  • 通过关闭流来关闭输出缓冲区。

TCP缓冲区(发送和接收)位于内核空间,只有操作系统才能访问。例如,使用TCP发送缓冲区,TCP协议实现可以

  • 发送数据包并接受ACK。
  • 保证数据包的传递和排序。
  • 通过调整飞行数据包窗口来控制拥塞。

顺便说一下,UDP协议没有缓冲区,但UDP套接字仍然可以有IO缓冲区。

这些是我的理解,我非常乐意得到任何反馈/修改/纠正。


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