优雅的结束
在介绍完一个过程如何开始之后,自然的就应该去了解他是如何结束的。TCP的结束设计如果让我想一个形容词的话我就会选择优雅,有一种双方都是在商量好之后并且给足对方时间之后不紧不慢的结束的感觉。相对于TCP的开始,TCP的结束一般会有四个阶段,而这四个阶段之间的等待时间是理解TCP结束的重点,这个就不得不用一个图来表示了。
在这个图中,将发起关闭请求的一方成为发送方,这样另一方叫做接收方,但看两边的发包状态其实结束的过程很简单,无非是发送方发出一个包,将TCP报头中FIN标识位置1表示这是一个结束请求包(FIN是finish的缩写)。接收方在接收到这个包之后,发送ACK包通知发送方我已接收到结束请求,你可以结束你这边的通信了。接着接收方也会发送一个请求结束的请求,同理发送方收到这个请求之后发送ACK表示我已经很好的接收到你的请求,你也可以结束从你这段的通信了。
停下来想一下,为什么结束的时候不同样遵守三次握手的过程呢?换句话说,为什么接收方的FIN不能和它上一个ACK一起发送到发送方呢?回想TCP的三次握手,第二个SYN就是随着ACK一起发送至对端的。根据经验,这种面试题一般可以筛选掉一些只看过啥啥面试指南的。
在描述答案之前,其实可以试想一个生活中打电话的例子,比如说你打电话给某个人吧。要结束了,你说“那没事我挂啦“,短短的六个字包含了两层意思,“那没事”表示你确认对方还有没有事,“我挂啦”表示你已经有了要结束通话的意图。对方听到这句话后可能会说"那你挂吧”,电话到这个时候就立即挂断了,通话结束。也有可能说,“哦,我还想起一件事,xxx”,这个时候你会继续听别人有什么事,给别人一个你想结束通话但是别人还有讲述未完成事情的机会。如果直接是你想挂电话的时候咔就给挂了,首先这很不礼貌,不优雅,其次,你可能会错过重要的事情。
TCP也是这样的,在发送数据的过程中,双方都有可能是发送数据的一方(这里要特别说明,虽然在这一篇和上一篇里通篇都有发送方和接收方,但是这只是为了在连接建立和结束阶段方便叙述,在通信的过程中,其实没有发送发和接收方的区别的)。所以在每时每刻,每一方都有可能在发送数据,每一方都有可能要读取数据,在结束的时候,当结束发起方发起结束的要求时,只可以肯定的是发送方这边没有数据要发送了(不然也不会发出结束的请求啊), 但是不能知道对端是否还有数据在发送。所以,当接收端接收到FIN请求时,他只能回送一个ACK请求,有一种"朕知道了,但是朕还有事”的感觉。然后在他觉得合适的时候(也就是本端没有发送数据请求的时候),发送自己这一端的FIN请求,发送端在ACK之后正是的关闭整个连接。
关闭的状态
回头再看上面的图,你会发现在竖线两边有很多名词,作为一个严谨的理工科概念,TCP利用了这些名词定义定义了一个严谨的关闭时候的状态和时序。
对于发起端,其状态和状态之间的变迁如下:
FIN_WAIT_1: 当发起端发送FIN消息后,发起端就进入了FIN_WAIT_1状态,这个状态一直会持续到发起端收到对端的ACK消息。
FIN_WAIT_2: 当发起端收到对端的ACK消息后,进入了FIN_WAIT_2状态,至此表示发起端这边的发送通道全部关闭,注意,仅仅是发送通道关闭,读取通道依然开启,因为此时对端并没有发出结束请求,不能贸然关闭读取通道。
TIME_WAIT: 当发起端收到接收端发起的FIN请求后,发起端就会进入一个等待状态,同时回复对方ACK消息表示自己已经接收到对端的FIN请求。为什么要有这么一个状态,可以这样想一下,在接收端发送“FIN”,消息的时候,接收端所能知道只是前面他想要发出的消息都发出去了(在这个阶段应该都是ACK消息,因为发起端的发送窗口已经关闭了,这个时候主动向发起端发消息,发起端是不会有任何反应的并且会报错,这个在下一篇会详细说明所有的出错情况怎么样),至于对方有没有收到,接收端并不能知道。所以这个时候发起端采取了一个最简单的方法,等待一段时间以保证所有消息都接收到,那么这个TIME_WAIT时间多长呢?多长才能确认信道中已经不可能再存在有还没有发送过来的消息呢? 在TCP中定义了一个概念叫做MSL,Maximum Segment Lifttime,表示一个包在网络中能生存的最大时间 ,在TCP标准中,定义是2分钟,linux下一般是定义为30秒。有了这个概念之后,很简单的就能得到TIME_WAIT的值应该是多少,从发送端发送到接收端最大可允许的时间是MSL,从接收端发回来的ACK最大允许时间也是MSL,所以如果经历了2*MSL的时间就可以确定网络中不会有还没有到达的包了,因为在这个时间之前一次往返的包应该都消失了。
CLOSED: 在这一切结束后,发起端就可以放心的关闭读和写两个通道完成关闭了。
在接收端,其状态和状态之间的转换如下:
CLOSE_WAIT:接收端在接收到对端的FIN请求后,发送ACK的同时进入CLOSE_WAIT状态,这个状态就是等待自己这一端的上层在一切完毕的时候发送结束请求。
LASK_ACK:表示接收端最后一个ACK已经发送,也就是最后一条需要发送的消息已经发送。
CLOSED: 在这一切结束后,接收端就可以放心的关闭读和写两个通道完成关闭了。
两个拷问
在详细介绍完两边的状态之后,我有两个拷问心灵的问题:
- 关闭的过程可不可能是三次握手而不是四次?
- 接收端需不需要TIME_WAIT阶段?
问题没有答案是不道德,但是我最希望的还是停下来想一想,毕竟30天也学不会这玩意儿。
下面就是答案了,高能预警先:
对于第一个问题,再回想打电话了例子,里面还是有一种可能就是在你询问有没有事情然后要挂了之后,对方确实没有事情,立马挂电话,这种情况是存在的。相当于在接收端收到发送端的FIN的时候,接收端已经没有消息需要发送了,严格的定义是LAST_ACK在接收端FIN之前就已经被发送了,并且在这时接收端的上层也发起了close的请求,那么发送端的FIN可以随着其将要发送的ACK一起发送回发送端。这样也就成为了三次通信结束连接,这种
对于第二个问题,答案是不需要,原因是在接收端发送FIN的时候其接收通道并没有关闭,而发送端已经等了2*MSL的时间,可以知道在接收端方FIN到close之前一定足够时间判断没有存留的包了。
最后赠送一个问题,不写答案的,接收端有没有可能直接进入CLOSED的阶段?