Ted

  博客园 :: 首页 :: 博问 :: 闪存 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::

1 问题 

问题起源:很多时候,server端如果重启或者崩溃,会遇到“ Address already in use”。过几分钟,就可以重新启动了。

下面是问题:

A)为什么会出现这种情况?

B) 如何解决,使得服务器能够马上启动?

 

2 分析

原来,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(重新启动)消息应答,这样会导致对等方进入错误状态而不是有序的终止状态。

    –重新启动2MSL计时器,防止该ACK再次丢失。
 
2)为连接中“离群的段”提供从网络中消失的时间。
   网络中的数据包因为延时等因素,可能在连接关闭之后才到达,如果没有进入TIME_WAIT状态,且满足
  A) 又建立了新的连接,且新的连接的4元组和上次的连接一样,即Src_IP, Src_Port, Dst_IP,Dst_Port一样。
  B)这个延时的数据包的序列号恰好又处于对方新连接的可接受窗口之内。
  满足这个2个条件,就会被接收,并且会破坏新的连接。
  而进入TIME_WAIT状态,并且等待2 MSL,就给网络中“离群的段”提供了消失的时间。

 

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调用会操立即返回,如果有数据残留在套接口缓冲区中则系统将试着将这些数据发送给对方,但是应用程序并不知道递交是否成功。

    –close的成功返回仅告诉我们发送的数据(和FIN)已由对方TCP确认,它并不能告诉我们对方应用进程是否已读了数据。如果套接口设为非阻塞的,它将不等待close完成
     –SO_LINGER选项用来改变此缺省设置
    
 
   设置SO_LINGER结构
 
struct linger {
     int l_onoff;   /* 0 = off, nozero = on */
     int l_linger;   /* linger time */
};
 
–l_onoff为0,则该选项关闭,l_linger的值被忽略,等于缺省情况,close立即返回;
–l_onoff为非0,l_linger为0,则套接口关闭时TCP中断连接,TCP将丢弃保留在套接口发送缓冲区中的任何数据并发送一个RST给对方,而不是通常的四次挥手终止序列,这避免了TIME_WAIT状态;
–l_onoff 为非0,l_linger为非0,当套接口关闭时内核将拖延一段时间(由l_linger决定)。如果套接口缓冲区中仍残留数据,进程将处于睡眠状态,直到所有数据发送完且被对方确认,之后进行正常的终止序列或延迟时间到。此种情况下,应用程序检查close的返回值是非常重要的,如果在数据发送完并被确认前时间到,close将返回EWOULDBLOCK错误且套接口发送缓冲区中的任何数据都丢失。
  

2.4 关于TIME_WAIT状态的结论

  健壮的应用程序永远不应该干涉TIME-WAIT状态----它是TCP靠性机制的一个重要部分。

 

3 Server问题分析

   上面讲了TIME_WAIT相关的知识,现在我们知道,当Server端重启或者崩溃的时候,它就是主动关闭的一方,会进入TIME_WAIT状态,导致服务器不能重启。
   那我们可以马上重启么,可以的。
 
 

4  如何马上重启Server

    在调用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."

 

 

 

posted on 2012-06-14 21:20  wufawei  阅读(2540)  评论(0编辑  收藏  举报