【问题标题】:What is the size of a socket send buffer in Windows?Windows 中套接字发送缓冲区的大小是多少?
【发布时间】:2015-05-01 09:05:00
【问题描述】:

根据我的理解,每个socket都关联了两个缓冲区,一个发送缓冲区和一个接收缓冲区,所以当我调用send()函数时,发生的情况是要发送的数据会被放入发送缓冲区,现在 Windows 的职责就是将这个发送缓冲区的内容发送到另一端。

在阻塞套接字中,send() 函数在提供给它的全部数据都放入发送缓冲区之前不会返回。

那么发送缓冲区的大小是多少?

我执行了以下测试(发送 1 GB 的数据):

#include <stdio.h>

#include <WinSock2.h>
#pragma comment(lib, "ws2_32.lib")

#include <Windows.h>

int main()
{
    // Initialize Winsock
    WSADATA wsa;
    WSAStartup(MAKEWORD(2, 2), &wsa);

    // Create socket
    SOCKET s = socket(AF_INET, SOCK_STREAM, 0);

    //----------------------

    // Connect to 192.168.1.7:12345
    sockaddr_in address;
    address.sin_family = AF_INET;
    address.sin_addr.s_addr = inet_addr("192.168.1.7");
    address.sin_port = htons(12345);
    connect(s, (sockaddr*)&address, sizeof(address));

    //----------------------

    // Create 1 GB buffer ("AAAAAA...A")
    char *buffer = new char[1073741824];
    memset(buffer, 0x41, 1073741824);

    // Send buffer
    int i = send(s, buffer, 1073741824, 0);

    printf("send() has returned\nReturn value: %d\nWSAGetLastError(): %d\n", i, WSAGetLastError());

    //----------------------

    getchar();
    return 0;
}

输出:

send() has returned
Return value: 1073741824
WSAGetLastError(): 0

send() 立即返回,这是否意味着发送缓冲区的大小至少为 1 GB?

这是关于测试的一些信息:

  • 我正在使用 TCP 阻塞套接字。
  • 我已连接到 LAN 计算机。
  • 客户端 Windows 版本:Windows 7 Ultimate 64 位。
  • 服务器 Windows 版本:Windows XP SP2 32 位(安装在 Virtual Box 上)。

编辑:我也尝试连接到 Google (173.194.116.18:80),得到了相同的结果。

编辑 2:我发现了一些奇怪的东西,将发送缓冲区设置为 64 KB 到 130 KB 之间的值将使 send() 按预期工作!

int send_buffer = 64 * 1024;    // 64 KB
int send_buffer_sizeof = sizeof(int);
setsockopt(s, SOL_SOCKET, SO_SNDBUF, (char*)send_buffer, send_buffer_sizeof);

编辑 3: 事实证明(感谢 Harry Johnston)我以不正确的方式使用了 setsockopt(),这就是它的使用方式:

setsockopt(s, SOL_SOCKET, SO_SNDBUF, (char*)&send_buffer, send_buffer_sizeof);

将发送缓冲区设置为 64 KB 和 130 KB 之间的值不会使 send() 按预期工作,而是将发送缓冲区设置为 0使它阻塞(这就是我注意到的,我没有任何关于这种行为的文档)。

所以我现在的问题是:我在哪里可以找到关于send()(可能还有其他套接字操作)如何在 Windows 下工作的文档?

【问题讨论】:

  • 如果没有错误,send返回发送的总字节数,可以小于len参数中请求发送的字节数。 msdn.microsoft.com/en-us/library/windows/desktop/…
  • 您可以通过getsockopt() 发现自己的尺寸。你怎么知道它立即返回?那个代码不能告诉你。
  • 你可以告诉它立即返回(错误或无错误),因为他发送了一个 1GB 的缓冲区。除非您拥有超快的互联网,否则这将花费他很长时间。很可能失败了。
  • @Brandon 您无法从此代码中知道发送何时开始和结束。那里还有一个connect(),这可能需要相当长的时间。他没有使用互联网,他使用的是局域网。
  • @EJP 其实我连接了谷歌(173.194.116.18:80),send() 也立即返回,没有错误。

标签: c++ windows sockets winapi tcp


【解决方案1】:

在调查了这个问题之后。这是我认为正确的答案:

调用send() 时,可能会发生两件事:

  • 如果有低于SO_SNDBUF 的待处理数据,则send() 将立即返回(无论您发送5 KB 还是发送500 MB 都没有关系)。

  • 如果存在大于或等于SO_SNDBUF 的待处理数据,则send() 将阻塞,直到发送了足够的数据以将待处理数据恢复到SO_SNDBUF 以下。

请注意,此行为仅适用于 Windows 套接字,不适用于 POSIX 套接字。我认为 POSIX 套接字只使用一个固定大小的发送缓冲区(如果我错了,请纠正我)。


现在回到您的主要问题“Windows 中套接字发送缓冲区的大小是多少?”。我猜如果你有足够的内存,它可能会在必要时超过 1 GB(但不确定最大限制是多少)。

