假设A->B已建立了连接,A为c端,B为s端。目前两端的状态为established。同时走的是正常的fin流程,而不是reset(发送缓冲区中有数据,直接closesocket会造成此情况,此时的连接已经处于错误状态)。
c端主动关闭连接(s端一样的流程)。c端发出fin,同时c端的状态进行了改变。
C established->fin_wait_1 (send fin)
s端收到了fin后,返回fin ack,同时状态由established转为close_wait。
S established->close_wait (recv fin and send fin ack)
c端收到了fin ack后,转为fin_wait_2状态,等待s端的关闭。在这儿假如长久没有收到fin ack,则c端会重新发送fin来再次关闭连接。
C fin_wait_1->fin_wait_2 (recv fin ack)
至此由C端至S端的写通道已关闭。TCP是全双工的,所以各自的通道必须各自来关闭。发送了fin之后,C端将无法再次发送数据包至S,而S至C的通道没有被关闭,也就代表着S端可以继续向C端发送数据包,tcp处于半连接状态。
所以在此情况下,虽然c端已经关闭了自己的连接,但是s端仍然可以向c端发送数据包,这样是因为为了防止一段关闭后造成的数据丢失。也就是send函数是无法得知对方已经关闭了连接,仍旧可以向对方传输数据。而recv不一样,当对方关闭了连接后,那么recv会立刻返回0值,正常流程我们应该讲处于close_wait
状态的socket关闭,那么就要走另一端的fin流程:
s端被动关闭连接,s段发出了fin,状态发生了改变。
S close_wait->last ack (send fin)
c端收到了s端的fin后,改变状态,同时发出fin ack。
C fin_wait_2->time_wait (recv fin and send fin ack)
s端收到了fin ack后,整个连接关闭
S last_ack->closed (recv fin ack)
仔细看以上的流程,3部中总有一个中间的状态,对于C,发出了fin后,s的状态转为了close_wait
状态;对于S,发出了fin后,c转为了time_wait
状态。这两个状态比较特殊,在某些情形下会长时间处于该状态,特别是在某些服务器程序上,假设有大量的连接处于这种中间状态,那么后果将是灾难性的。
为何要有这么两个中间状态?下面完全是个人的理解了,有可能有差错,再次仅仅是做个笔记。
首先,我们可以看出,上述2个状态均为收到了fin后的状态,而收到了fin后,自身的状态立刻发生改变,同时发出fin ack。也就是说,发出fin的一方,在正常的流程下,肯定会收到fin ack。那么我们仔细想想,假如该fin ack由于种种情况丢失了,那么主动关闭的一方会重发fin,如果没有上述两个中间状态,那么没法应付这种情况。
还有一个原因,仅仅是针对于time_wait
状态。处于该状态的套接字,是主动关闭的一方收到了被动关闭一方的fin后产生的状态,将会持续2MSL的事件(最大分节生命期,数据包在互联网上最长的生存时间)。假设在被动关闭一方发送fin前,某个包超时了,主动方长久没有收到确认包,该包将会被重发,假设之前的套接字的相关信息被重用了,那么该数据将会被新连接收到,那么就错乱了。为了避免该情况,设置了time_wait
状态。
一个正常连接在关闭后(closesocket),会有以下几种情况。