|
|
51CTO旗下网站
|
|
移动端

网络编程-再看TCP的四次挥手

作为一种常见的四次挥手场景,我们可能习以为常了,但需要注意的是,连接的断开并不只有这种情况,还可以是服务端发起主动关闭,或者双方同时发起,但这不是本文关注的重点。我们直接看看四次挥手有哪些需要注意的。

作者:守望先生来源:编程珠玑|2019-07-16 11:06

前言

四次挥手

四次挥手的流程在很多地方都可以看到,这里简略介绍一下,其常见流程如下图所示:

其大体流程如下:

  • 客户端发其结束请求,发送seq=X,处于FIN_WAIT_1状态
  • 服务端收到结束请求,发送应答ACK=X+1,处于CLOSE_WAIT状态
  • 客户端收到X的应答后,处于FIN_WAIT_2状态,此时还可以接收来自服务端的数据
  • 服务端没有数据要发送,也发送结束请求,seq=Y,处于LAST_ACK状态
  • 客户端又收到服务端的结束请求,客户端回应ACK,此时处于TIME_WAIT状态,确保ACK能够到达服务端;服务端收到客户端最终ACK,关闭连接。
  • 2MSL时间结束后,无论服务端是否收到最终ACK,客户端完全结束连接

作为一种常见的四次挥手场景,我们可能习以为常了,但需要注意的是,连接的断开并不只有这种情况,还可以是服务端发起主动关闭,或者双方同时发起,但这不是本文关注的重点。我们直接看看四次挥手有哪些需要注意的。

什么是TCP的半关闭

TCP半关闭指的是一端结束发送后还能够接受来自另一端的数据。也就是说,虽然客户端准备断开连接并且发送了FIN报文,客户端还是可以接收来自服务端的数据。不过这种关闭方式不能使用close接口,而需要使用shutdown:

  1. #include <sys/socket.h> 
  2. int shutdown(int sockfd, int how); 

并且how参数值为SHUT_WR,即1,表明shutdown for writing ,仅关闭本端的发送。

为什么要四次挥手

为什么建立一个TCP连接需要三次握手,而终止一个连接需要四次挥手呢?这是因为TCP半关闭造成的。由于一个TCP连接是全双工的,在两个方向上都能传输数据,因此两个方向就需要单独关闭。所以这个流程是这样的:

  • 客户端执行主动关闭,发送FIN报文,告诉服务端,我没有数据要发送了,我要关闭连接,当然了,你有啥数据要给我,我随时候着
  • 服务端收到后,必须及时告诉客户端我收到了,因此先回复客户端一个ACK。但是服务端可能还有未发送完的数据,因此它可以将自己未完成的数据进行发送,发送完成之后,再发送给客户端FIN报文,表明我也没啥要发送的了,关闭吧
  • 客户端收到后,也回复ACK响应,最终关闭连接

因而整个过程需要四次挥手。

为什么要TIME_WAIT状态

TIME_WAIT也称为2MSL等待时间。MSL为报文最大生存时间,它是任何报文在被丢弃前存在于网络内的最长时间。这个时间在不同类型的系统中可能有所不同,但这不是关键。在我个人的机器上,可以借助netstat命令和nc命令通过下面的方式观察到。在终端1监听1234端口:

  1. $ nc -l 1234 

在终端2连接到1234端口:

  1. $ nc 127.0.0.1 1234 

在终端3通过netstat命令观察:

  1. $ netstat -anpoc|grep :1234 

然后在终端1按ctrl+c,终止连接,立刻观察终端3的结果,我们发现:

  1. tcp        0      0 127.0.0.1:1234          127.0.0.1:33524         TIME_WAIT   -                timewait (59.76/0/0) 
  2. tcp        0      0 127.0.0.1:1234          127.0.0.1:33524         TIME_WAIT   -                timewait (58.74/0/0) 
  3. tcp        0      0 127.0.0.1:1234          127.0.0.1:33524         TIME_WAIT   -                timewait (57.71/0/0) 
  4. tcp        0      0 127.0.0.1:1234          127.0.0.1:33524         TIME_WAIT   -                timewait (56.69/0/0) 

我们可以观察到,服务端当前处于TIME_WAIT,且有一个timewait的定时器,为1分钟。

netstat命令和nc命令的使用可以分别参考《不可不知的网络命令-netstat》和《网络工具中的”瑞士军刀“-nc》。

TIME_WAIT状态的存在主要考虑以下两个方面:

  • 实现可靠的四次挥手
  • 避免收到老的报文

为什么说TIME_WAIT是为了实现可靠的四次挥手呢?试想一下,如果客户端最后回应的ACK丢了,那么服务端会再次发送FIN报文,此时,客户端必须处于一个等待状态,否则服务端永远无法收到这个ACK,而会收到一个RST,以为出错。而如果客户端此时处于TIME_WAIT状态,即等待2MSL时间,它还可以再次回应服务端ACK。这也就保证了可靠的四次挥手。

当然了,如果在2MSL时间内,服务端还没有收到,那么对不起,客户端已经仁至义尽了,不会再等待了。

