【问题标题】:Why need to sleep(1) to allow socket to drain?为什么需要 sleep(1) 以允许套接字耗尽?
【发布时间】:2014-10-06 01:55:07
【问题描述】:

我从以下网站下载了一个简单的静态 Web 服务器的源代码 http://www.ibm.com/developerworks/systems/library/es-nweb/sidefile1.html

但是,我对第 130 行感到困惑:

#ifdef LINUX
sleep(1);       /* to allow socket to drain */    
#endif

exit(1);

既然套接字没有关闭,是否意味着我需要等待客户端关闭套接字?

【问题讨论】:

  • 代码太多要读,sleep在什么情况下使用?通常情况并非如此,您不应该在睡觉,而是在连接关闭或 EOF 之前读取套接字。
  • 程序也需要休眠。
  • 那个服务器是个笑话——不要在愤怒中使用它,不要研究它。 sleep 是一种迷信:不可靠的代码首先在 Linux 上失败了,所以他们做了一些事情并再次尝试,结果恰好奏效了。另外:“ret =read(fd,buffer,BUFSIZE); /* 一次性读取 Web 请求 */” - 实际上需要 recv(..., MSG_WAITALL),因为 read 可能会合法地返回部分消息,因为允许客户端将请求写入它喜欢的任何数据包(这甚至可能是出于瞬时缓冲考虑所必需的),并且数据包可能会被中间的网络硬件拆分(或重新组合)。
  • @codenheim:无论如何,TCP 代码的通用可移植性有点过于雄心勃勃,但在许多情况下,信号处理将使用SA_RESTART 或类似设置(毕竟,有多少代码检查例如@987654328 @ 或 write 在收到信号后申请部分成功?- 虚拟无,因此通常需要重新启动系统才能可靠,但不幸的是,在调用 sigaction 时默认情况下它不是打开的)。如果来自recv任何 错误,关闭连接通常是合理的。
  • @TonyD:这对我来说并不是很有趣,但我真的觉得这是一个非常好的陈述。忽略EINTR,如果您收到错误消息,那还有什么有用的事情要做,您真正关心的是发生了什么。要么对方拒绝了连接(第一次接收失败,ECONNREFUSE),要么你有一个严重的程序错误,比如使用了错误的描述符或内存分配问题。无论哪种方式,您都无法以有意义的方式继续。所以你的陈述非常好。

标签: c++ c sockets


【解决方案1】:

无论作者的意图如何,它都是不必要的不正确的exit() 就足够了。当在 TCP 套接字上调用 close() 或调用 exit() 以终止进程时,除非 SO_LINGER 套接字选项已设置为非默认设置,否则内核将保持套接字处于等待状态状态并尝试传递任何未传递/缓冲的数据。您可以通过 netstat 看到这一点,这就是快速重启不是为快速重启而编写的 TCP 服务器会在快速重新打开端口时出现问题的原因(也有一种正确的方法来完成此操作)。

我不同意接受的答案中的几件事。

close()exit() 应该对套接字产生相同的效果,传统上,如果您要使用exit,是否使用close 套接字只是风格问题。

它应该与溢出 TCP 发送缓冲区无关,因为它发生在所有写入之后。完整的写入缓冲区将通过write() 返回码立即返回错误;最后睡觉将与此无关。

sleep(1) 应该对套接字缓冲区或可靠的数据传输没有影响。如果有的话,这段代码会在写入后限制 Web 服务器子进程,因此确实没有什么好的效果,实际上可能会增加拒绝服务攻击的可能性。

我正在描述默认操作。可以通过许多选项更改默认值。

有关套接字编程的“圣经”,请参阅 W. Richard Steven 的 UNIX 网络编程 - 网络 API:套接字和 XTI,其中详细介绍了这一点。

【讨论】:

  • 我在看书,但是大部分功能都是预先包装好的。我在独立尝试时发现了一些问题,这就是我找到这段代码的原因,因为它们都是在原始 API 中实现的。也许我没有深入阅读这本书,但我会尝试一下。
  • "快速重新启动未正确写入的 TCP 服务器将无法快速重新打开端口(也有适当的方法来完成此操作)。" - 这不是正确写入与未正确写入的情况,它是快速重启和将旧服务器的数据包与新服务器的数据包混淆的微小风险之间的选择,或者缓慢但完全安全的重启。
【解决方案2】:

对我来说,这看起来有点草率。

如果带有打开套接字的进程终止,并且套接字有一些未写入的数据,内核将拆除套接字而不清除未发送的数据。

当您向套接字写入内容时,写入的数据不一定会立即传输。内核维护一个小缓冲区,用于收集写入套接字的数据。或者管道,也是。让进程继续下去会更有效率,然后内核会在有时间的时候负责实际传输写入的数据。

显然,一个进程向套接字写入数据的速度比通过典型网络接口传输的速度要快得多,而且内部套接字缓冲区的大小是有限的,所以如果进程不断向套接字写入数据,在某个时候它会填满内部缓冲区,并且必须等到内核实际传输数据,并从内部缓冲区中删除写入的数据,然后才能写入更多空间。

[*] 我省略了一些技术细节,例如在接收方确认数据之前不会将数据视为已写入。

无论如何,sleep() 调用的目的似乎是在进程终止之前允许内部缓冲区实际传输一些时间,因为如果它在实际数据被写入之前完成,内核赢了就像我刚才提到的那样,不必费心发送它并终止套接字。

这是一个不好的例子。这不是做这些事情的正确方法。套接字应该只是close()d。这将正确地冲洗掉东西,并确保一切都在它应该去的地方。我看不出该示例没有正确关闭套接字而不是从事这种黑客行为的任何正当理由。

【讨论】:

  • 你的意思是写只是写到缓冲区,真正的发送工作是由内核完成的,关闭会刷新缓冲区吗?另一个小问题,会关闭发送FIN并执行TIME_WAIT吗?
  • 我不同意这里的原因是在所有 write() 调用完成后调用 sleep()。它对发送缓冲区没有影响。其次,默认情况下,TCP 套接字是可靠的流。除非您禁用此功能,否则内核将在一段时间内保留流并传递未发送的数据。 exit() 不会绕过可靠传输。它只是导致内核自动关闭进程中的所有套接字,并在每个流上执行与close() 相同的语义。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-01-04
  • 2020-06-02
  • 1970-01-01
相关资源
最近更新 更多