由于上一篇文章讲了socket编程中遇到的一些问题,因此觉得有必要写一篇关于tcp的事,以用这些来解释之前遇到的一些问题,不然对于那些情况有点摸不着头脑。
废话不多说,看图
这是tcp有限状态机的状态转换图,这里面包含了建立连接前的三次握手,以及三次握手时的状态转换;同时包含了断开连接的四次握手,以及期间端口的状态转换。
刚开始server端开启一个端口监听,此时server端端口处于LISTEN状态。client端端口处于CLOSED状态。
建立连接协议(三次握手)
(1)client端主动打开,发送一个带SYN标志的包到server端。
(2)server端收到client端发送的包,返回带ACK标志和SYN标志的包(相当于server端告诉client端:我已经装备好了,你呢?),并询问client是否准备好了连接。
(3)client端收到带ACK和SYN标志的包后,知道server端同意并准备好连接了,此时返回一个ACK包,告诉server端我也装备好了,咱们可以开始了。于是双方端口都进入ESTABLESHED状态。
连接终止协议(四次握手)
由于TCP连接是全双工的,因此每个方向都必须单独进行关闭。这原则是当一方完成它的数据发送任务后就能发送一个FIN来终止这个方向的连接。收到一个 FIN只意味着这一方向上没有数据流动,一个TCP连接在收到一个FIN后仍能发送数据。首先进行关闭的一方将执行主动关闭,而另一方执行被动关闭。下面我们将主动发送FIN包的一端成为client端
(1) client客户端发送一个FIN,用来关闭到server端的数据传送。
(2) server端收到这个FIN,它发回一个ACK,确认序号为收到的序号加1。和SYN一样,一个FIN将占用一个序号。
(3) server端关闭client的连接,发送一个FIN给client端。
(4) client端发回ACK报文确认,并将确认序号设置为收到序号加1。此后双方端口进入CLOSED状态
TIME_WAIT
从上图中可以看出,主动断开连接的一方的端口会进入TIME_WAIT状态。
那什么是TIME_WAIT?
表示收到了对方的FIN报文,并发送出了ACK报文,就等2MSL后即可回到CLOSED可用状态了。
注:MSL(最大分段生存期)指明TCP报文在Internet上最长生存时间,每个具体的TCP实现都必须选择一个确定的MSL值。RFC 1122建议是2分钟,但BSD传统实现采用了30秒。TIME_WAIT 状态最大保持时间是2 * MSL,也就是1-4分钟。
为什么TIME_WAIT状态还需要等2MSL后才能返回到CLOSED状态?
这是因为:虽然双方都同意关闭连接了,而且握手的4个报文也都协调和发送完毕,按理可以直接回到CLOSED状态(就好比从SYN_SEND状态到ESTABLISH状态那样);但是因为我们必须要假想网络是不可靠的,你无法保证你最后发送的ACK报文会一定被对方收到,因此对方处于LAST_ACK状态下的SOCKET可能会因为超时未收到ACK报文,而重发FIN报文,所以这个TIME_WAIT状态的作用就是用来重发可能丢失的ACK报文,并保证于此。
因此我们在构建高并发服务器的时候,尽量让客户端主动断开连接,这样服务器被动关闭则不会进入TIME_WAIT状态。
服务端为了解决这个TIME_WAIT问题,可选择的方式有三种:
· 保证由客户端主动发起关闭(即做为B端)
· 关闭的时候使用RST的方式(用来异常的关闭连接的包)
· 对处于TIME_WAIT状态的TCP允许重用