Windows 中套接字发送缓冲区的大小是多少?

What is the size of a socket send buffer in Windows?

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

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

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

我执行了以下测试(发送了 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() 立即 returned,这是否意味着发送缓冲区的大小至少为 1 GB?

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


编辑: 我也尝试连接到 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()按预期工作,而是[=64] =] 将发送缓冲区设置为 0 使其阻塞(这是我注意到的,我没有关于此行为的任何文档)。

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

In a blocking socket, the send() function does not return until the entire data supplied to it has been placed into the send buffer.

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

So what is the size of the send buffer?

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

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

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

一个有趣的功能是,如果您在第一次发送后立即进行第二次发送,则该调用不会return直到两次发送都完成。发送完成后,第一次发送的缓冲区 space 将被释放,但第二次发送 () 会继续阻塞,直到所有数据都已传输。

我怀疑行为上的差异是因为对 send() 的第二次调用在第一次发送完成时已经阻塞。第三次调用 send() returns(并分配了 1GB 缓冲区 space),就像第一次调用一样,依此类推。

所以我得出结论,问题("how large are the send buffers?")的答案是"as large as Windows sees fit"。结果是,为了避免耗尽系统内存,您应该将阻塞发送限制在不超过几百兆字节。

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

总而言之,观察到的行为是 send() 将 return 立即提供:

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

否则,一旦数据发送完成,它将return。

KB214397 描述了其中的一些 - 谢谢汉斯!特别是它描述了将缓冲区大小设置为零会禁用 Winsock 缓冲,并评论说 "If necessary, Winsock can buffer significantly more than the SO_SNDBUF buffer size."

(描述的完成通知与观察到的行为不太匹配,我猜这取决于你如何解释 "previously buffered send"。但它很接近。)

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

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

调用 send() 时,可能会发生两种情况:

  • 如果有低于 SO_SNDBUF 的未决数据,那么 send() 会立即 return(无论您是发送 5 KB 还是您正在发送 500 MB)。

  • 如果有高于或等于 SO_SNDBUF 的挂起数据,则 send() 将阻塞,直到发送了足够的数据以将挂起数据恢复到低于 [=11] =].

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


现在回到你的主要问题"What is the size of a socket send buffer in Windows?"。我想如果你有足够的内存,它可以在必要时增长到超过 1 GB(虽然不确定最大限制是多少)。