TCP四次挥手
好说好散,四次挥手。
- A:B啊,我不想玩了
- B:哦,你不想玩了啊,我知道了
这个时候,还只是A不想玩了,即A不会再发数据,但B能不能在ACK时,直接关闭呢?当然不可以,很可能A是发完了最后的数据就准备不玩了,但是B还没做完自己的事情,还是可发送数据,所以称为半关闭状态。
这时A可选择:
- 不再接收数据
- 或最后再接收一段数据,等待B也主动关闭
B:A啊,好吧,我也不玩了,拜拜
A:好的,拜拜
这样整个连接就关闭了。这是和平分手。
A开始说“不玩了”,B说“知道了”,这个没问题,因为在此之前,双方还处于合作的状态。
若A说“不玩了”,没有收到回复,则A会重发“不玩了”。但这个回合结束后,可能出现异常情况,因为已有一方率先撕破脸:
- A说完“不玩了”后,直接跑路,就有问题,因为B还没发起结束,而若A跑路,B就算发起结束,也得不到回答,B就不知道该怎么办了
- A说完“不玩了”,B直接跑路,也有问题,因为A不知道B是还有事情要处理,还是过一会儿会发送结束回来
解决这些问题?
TCP协议专门设计了几个状态来处理这些问题。我们来看断开连接的时候的状态时序图
断开时可见:
- 当A说“不玩了”,就进入FIN_WAIT_1状态
- B收到“A不玩”消息后,发送知道了,进入CLOSE_WAIT状态
- A收到“B说知道了”,就进入FIN_WAIT_2状态。若此时B直接跑路,则A将永远在这个状态。TCP协议里面并没有对这个状态的处理,但Linux有,可以调整tcp_fin_timeout参数,设置一个超时时间。
- 若B没有跑路,发送“B也不玩了”请求到达A时
- A发送“知道B也不玩了”ACK后,从FIN_WAIT_2状态结束。按理说,A现在可以跑路了,但最后的这个ACK万一B收不到呢?则B会重新发一个“B不玩了”,这时若A已跑路,B就再也收不到ACK,所以TCP协议要求A最后等待一段时间TIME_WAIT,这个时间要足够长,长到如果B没收到ACK,“B说不玩了”会重发的,A会重新发一个ACK并且足够时间到达B。
A直接跑路还有一个问题是,A的端口就直接空出来了,但B不知道,B原来发过的很多包很可能还在路上。此时若A的端口被一个新应用占用,这个新应用会收到上个连接中B发过来的包,虽然序列号是重新生成的,但这里要上一个双保险,防止产生混乱,因而也需要等足够长的时间,等到原来B发送的所有的包都死翘翘,再空出端口来。
等待的时间设为2MSL,MSL:Maximum Segment Lifetime,报文最大生存时间,是任何报文在网络上存在的最长时间,超过这个时间报文将被丢弃。因为TCP报文基于是IP协议的,而IP头中有一个TTL域,是IP数据报可以经过的最大路由数,每经过一个处理他的路由器此值就减1,当此值为0则数据报将被丢弃,同时发送ICMP报文通知源主机。协议规定MSL为2分钟,实际应用中常用的是30秒,1分钟和2分钟等。
若B超过2MSL,依然没有收到它发的FIN的ACK,怎么办?
按TCP原理,B会重发FIN,这时A再收到这个包后,A就表示,我已经在这里等了这么长时间了,仁至义尽,之后的我也不认了,于是就直接发送RST,B就知道A早跑了。
TCP状态机
将连接建立和连接断开的两个时序状态图综合起来,就是这个著名的TCP的状态机。学习的时候比较建议将这个状态机和时序状态机对照着看,不然容易晕。
阿拉伯数字序号,是连接过程中的顺序,而大写中文数字的序号,是连接断开过程中的顺序。
- 加粗的实线是客户端A的状态变迁
- 加粗的虚线是服务端B的状态变迁