这里需要注意,最终执行主动关闭的那一端会处于TIME_WAIT状态。

那么为什么又说是为了避免收到老的重复报文呢?

试想这样的场景:

假设一开始已经有一个连接在1234端口建立,我们关闭这个连接;过一会我们在同样的ip和端口建立连接,但是TCP必须防止在前一次连接中的老的报文在它原先的连接已终止后,还出现在这个新的连接中,因此,TCP将不允许在处于TIME_WAIT状态的ip和端口处建立新的连接。而2MSL时间过后,老的报文早已在网络中消失了,也就避免了这种情况的发生。

这种情况可以很容易通过《网络编程-一个简单的echo程序》的server程序来观察:

  1. $ ./server  #在一个终端启动server, 
  2. $ ./client 127.0.0.1 1234 #在另一个终端启动client 

在服务端终端ctrl+c终止服务端,然后再次启动server:

  1. $ ./server 
  2. bind error: Address already in use 
  3. $ netstat -anop|grep :1234 
  4. tcp        1      0 127.0.0.1:33722         127.0.0.1:1234          CLOSE_WAIT  11691/client     off (0.00/0/0) 
  5. tcp        0      0 127.0.0.1:1234          127.0.0.1:33722         FIN_WAIT2   -                timewait (57.92/0/0) 

终止服务端后,服务端处于TIME_WAIT状态,此时再次启动server,将不能使用原来的ip和端口建立连接,因此出现Address already in use的报错。

但是需要注意:

  • 由于客户端通常使用的是临时端口(仔细观察会发现,客户端每次启动使用的端口基本都不一样),因此客户端即便处于TIME_WAIT状态,也不影响它马上再次启动
  • 一些实现允许一个新的连接请求仍然处于TIME_WAIT状态的连接,只要新的seq大于该连接的前一个连接的最后序号
  • 通过设置选项SO_REUSEADDR,可以让一个进程重新使用仍处于TIME_WAIT状态的socket

半打开的TCP连接

假设一个连接建立之后,突然有一方异常终止连接了,但是另一个不知道,这个时候TCP的连接就是半打开的。如果服务端不加处理,那么最终就会导致服务端有大量的半打开连接。那么服务端如何知道客户端的连接已经异常终止了呢?如果等待服务端发送数据出错时发现,那么这个时候可能已经太晚了。

幸运的是,TCP有保活定时器。即服务端可以通过设置保活选项来了解客户端是否已经终止连接。

通过下面的方式可以看到很多连接有这样的定时器:

  1. $ netstat -npo|grep keepalive 
  2. tcp        0      0 192.168.0.103:50832     59.111.179.136:443      ESTABLISHED 5882/chrome      keepalive (37.33/0/0) 
  3. tcp        0      0 192.168.0.103:50638     154.8.131.191:443       ESTABLISHED 5882/chrome      keepalive (0.00/0/0) 
  4. tcp        0      0 192.168.0.103:59330     203.107.41.32:9026      ESTABLISHED 5882/chrome      keepalive (0.35/0/0) 
  5. tcp        0      0 127.0.0.1:45632         127.0.0.1:1080          ESTABLISHED 5886/firefox     keepalive (335.28/0/0) 
  6. tcp        0      0 192.168.0.103:49940     59.56.78.189:443        ESTABLISHED 5882/chrome      keepalive (26.36/0/0) 

但可惜的是,这样的定时器时间太长了,并且它不能代表应用程序能够正常工作,能够正常收发数据,因此应用层常常也会实现一个心跳机制。

总结

本文花了大量篇幅介绍了TIME_WAIT状态,这也是面试中常问的问题,重新梳理TCP的四次挥手是很有必要的。

【编辑推荐】

  1. TCP到底怎么做流量控制?
  2. TCP/IP,你必知必会的十个问题
  3. 你猜一个TCP连接上面能发多少个HTTP请求?
  4. TCP/IP 协议就是传输洋葱?一文带你深入了解
  5. 不可不知的socket和TCP连接过程
【责任编辑:武晓燕 TEL:(010)68476606】

点赞 0
分享:
大家都在看
猜你喜欢

订阅专栏+更多

16招轻松掌握PPT技巧

16招轻松掌握PPT技巧

GET职场加薪技能
共16章 | 晒书包

343人订阅学习

20个局域网建设改造案例

20个局域网建设改造案例

网络搭建技巧
共20章 | 捷哥CCIE

674人订阅学习

WOT2019全球人工智能技术峰会

WOT2019全球人工智能技术峰会

通用技术、应用领域、企业赋能三大章节,13大技术专场,60+国内外一线人工智能精英大咖站台,分享人工智能的平台工具、算法模型、语音视觉等技术主题,助力人工智能落地。
共50章 | WOT峰会

0人订阅学习

读 书 +更多

网管第一课——计算机网络原理

本书是《网管第一课》系列图书中的第一本,是专门针对高校和培训机构编写的,其主要特点是内容细而精、针对性强。书中内容全是经过精心挑选...

订阅51CTO邮刊

点击这里查看样刊

订阅51CTO邮刊

51CTO服务号

51CTO播客