使用 Winsock 的 send()/recv() 时是不是需要确认响应?

Posted

技术标签:

【中文标题】使用 Winsock 的 send()/recv() 时是不是需要确认响应?【英文标题】:Is acknowledgment response necessary when using send()/recv() of Winsock?使用 Winsock 的 send()/recv() 时是否需要确认响应? 【发布时间】:2011-09-13 05:16:37 【问题描述】:

使用 Winsock、C++,我通过 send()/recv()、TCP 连接发送和接收数据。我想确定数据是否已经传递给对方,想知道是否建议(如果)用recv接收数据后发回一些确认消息。

这里有两种可能,请指教:

    如果 send 返回传递缓冲区的大小,则假定数据至少已传送到线路另一端的 recv 函数。当我说“至少”时,我的意思是即使 recv 在那里失败(例如由于缓冲区不足等),我不在乎,我只是想确定我已经完成了我的服务器部分工作正常 - 我已完全发送数据(即数据到达另一台机器)。

    使用附加确认:在接收到带有 recv 的数据后,发回接收到的数据包的一些 ID(发送的每个数据的标头的一部分),表明该数据包的成功接收操作。如果在一段时间后我没有收到这样的“确认消息”,则从发送函数返回失败代码。

第二个答案看起来更安全,但如果它是多余的,我不想让传输协议复杂化。另请注意,我说的是 TCP 连接(它本身比 UDP 更安全)。

是否有任何其他机制(可能是其他一些 API?也许 WSARecv()/WSASend() 工作方式不同?)确保将数据传递到 recv另一边的功能?

如果你推荐第二种方式,你能否给我一些代码 sn-p 允许我使用 recv 超时来接收确认? recv 是一个阻塞操作,因此如果之前的发送尝试失败(未通知对方),它将永远挂起。是否有任何简单的方法可以在超时的情况下使用 recv (无需每次都创建单独的线程,这可能是每个 send 操作的过度杀伤力)。

另外,我传递给 send 函数的数据量可能很大(几兆字节),那么如何选择“确认消息”的超时时间呢?也许我应该“拆分”大缓冲区并使用多个 send 调用?我觉得会很复杂,请指教!

编辑:好的,你们建议 TCP/IP 堆栈将处理它(即不需要手动确认),但这是我在 MSDN 页面上找到的:“send 的成功完成函数并不表示数据已成功发送和接收到接收方。此函数仅表示数据已成功发送。"因此,即使 TCP 机制有能力确保数据传递,我也无法通过 send() 函数或我知道的任何其他 Winsock 函数获得该状态(成功与否)。您知道从 TCP 层获取状态的任何方法吗?再次 - send() 函数的返回值似乎还不够!

================================================ =========

编辑 2:好的,我认为我们同意即使 TCP 协议在出现问题时考虑错误处理,但 Winsock 的 send() 函数无法报告错误(仅仅是因为它在网络驱动程序开始实际传输数据之前返回)。所以这里有一个百万美元的问题:Winsock 的 send() 函数是否至少确保在当前数据包发送之前不会有其他数据包发送给对方?换句话说,如果由于某些网络故障而发送失败(但 send() 调用未报告),则网络故障将在下一次 send() 调用之前修复 strong> 功能与下一个数据块,是否确保前一个数据包(失败但 send() 未报告)将在下一个数据包之前传递?换句话说,一个特定的 send() 函数是否有可能“静默”失败,以便后续的 send() 调用会成功但第一个数据包会丢失?再次 - 我不是在谈论 TCP 级别,我是在谈论 Winsock API 级别!

【问题讨论】:

你要一匹小马。 TCP 保证交付。它会,它需要 send() 缓冲区并立即返回,承诺交付它。但是对于 8.5 级地震之后的海啸没有很好的解决方法。或有人绊倒电源线。这有关系吗?当这种情况发生时,没有更大的事情需要担心吗?如果你说“不!”那就不要使用 TCP。 记住数据包仅以光速传播可能是明智之举。 50 毫秒的行程时间并不罕见。如果操作系统希望每秒发送超过 20 个数据包 (~30 kB/s),则操作系统必须在第一个数据包到达之前发送第二个数据包。显然,在发送第二个数据包之前,操作系统不能等待 100 毫秒来确认第一个数据包到达。您的 LAN 可能更快,例如 100 us,但在每个数据包后等待 200 us 会将您的 LAN 限制为 5000 个数据包/秒 = 7.5 MB/秒。也不是很好。 【参考方案1】:

这里的现有答案大多是正确的:如果您使用 TCP,您真的不需要担心您的数据包是否可靠地传递给您的对等方。

但是对于一些系统来说,这是一个危险的观点,其中必须将数据完整性提升到一个新的水平:通用标准审计要求FAU_STG.4.1需要能够防止可审计事件 em> 如果审计日志可能会丢失审计条目。 (例如,Linux auditd(8) 审计日志守护进程可以配置为将计算机置于单用户模式或在没有剩余空间用于审计日志时完全停止系统。)来自远程系统的审计日志可能应该是一直保持到知道它们已成功写入集中式日志服务器。

最好使用比简单 TCP 更可靠的协议来处理金融交易——贷记或借记帐户最好使用多阶段协议来处理,以确保资金的可用性、执行交易,然后报告结果交易的起始点。

TCP 允许在两个对等方之间使用nearly a gigabyte of in-flight data(在极端条件下);根据您的应用程序的要求,您可能需要在发送端维护该数据,直到您收到来自您的对等方的确认数据已得到正确处理。