【讨论】:

    【解决方案2】:

    我可以重现这种行为,并且使用资源监视器很容易看到 Windows 在 send() 发生时确实分配了 1GB 的缓冲区空间。

    一个有趣的特性是,如果您在第一次发送后立即进行第二次发送,则该调用在 两个 发送完成之前不会返回。一旦发送完成,第一次发送的缓冲区空间就会被释放,但第二次 send() 会继续阻塞,直到所有数据都传输完毕。

    我怀疑行为上的差异是因为当第一次发送完成时,第二次调用 send() 已经阻塞。第三次调用 send() 立即返回(并分配 1GB 的缓冲区空间),就像第一次调用一样,依此类推,交替进行。

    所以我得出结论,问题的答案(“发送缓冲区有多大?”)是“与 Windows 认为合适的一样大”。结果是,为了避免耗尽系统内存,您可能应该将阻塞发送限制为不超过几百兆字节。

    您对 setsockopt() 的调用不正确;第四个参数应该是指向整数的指针,而不是转换为指针的整数。一旦这个问题得到纠正,事实证明将缓冲区大小设置为零会导致 send() 总是阻塞。

    总而言之,观察到的行为是 send() 将立即返回:

    • 有足够的内存来缓冲所有提供的数据
    • 没有正在进行的发送
    • 缓冲区大小未设置为零

    否则,数据发送完毕后返回。

    KB214397 描述了其中的一些 - 谢谢汉斯!特别是它描述了将缓冲区大小设置为零会禁用 Winsock 缓冲,并表示“如有必要,Winsock 可以缓冲比 SO_SNDBUF 缓冲区大小大得多的缓冲区。”

    (所描述的完成通知与观察到的行为不太匹配,这取决于我猜你如何解释“先前缓冲的发送”。但它很接近。)

    请注意,除了无意中耗尽系统内存的风险之外,这些都不重要。如果您确实需要知道另一端的代码是否已经收到您的所有数据,唯一可靠的方法就是让它告诉您。

    【讨论】:

    • 不幸的是,我认为这不是正确的答案。我试过只发送一个 2 MB 的缓冲区,send() 也立即返回(在 2 MB 发送到另一端之前,并不是因为我的连接速度很快,所以它立即返回!)。至于发送第二个大缓冲区,并不是说它是一个大缓冲区,我试图在 1 GB 缓冲区之后发送一个大小仅为 1 KB 的第二个缓冲区,并且我遇到了您描述的相同行为(出现第二个send() 已阻止)。
    • 让这一切变得非常奇怪的是我在我的问题中所说的:将发送缓冲区设置为 64 KB 和 130 KB 之间的值将使 send() 按预期工作,即使在发送第二个缓冲区(send() 将阻塞,直到发送第一个缓冲区,也将阻塞直到发送第二个缓冲区)。
    • 我认为我们需要确定 Windows 如何处理send()(可能还有其他与套接字相关的操作)。因此,当我们创建使用套接字的程序时,我们不会得到不可预测的行为。
    • 是的,你是对的。我忘了在(char*)send_buffer 中的send_buffer 之前加上&amp;!我将再次测试代码。
    • 你是对的,很好的发现!看起来地址 65536 (64 KB) 到 133120 (130 KB) 的内存值是0。但这是send() 在其他操作系统下的工作方式吗?如果这不是标准行为,Microsoft 是否会提供所有这些工作原理的文档?
    【解决方案3】:

    在阻塞套接字中,send() 函数在提供给它的全部数据都放入发送缓冲区之前不会返回。

    不能保证。如果有可用的缓冲区空间,但没有足够的空间容纳整个数据,则套接字可以(并且通常会)接受它可以接受的任何数据并忽略其余数据。 send() 的返回值告诉您实际接受了多少字节。您必须再次致电send() 才能发送剩余数据。

    那么发送缓冲区的大小是多少?

    使用getsockopt()SO_SNDBUF 选项来找出答案。

    使用setsockopt()SO_SNDBUF 选项来指定您自己的缓冲区大小。但是,套接字可能会对您指定的值施加最大上限。使用getsockopt() 找出实际分配的大小。

    【讨论】:

    • 这是不正确的。阻塞模式套接字将阻塞,直到所有数据都传输到套接字发送缓冲区。来源:Posix。
    • 请参阅我的问题中的 Edit 2
    • @EJP:这也不完全正确。如果消息太长而无法以原子方式传递给底层协议,它将失败。因此,它可能 阻塞,但不一定。 (这就是我猜这里发生的事情,我认为这个 1G 发送只是失败了。唯一有趣的是没有报告错误,但很可能 Winsock 是错误的。)
    • @Damon 我不认为send() 失败了。我在另一端得到了整个 1 GB(它立即返回但继续发送!)。我发现了一个奇怪的“解决方案”,可以让 send() 按预期工作,请参阅我的问题中的 Edit 2
    • @Damon 谷歌并没有接受 1 GB 的数据(当然它没有接受!)。 Windows 似乎立即将 1 GB 的数据放入发送缓冲区。在你的机器上试试我的代码,看看会发生什么。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-02-18
    • 2012-04-16
    • 1970-01-01
    • 1970-01-01
    • 2011-06-06
    • 1970-01-01
    • 2017-08-26
    相关资源
    最近更新 更多