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?
这是关于测试的一些信息:
- 我正在使用 TCP 阻塞套接字。
- 我已连接到局域网机器。
- 客户端 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()
按预期工作,而是[=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(虽然不确定最大限制是多少)。
根据我的理解,每个套接字都与两个缓冲区相关联,一个发送缓冲区和一个接收缓冲区,所以当我调用 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?
这是关于测试的一些信息:
- 我正在使用 TCP 阻塞套接字。
- 我已连接到局域网机器。
- 客户端 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()
按预期工作,而是[=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(虽然不确定最大限制是多少)。