问题起源:很多时候,server端如果重启或者崩溃,会遇到“ Address already in use”。过几分钟,就可以重新启动了。
下面是问题:
A)为什么会出现这种情况?
B) 如何解决,使得服务器能够马上启动?
原来,Server端如果重启或者遇到崩溃,会进入TIME_WAIT状态,并且会等待2MSL的时间,在这个时间内,是不允许服务器重启的。
那为什么Server端会是TIME_WAIT状态,而不是Close状态。这就涉及到TCP连接关闭的问题。
2.1 TCP连接关闭流程
TCP中,执行主动关闭的一方会进入TIME_WAIT的状态,图中的例子是Client进入TIME_WAIT状态。
进入 TIME_WAIT状态之后,会等待2MSL(Max Segment Lifetime,最大段生存时间,MSL为2min,1min,30s,根据不同的实现决定,RFC 793 建议为2min)。
作为参考,下面是TCP连接状态转换图。
2.2 TIME_WAIT的作用
TIME_WAIT有2个作用:
1)当主动关闭方发送最后的ACK消息丢失时,会导致另一方重新发送FIN消息。 TIME-WAIT 状态用于维护连接状态。
–如果主动关闭方直接关闭连接,当重传的FIN消息到达时,因为TCP已经不再有连接的信息了,所以它就用RST(重新启动)消息应答,这样会导致对等方进入错误状态而不是有序的终止状态。
2.3 如何结束TIME_WAIT状态呢
有种说法,叫做TIME_WAIT Assassination,就是TIME_WAIT暗杀。有2种情况会导致TIME_WAIT Assassination.
A) 意外终止。
如下图所示,当有个延时的MSG发送过来的时候,执行主动关闭的HOST1处于TIME_WAIT,因为这个延时的MSG的序列号不在当前能处理的窗口范围之内,HOST1会发送一个ACK包,告诉对方说,我HOST1能收的序列号是多少。而对方已经关闭,处于Close状态,收到一个ACK包,就会回复一个RST包给HOST1。导致HOST1立即结束。
TIME_WAIT给Assassinate掉了。这种情况有没有办法避免呢?
有的,有的实现这么处理:当处于TIME_WAIT状态时不处理RST包即可。
B) 人为造成。
可以调用setsockopt,设置SO_LINGER,就可以不进行结束连接的4次握手,不进入TIME_WAIT,而直接关闭连接。
关于SO_LINGER
–应用程序关闭连接时,close或者closesocket调用会操立即返回,如果有数据残留在套接口缓冲区中则系统将试着将这些数据发送给对方,但是应用程序并不知道递交是否成功。
2.4 关于TIME_WAIT状态的结论
健壮的应用程序永远不应该干涉TIME-WAIT状态----它是TCP可靠性机制的一个重要部分。
在调用bind函数之前,设置SO_REUSEADDR就可以了。
说到这里,好像应该结束了,但是,我们刚刚介绍过,TIME_WAIT的作用有2个,那这里Server重用这个地址,有没有可能导致问题呢。
答案是肯定的,有这个可能。只要满足4元组相同,并且delay的数据包的序列号在新的连接可接受的窗口之内,就可能导致问题。
在Stackoverflow上,有人问过这个问题:
Using SO_REUSEADDR - What happens to previously open socket?
答案就是:The SO_REUSEADDR
option overrides that behavior, allowing you to reuse the port immediately.
Effectively, you're saying: "I understand the risks and would like to use the port anyway."