Linux 操作系统中的 TCP close() 与 shutdown()

Phi*_*ixz 2 linux networking network-programming

我知道在 stackoverflow 中已经有很多类似的问题,但似乎没有什么令人信服的。基本上是想了解在什么情况下我需要使用一种或两种方式。还想了解 close() 和 shutdown() 与 shut_rdwr 是否相同。

hae*_*lix 6

关闭 TCP 连接引起了很多混乱,我们可以理直气壮地说 TCP 的这方面设计很差,或者文档中缺少某处。

简答

要以正确的方式执行此操作,您应该按此顺序使用所有 3: shutdown(SHUT_WR),shutdown(SHUT_RD)close(), 。不,shutdown(SHUT_RDWR)close()不一样。仔细阅读他们的文档和关于 SO 的问题以及有关它的文章,您需要阅读更多内容以获得概述。

更长的答案

首先要澄清的是关闭连接时您的目标什么。据推测,您将 TCP 用于更高级别的协议(请求-响应、稳定的数据流等)。一旦您决定“关闭”(终止)连接,您必须发送/接收、发送和接收的所有内容(否则您不会决定终止)-那么您还想要什么?我正在尝试概述您在终止时可能想要的内容:

  1. 知道在任一方向发送的所有数据都到达了对等方
  2. 如果有任何错误(在您决定终止时以及之后传输正在发送的数据时,以及在执行终止本身时- 这也需要发送/接收数据),则通知应用程序
  3. 可选地,一些应用程序希望在终止之前是非阻塞

不幸的是,TCP 并没有使这些功能易于使用,并且用户需要了解幕后的内容以及系统调用如何与幕后的内容进行交互。recv联机帮助页中有一个关键句子:

   When a stream socket peer has performed an orderly shutdown, the
   return value will be 0 (the traditional "end-of-file" return).
Run Code Online (Sandbox Code Playgroud)

这里的手册页的意思是,有序关闭是由一端(A)选择调用完成的shutdown(SHUT_WR),这会导致向对端(B)发送一个 FIN 数据包,并且该数据包采用Brecv内部的 0 返回码的形式. (注意:FIN 数据包是一个实现方面,手册页没有提到)。手册页调用的“EOF”意味着不会再有从 A 到 B 的传输,但应用程序B可以并且应该继续发送它在发送过程中的内容,甚至可能发送更多(A还在接收)。当发送完成(很快),B应该自己调用shutdown(SHUT_WR)关闭双工的另一半。现在应用程序A收到 EOF 并且所有传输都已停止。这两个应用程序可以调用shutdown(SHUT_RD)关闭它们的套接字以进行读取,然后close()释放与套接字关联的系统资源(TODO 我还没有找到明确的文档 taht 说对 shutdown(SHUT_RD) 的 2 个调用正在发送终止序列中的 ACK FIN --> ACK,FIN --> ACK,但这似乎是合乎逻辑的)。

继续我们的目标,对于 (1) 和 (2) 基本上应用程序必须以某种方式等待关闭序列发生,并观察其结果。注意一下,如果我们按照上面的小协议,很显然这两个应用程序的终止引发剂()已派出一切。这是因为B收到了 EOF(并且只有在其他所有事情之后才收到 EOF)。A也收到了 EOF,它是为了回复它自己的 EOF 而发出的,所以A知道B收到了所有东西(这里有一个警告——终止协议必须有一个关于谁发起终止的约定——所以不是两个对等方同时这样做) . 然而,反之则不然。后通话shutdown(SHUT_WR),没有什么回来了应用级的,告诉一个接收的所有数据发送时,加上FIN(的A-> B传输已经停止!)。如果我错了,请纠正我,但我相信在此阶段B处于“LAST_ACK”状态,当最终 ACK 到达时(4 次握手的第 4 步),结束关闭,但不会通知应用程序,除非它已将 SO_LINGER 设置为足够长的超时时间。SO_LINGER "ON" 指示关闭调用阻塞(在前台执行),因此关闭调用本身将进行等待。

总之,我建议将 SO_LINGER ON 配置为长时间超时,这会导致它阻塞并因此返回任何错误。不完全清楚的是,是shutdown(SHUT_WR) 还是shutdown(SHUT_RD) 阻止了LAST_ACK,但这并不重要,因为我们需要同时调用两者。

关闭时阻塞对于上面的要求 #3 是有问题的,例如,您有一个服务于所有连接的单线程设计。使用 SO_LINGER 可能会在其中一个终止时阻塞所有连接。我看到 3 条路线来解决这个问题:

  1. 从不同的线程使用 LINGER 关闭。这当然会使设计复杂化
  2. 徘徊在背景中,或者

2A。将 FIN 和 FIN2“提升”为您可以阅读并等待的应用级消息。这基本上将 TCP 旨在解决的问题提高了一个级别,我认为这是 hack-ish,也是因为随后的关闭调用可能仍然以不确定的方式结束。

2B。尝试找到一个较低级别的工具,例如此处SIOCOUTQ ioctl描述的查询网络堆栈中未确认字节数的工具。警告有很多,这是 Linux 特定的,我们不确定它是否适用于 FIN ACK(以了解关闭是否完全完成),而且您需要定期轮询 taht,这很复杂。总的来说,我倾向于选项 1。

我试图对问题进行全面总结,欢迎更正/补充。


小智 -2

Close 将关闭套接字的发送端和接收端。如果您只想关闭套接字的发送部分而不是接收部分,反之亦然,您可以使用 shutdown。

close()------->will close both sending and receiving end.
shutdown()------->only want to close sending or receiving.
  argument:SHUT_RD(shutdown reading end (receiving end))
           SHUT_WR(shutdown writing end(sending end))
SHUT_RDWR(shutdown both)
Run Code Online (Sandbox Code Playgroud)