TCP半关闭

Posted vector6_

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了TCP半关闭相关的知识,希望对你有一定的参考价值。

TCP半关闭

建立一个TCP连接需要三次握手,而关闭一个TCP连接需要四次挥手,我们知道三次握手因为被动开启连接方的ACK与SYN优化为一个报文发送,那么为什么关闭连接的挥手需要四次而不是三次呢?原因就是本文的主题:TCP的半关闭状态。

我们知道TCP是一个全双工的连接,任何一方都可以发送数据接收数据,并且都可以发起关闭连接请求,除此之外TCP还支持半关闭操作,即仅关闭一个数据流的一个传输方向,而两个半关闭操作合在一起就能够关闭整个连接,TCP协议规定:通信的任何一方在完成数据发送任务后都能够发送一个FIN。

由此可以知道采用四次而不是三次是考虑到被动关闭的一方发送ACK后,出于半关闭状态,仍有数据未发送完,此时还是会继续发送完数据,然后才会发送FIN关闭连接的请求。所以被动关闭一方的ACK与FIN不能合并,必须要有四次挥手的过程。

半关闭 shutdown 函数

伯克利套接字的API提供了半关闭操作:shutdown 函数,其原型为:

int shutdown(int sockfd, int howto)

对已连接的套接字执行 shutdown 操作,若成功则为 0,若出错则为 -1。

第二个参数 howto 是这个函数的设置选项,它的设置有三个主要选项:

  • SHUT_RD(0):关闭连接的“读”这个方向,对该套接字进行读操作直接返回 EOF。从数据角度来看,套接字上接收缓冲区已有的数据将被丢弃,如果再有新的数据流到达,会对数据进行 ACK,然后悄悄地丢弃。也就是说,对端还是会接收到 ACK,在这种情况下根本不知道数据已经被丢弃了。
  • SHUT_WR(1):关闭连接的“写”这个方向,这就是常被称为”半关闭“的连接。此时,不管套接字引用计数的值是多少,都会直接关闭连接的写方向。套接字上发送缓冲区已有的数据将被立即发送出去,并发送一个 FIN 报文给对端。应用程序如果对该套接字进行写操作会报错。
  • SHUT_RDWR(2):相当于 SHUT_RD 和 SHUT_WR 操作各一次,关闭套接字的读和写两个方向。

close 函数

我们再来看一下最常见的 close 函数:

int close(int sockfd)

close 函数对已连接的套接字执行,若成功则返回为 0,若出错则为 -1。

首先我们要知道套接字存在引用计数的概念,因为套接字可以被多个进程共享,如果我们通过 fork 的方式产生子进程,套接字就会引用计数 +1, 如果我们调用一次 close 函数,套接字引用计数就会 -1

close 函数会对套接字引用计数减一,一旦发现套接字引用计数到 0,就会对套接字进行彻底释放,并且会关闭 TCP 两个方向的数据流。

close 函数具体是如何关闭两个方向的数据流呢?

  • 在输入方向,系统内核会将该套接字设置为不可读,任何读操作都会返回异常。

  • 在输出方向,系统内核尝试将发送缓冲区的数据发送给对端,并最后向对端发送一个 FIN 报文,接下来如果再对该套接字进行写操作会返回异常。

如果对端没有检测到套接字已关闭,还继续发送报文,就会收到一个 RST 报文,告诉对端套接字已关闭。

通过以上我们会发现,close 函数并不能帮助我们关闭连接的一个方向。这就是需要 shutdown 函数。

以上是关于TCP半关闭的主要内容,如果未能解决你的问题,请参考以下文章

关于tcp半关闭的问题

深入浅出TCP之半关闭与CLOSE_WAIT

tcp的几个半状态

TCP之半关闭与CLOSE_WAIT

有关服务端主动关闭socket带来的几个问题分析--tcp四次握手半关闭问题导致

TCP连接与关闭的相关概念