【问题标题】:How to send and receive data up to SO_SNDTIMEO and SO_RCVTIMEO without corrupting connection?如何在不破坏连接的情况下发送和接收高达 SO_SNDTIMEO 和 SO_RCVTIMEO 的数据?
【发布时间】:2015-06-13 18:11:15
【问题描述】:

我目前正在计划如何为 TCP 服务器开发一个中间人网络应用程序,该应用程序将在服务器和客户端之间传输数据。它将充当服务器的常规客户端和远程客户端的服务器,而无需修改任何数据。它将可选地用于检测和测量服务器或客户端在连接处于非活动状态时无法接收准备接收的数据的时间。

我打算使用阻塞发送和接收功能。在进行任何数据传输之前,我会调用 setsockopt 函数将 SO_SNDTIMEO 和 SO_RCVTIMEO 设置为大约 10 - 20 毫秒,假设它将强制阻塞发送和接收函数提前返回,以便路由另一个活动连接数据。每个连接运行线程看起来太昂贵了。我不会在这里使用异步套接字,因为我无法保证它们会在几秒钟内完成,尤其是在发送或接收大量数据时。高数据延迟看起来不太好。我会在这里使用非常小的缓冲区,但是为每个接收到的字节调用函数看起来有点过头了。

我的下一个假设是,如果之前已因超时终止并且接收的数据少于请求的数据,那么稍后调用 send 或 recv 是安全的。

但我对 msdn 提供的相互矛盾的信息感到困惑。

发送函数

https://msdn.microsoft.com/en-us/library/windows/desktop/ms740149%28v=vs.85%29.aspx

如果没有发生错误,send 返回发送的总字节数,即 可以小于 len 参数中请求发送的数量。


SOL_SOCKET 套接字选项

https://msdn.microsoft.com/en-us/library/windows/desktop/ms740532%28v=vs.85%29.aspx

SO_SNDTIMEO - 阻塞发送调用的超时时间,以毫秒为单位。 此选项的默认值为零,表示发送 操作不会超时。如果阻塞发送调用超时,则 连接处于不确定状态,应关闭。

我的假设是否正确,我可以像这样使用这些功能?也许有更有效的方法来做到这一点?

感谢解答

【问题讨论】:

  • SO_SNDTIMEO 的文档用非常清楚的语言与您的假设相矛盾。尊重文档。

标签: c++ c sockets network-programming


【解决方案1】:

正如之前的海报所建议的,我强烈建议您重新考虑服务器的设计,以便它采用异步 I/O 策略。这很可能需要您花大量时间了解每个操作系统的首选方法。这将是美好的时光。

对于除玩具应用程序之外的任何应用程序,以您建议的方式使用阻塞 I/O 将不会很好地执行。即使超时时间很短,在我看来,在您完成当前连接的工作之前,您将无法为新连接提供服务。您还可能会发现(在短暂的超时情况下)您在等待工作时消耗的 CPU 时间比实际工作要多。

之前的一张海报明智地建议查看 Windows I/O 完成端口。看看我在 2007 年为 Dobbs 博士写的这篇文章。它并不完美,但我试图做一个体面的工作来解释如何设计一个使用小线程池来处理潜在大量连接的简单服务器:

Windows I/O 完成端口 http://www.drdobbs.com/cpp/multithreaded-asynchronous-io-io-comple/201202921

如果您使用的是 Linux/FreeBSD/MacOSX,请查看 libevent:

Libevent http://libevent.org/

最后,Michael Donahoe 和 Kenneth Calvert 合着的“Practical TCP/IP Sockets in C”是一本关于编写 TCP/IP 服务器和客户端的优秀实用书籍。您还可以查看 W. Richard Stevens 的文章(它完全涵盖了 UNIX 的主题。)

总之,我认为您应该花一些时间来了解更多关于异步套接字 I/O 以及用于开发服务器的成熟、同类最佳方法的信息。

如果您有任何疑问,请随时私信我。

【讨论】:

  • 也可以(并且在某些情况下也可能需要)使用来自 1 个线程(没有线程池)的 IO 完成端口。但是,从现有的文档来源中找出一个有点困难。我之所以提到这个,主要是因为如果你去超高性能(这个问题的作者可能不会尝试),你最终会在用户模式调度领域,避免上下文切换和可扩展性。对于“正常的高性能”,线程池方法可能是不错的选择。
  • +1 完全同意。事实上,对于 I/O 绑定系统,这很可能是这样,您可能根本看不到 >1 线程的好处。用 libevent 编写的服务器使用单线程模型,而我在 FactSet 工作的地方,我们为某些应用程序提供了类似的框架——I/O 由主线程处理,长时间运行的工作被委托给后台线程游泳池。
【解决方案2】:

虽然您可能会按照您在问题中给出的想法实施某些东西,但在所有主要系统上都有更好的替代方案。

即:

  • FreeBSD 和家族上的 kqueue。在 MAC OSX 上。
  • epoll 在 linux 和相关类型的操作系统上。
  • Windows 上的 IO 完成端口。

使用这些技术,您可以处理多个套接字上的流量,而无需超时逻辑并以高效、被动的方式轮询。它们都可以被认为是套接字 API 中古老的 select() 函数的继承者。

至于您的问题中引用的 send() 文档,它并没有真正令人困惑或矛盾。有用的网络协议实现了一种机制,以在发送方尝试发送的数据超出接收方(和/或传输通道)所能容纳的数据的情况下创建“背压”。因此,如果网络堆栈已为它准备好缓冲区空间,应用程序只能向 send() 提供更多数据。

例如,如果一个应用程序尝试发送 3Kb 的数据,而 tcp/ip 堆栈只有 800 个字节的空间,send() 可能会成功并返回它使用了 3k 提供的字节中的 800 个字节。

在连接上转发数据的基本方法是:在知道可以将数据发送到传出套接字之前,不要从传入套接字读取。如果你贪婪地阅读(并在应用层缓冲),你就剥夺了通信通道的背压机制。

所以基本上,“发送能力”应该驱动接收操作。

至于这个“中间人”使用超时,主要有2个场景:

  1. 您知道发送方应用程序的发送行为。 IE。如果它有意在您选择的接收超时范围内随时发送任何数据。一些应用程序只是偶尔发送,任何选择的接收超时值都可能是错误的。即使它应该以特定的时间间隔发送,一旦有人调试发送应用程序,您的超时也会造成麻烦。
  2. 您希望“中间人”为未知应用程序工作(当然,这些应用程序不能使用一些加密让中间人有机会)。在那里,您无法选择任何“适当的”超时值,因为您对相关应用程序的发送行为一无所知。

【讨论】:

  • 这回答了我可以使用的更可取的替代方案。但是你没有回答这个超时逻辑是否对 Windows 系统有效。
  • 答案应该告诉我们,对常规操作使用超时并不是最好的主意。
  • 这将用于终止应该关闭的 HTTP 服务器的连接。查看相关问题serverfault.com/questions/680855/…
猜你喜欢
  • 1970-01-01
  • 2011-04-15
  • 1970-01-01
  • 2021-09-12
  • 1970-01-01
  • 2012-04-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多