windows下在非阻塞TCP套接字上使用SO_SNDBUF的奇怪行为

Posted

技术标签:

【中文标题】windows下在非阻塞TCP套接字上使用SO_SNDBUF的奇怪行为【英文标题】:Strange behavior using SO_SNDBUF on non-blocking TCP socket under windows 【发布时间】:2011-07-15 20:32:07 【问题描述】:

我正在尝试降低非阻塞 TCP 套接字上的发送缓冲区大小,以便可以正确显示上传进度条,但我看到了一些奇怪的行为。

我正在创建一个非阻塞 TCP 套接字,将 SO_SNDBUF 设置为 1024,验证设置是否正确,然后连接(在调用连接之前和之后尝试此操作没有区别)。

问题是,当我的应用程序实际出现并调用 send(发送大约 2MB)而不是返回大约 1024 个字节已发送时,send 调用显然接受所有数据并返回 2 MB 的发送值(究竟是什么我通过了)。一切正常(这是一个 HTTP PUT,我得到了响应等),但我最终在进度条中显示的是上传以 100% 的速度放置了大约 30 秒,然后响应进来了。

我已经验证,如果我在收到响应之前停止,则上传不会完成,所以它不像它只是上传得非常快然后服务器停止了......有什么想法吗? windows甚至会看这个设置吗?

【问题讨论】:

SO_SNDBUF 是发送缓冲区的最大大小......尝试使用 1 kB 缓冲区一次发送 2MB 对我来说听起来有点奇怪。您可能应该将发送缓冲区增加到 2 MB 以上或使发送小于 1kb。我认为您错误地认为设置 SO_SNDBUF 会将您的 2 MB “剪切”为 1 kB 段并一一发送,您需要自己做。 我的印象是它应该限制可以随时排队的数据量,对吧。基本上我只需要一个指示实际传输了多少数据(而不是排队)。缓冲区的大小与我并不真正相关,我只是想选择足够小的东西,以便我应该看到一些粒度。有一个更好的方法吗? HTTP 没有任何内置的中间上传 ACK afaik。 是的,就是这样。如果只能排队 1kB,则无法发送 2MB。为什么它假装它确实发送了 2 MB 是另一个问题。请记住,无论您做什么,TCP 都可以很好地在您背后合并或拆分您的数据包(请参阅 Nagle)。您应该自己拆分发送,我想在您的情况下应该在 10 到 50 kB 之间。要查看正在进行的上传,请使用 Wireshark 或 Fiddler2 等代理。 【参考方案1】:

Windows 确实会查看此设置,但该设置未按预期工作。 当您设置这些缓冲区的大小时,您实际上是在设置您正在与之通信的实际 NIC 上的缓冲区大小,从而确定要发出的数据包的大小。

关于 Windows,您需要了解的是,在您的调用代码和实际 NIC 之间有一个缓冲区,我不确定您是否可以控制它的大小。如果您在套接字上调用 Send 操作时会发生什么情况,您正在转储该套接字中的数据,Windows 的内核将使用缓冲区中的数据在 NIC 上执行小步发送。

这意味着代码实际上会报告 2MB beeing 'sent',但这只是意味着您的 2MB 数据已成功写入内部缓冲区,并不意味着/保证数据已经发送。

我一直在从事有关视频流和 tcp 通信的类似项目,这些信息可以在 MSDN 论坛和技术网上的某个地方找到,但它需要一些非常详细的搜索来了解它们的实际工作原理。

【讨论】:

【参考方案2】:

我在 Windows 上观察到同样的事情,使用 Java 非阻塞通道。

根据http://support.microsoft.com/kb/214397

如有必要,Winsock 可以缓冲远远超过 SO_SNDBUF 缓冲区大小。

这是有道理的;发送由本地机器上的程序发起,该程序被认为是合作的而不是敌对的。如果内核有足够的内存,那么拒绝发送数据是没有意义的;无论如何,有人必须缓冲它。 (接收缓冲区是给远程程序的,可能是敌对的)

内核对发送数据的缓冲确实有限制。我正在制作一个服务器套接字,内核每次发送最多接受 128K;不像您的示例中用于客户端套接字的 2MB。

同样根据同一篇文章,内核只发送缓冲区 2;下一个非阻塞发送应立即返回报告写入的 0 个字节。所以如果我们每次只发送少量数据,程序会被接收端限制,你的进度指示器会很好地工作。

【讨论】:

【参考方案3】:

该设置不会影响 NIC 上的任何内容;受影响的是内核缓冲区。发送和接收默认为 8k。

您看到的行为的原因是:发送缓冲区大小不是您一次可以发送的数量的限制,它是“标称”缓冲区大小。只有当缓冲区中还有数据等待发送时才会影响后续发送。

例如:

    将发送缓冲区设置为 101 字节

    发送10个字节,会被缓冲

    再发送10个字节,就会被缓冲

    ...继续直到缓冲区中有 100 个字节

    再发送 10 个字节

    此时 WinSock 使用一些逻辑来确定是接受新的 10 字节(并使缓冲区变为 110 字节)还是阻塞。我不记得确切的行为,但它在 MSDN 上。

    再发送 10 个字节

这最后一个肯定会阻塞,直到一些缓冲区空间可用。

因此,从本质上讲,发送缓冲区是相当大的,并且:

WinSock 将始终接受几乎任何大小的缓冲区为空的发送 如果缓冲区有数据,写入会溢出,有一些逻辑来判断是否接受/拒绝 如果缓冲区已满或溢出,它将不接受新的发送

对于含糊不清和缺少链接,我们深表歉意;我有点着急,但碰巧记得我前段时间写的一个网络产品的这些细节。

【讨论】:

以上是关于windows下在非阻塞TCP套接字上使用SO_SNDBUF的奇怪行为的主要内容,如果未能解决你的问题,请参考以下文章

如何在非阻塞套接字上处理 OpenSSL SSL_ERROR_WANT_READ / WANT_WRITE

如何将 TCP 套接字更改为非阻塞?

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

使用 tcp 套接字在 windows phone 7 上解析 xml 内容

在 Windows 7 64 位上使用 TCP/IP 套接字发送/接收结构

C++:如何测量非阻塞套接字的实际上传速率