谢天谢地,大多数应用程序都不是那么重要。在“将来”某个时间点报告关闭连接的套接字在这里或那里丢失一兆字节的数据真的并不可怕——我们只是重新尝试我们的 HTTP 请求,或重新尝试 SFTP 连接。

更新

套接字将只接受足够的数据来填充其可用的窗口。窗口大小在会话握手期间在两个对等方之间协商。因此,当套接字的窗口填满时,您对 send() 的调用将开始阻塞。 (操作系统可能也会继续让您将数据添加到其内部缓冲区,但在某些时候写入会阻塞。)如果对等端使用 RSTICMP Unreachable 消息中断连接, 未来send()的调用将返回连接重置或损坏管道的错误值。

更新 2

我说的不是 TCP 级别,我是说 Winsock API 级别

这可能是混乱的根源。 send() 与 TCP 一起使用时别无选择,只能遵守 TCP 行为。

TCP 保证字节流的有序可靠传递,在一定程度上可以传递数据包。 (请参阅@Hans 关于小马和粗心的人踢电源线的评论。)对等程序以正确的发送顺序查看字节。 (嗯,好吧,TCP 有out-of-band 紧急数据包传递,但我实际上还没有看到任何使用它的应用程序。使用 OOB 数据包,您可以获得一些数据外线。忘了我提过。)

如果远程程序接收到通过 TCP 流发送的字节,它也会可靠地接收到所有前面的字节。 (好吧,replay attacks 的整个类将远程对等方的合法和虚假数据包拼接在一起,但是在具有随机初始序列号的系统上,这些越来越困难。如果这在您的威胁模型中,您应该使用 @987654324 @ 在 TCP 之上提供加密的强防篡改信息。但 TLS 无法提供更好的每个数据包传递通知。)

【讨论】:

我想我明白了。您能否查看我的最后一次编辑(主要问题)并确认我可以信任 Winsock 的上述行为? IE。如果一个数据包失败,则在重新发送之前不会传递其他数据包! +1,一个很好的总结。不过要注意的一件事是,如果您使用异步发送调用而不是潜在的阻塞调用,它可能会有所不同,请参阅此处:serverframework.com/asynchronousevents/2011/06/… 了解详情。 @Len,好文章,感谢您传递它。不过我很好奇,真的没有 API 可以通知程序哪些套接字准备接受更多数据吗? POSIX select() API 可以很容易地避免过度填充发送缓冲区,我希望在 Windows 上也可以使用类似的东西以避免变通办法。 SIO_IDEAL_SEND_BACKLOG_QUERY 让您知道排队的理想数量是多少——但这是一篇尚未写完的文章的主题。为了获得最高的重叠性能,您可能无论如何都希望关闭 TCP 堆栈发送缓冲区,请将它们设置为零......在这种情况下,您纯粹是在客户端广告的 TCP 窗口上工作。此外,为了获得最佳性能,您需要已经提交并等待额外的缓冲区;诀窍是不要有太多:)【参考方案2】:

我认为您过于复杂了,请相信您的 TCP/IP 软件堆栈及其提供的可靠交付。 TCP 套接字对数据流而不是数据包进行操作。此外,拨打send 并不能保证拨打recv

【讨论】:

你在自相矛盾。您说“一次调用 send 并不能保证一次调用 recv”,所以我不能仅根据 send() 的返回值来确定 对吧?那么我如何“信任我的 TCP/IP 软件堆栈”呢?即我怎么知道操作是否失败? (也请查看我的编辑)。【参考方案3】:

如果您使用 UDP 并且您关心对方实际接收到的数据,则您需要使用 ACK,但如果您不需要 UDP 的速度,您应该使用 TCP,因为它会为您进行 ACKing。

【讨论】:

所以你的意思是send()函数的返回值足以假设发送成功? 是的,如果您使用的是 TCP 连接,TCP 连接会负责接收或不接收数据,这就是 TCP 如此出色的原因:) 请查看我的编辑(主要问题)。以下是我们在 MSDN 上看到的内容:“send 函数的成功完成并不表示数据已成功交付和接收到收件人。此函数仅表示数据已成功发送。”那么如何从 TCP 层本身获取该状态(成功或失败)?好像send()函数的返回值还不够! 只有在你的数据被传送之前断开 TCP 连接才不会——如果你正在使用 TCP,不要担心这个,如果你有一个活动的连接,你可以随时使用keepalive来测试对方是否还活着踢! 我唯一的愿望是确保没有数据包“静默”失败,也就是说,如果一个数据包失败,在重新发送之前不会传递其他数据包!请查看我的最后一次编辑。请确认我是否可以信任 Winsock 的该功能。谢谢!【参考方案4】:

您为什么不信任您的 TCP/IP 堆栈来保证交付。毕竟,这就是使用 TCP 而不是 UDP 的全部意义所在。

【讨论】:

所以你的意思是send()函数的返回值足以假设发送成功?

以上是关于使用 Winsock 的 send()/recv() 时是不是需要确认响应?的主要内容,如果未能解决你的问题,请参考以下文章

面临 recv() 和 send() winsock api 的问题。 Recv() 在接收最后一个数据包时挂起

WinSock WSAEventSelect 模型

winsock api会自动多线程吗?

Select模型

winsock套接字的recv函数

在 C++ 中的 Winsock 中发送 HTTP GET 请求后,Recv() 函数挂起