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/IP网络编程基础阅读笔记