TCP TIME_WAIT解决方案

写过TCP服务器的人都知道,要解决主动关闭后的TIME_WAIT状态是件很麻烦的事情,如果服务器设置Linger生效且延迟为0秒,则服务器发送给Client的最后一个数据包极可能丢失。ServerTIME_WAIT过多会导致服务器效率急剧下降,ClientTIME_WAIT过多会导致connect to server失败(WSAEADDRINUSE错误,休息一段时间让部分处于TIME_WAIT状态的句柄超时后又能connect成功到服务器)

几乎翻遍互联网也没找到比较好的解决方案,其中有老外提出修改TCP/IP协议栈的方案(前几天我也在考虑这个方案的可行性)。 

下面给出我的关于解决TIME_WAIT的方案。
 首先分析下从ESTABLISHED状态到关闭状态的过程:
只有两种方式: 1,主动关闭socket连接。 

         2,被动关闭socket连接。

我们知道主动关闭至少会经过TIME_WAIT_1--->TIME_WAIT(2MSL timeout)--->CLOSED
而被动关闭会经过CLOSE_WAIT--->LAST_ACK--->CLOSED

Step 1:

 分配SERVER端处理CLIENT并发TCP connection请求的节点数目要大于Client单次select()能发起的最大连接数目。这样SERVER端保证不会有请求得不到临时节点而无法接入到SERVER.

 设置新accept接入的句柄reuse address, XSetSocketReuseAddress(),这个是我自己封装的跨平台函数,后面同,大家可以自己用setsockopt()实现。

  Step 2:
 将新接入的节点压入读检测队列(select or epoll). 

Step 3:
 有数据到来,读取数据(如果读取长度为0,则说明对方关闭了该TCP connection)。如果读取完毕,准备好回应数据,并压入写检测队列。 

Step 4:
 检测数据可写,发送数据。如果发送完毕,将TCP connection压入读检测队列(重复Step 2~Step 4以实现长连接J)。 

Step 5: 
        SERVER检测超时节点。
----------------------------------------------------------------------------------------------------

Step 6: 

CLIENT new socket handle,设置Linger生效,且延迟为0XSetSocketLinger(),设置为非阻塞模式XSetSocketBlockingMode(),便于快速connectSERVER ,异步检测是否连接成功。
 将句柄压入写检测队列。 

Step 7:
 如果可写,CLIENT发送请求数据给SERVER,发送完毕将句柄压入读检测队列。 

Step 8:
 如果有数据可读,则读取数据,读取完毕可以直接关闭连接。 

Step 9:
 CLIENT检测超时节点。 

效果:
D:/vcoutput/Release>SimuCGIClient.exe 172.30.14.13 25000 1000000
usage:SimuCGIClient.exe CacheServerHost Port LoopTimes
total ssuccess query:1000012 in 703 seconds.
1422.49 querys/second.

windows下向SERVER请求100万个连接,在703seconds内完成响应,平均每秒约能完成1422TCP并发请求。SERVER,CLIENT均为单线程程序。
在这100万个节点查询过程中,查看服务器和client的协议栈句柄消耗情况:
----------------------------------------------------------------------------------------------------
Active Connections
 Proto Local Address Foreign Address State
 TCP WOOKIN-NB:1847 WOOKIN-NB:25000 ESTABLISHED
 TCP WOOKIN-NB:1848 WOOKIN-NB:25000 ESTABLISHED
 TCP WOOKIN-NB:1849 WOOKIN-NB:25000 ESTABLISHED
 TCP WOOKIN-NB:1850 WOOKIN-NB:25000 ESTABLISHED
 TCP WOOKIN-NB:1851 WOOKIN-NB:25000 ESTABLISHED
 TCP WOOKIN-NB:1852 WOOKIN-NB:25000 ESTABLISHED
 TCP WOOKIN-NB:1853 WOOKIN-NB:25000 ESTABLISHED
 TCP WOOKIN-NB:25000  WOOKIN-NB:0 LISTENING
 TCP WOOKIN-NB:25000 WOOKIN-NB:1847 ESTABLISHED
 TCP WOOKIN-NB:25000 WOOKIN-NB:1848 ESTABLISHED
 TCP WOOKIN-NB:25000 WOOKIN-NB:1849 ESTABLISHED
 TCP WOOKIN-NB:25000 WOOKIN-NB:1850 ESTABLISHED
 TCP WOOKIN-NB:25000 WOOKIN-NB:1851 ESTABLISHED
 TCP WOOKIN-NB:25000 WOOKIN-NB:1852 ESTABLISHED
 TCP WOOKIN-NB:25000 WOOKIN-NB:1853 ESTABLISHED
 UDP  WOOKIN-NB:microsoft-ds *:* 
----------------------------------------------------------------------------------------------------
 只看到LISTENING, ESTABLISHED状态,太漂亮了。 接下来我会在Linux环境下进行继续验证。 

方案关键点:
 将通常情况下应该由SERVER端主动关闭的句柄逆向思维转由CLIENT端主动关闭,则SERVERsocket handle能快速优雅地进入到CLOSED状态。 CLIENT端接收完数据以后采用Linger on延迟0秒的模式,“武断”地关闭client socket handle。其实这种做法一点都不武断,因为CLIENT已经收完数据包,延迟为0关闭句柄并不会造成数据丢失(如果是发送方这样关闭则会造成数据丢失,接收方可以安全快速关闭)。
 SERVER端采用支持长连接的方式巧妙地检测到CLIENT关闭socket connection,又能正常支持长短连接.





请问我如何

检查 自己的socket连接当前处于什么TCP状态呢?  
  因为我想如果检查到当前连接处于CLOSE_WAIT状态的话,就调用closesocket()函数。  
   
  是否是使用select+fd_isset来做到这一点呢?  
  Thanks!  

        是否是使用select+fd_isset来做到这一点呢?  
  ---  
  肯定不是。  
   
  因为我想如果检查到当前连接处于CLOSE_WAIT状态的话,就调用closesocket()函数。  
  --------  
  我印象中CLOSE_WAIT和调用closesocket没关系。你为什么要检测CLOSE_WAIT状态?奇怪的想法。  
       
     
  Top      
     
    回复人:   xiaohaiyan(xiaohaiyan)   (   )   信誉:100     2005-02-03   12:15:00     得分:   0      
     
     
          如果是linux

系统 ,可以参考一下这个,  
  enum   {  
      TCP_ESTABLISHED   =   1,  
      TCP_SYN_SENT,  
      TCP_SYN_RECV,  
      TCP_FIN_WAIT1,  
      TCP_FIN_WAIT2,  
      TCP_TIME_WAIT,  
      TCP_CLOSE,  
      TCP_CLOSE_WAIT,  
      TCP_LAST_ACK,  
      TCP_LISTEN,  
      TCP_CLOSING,   /*   now   a   valid   state   */  
   
      TCP_MAX_STATES   /*   Leave   at   the   end!   */  
  };  
   
   
  socket->sock->sk_state     记录上述状态  
   
  调用getsockname   ,可以通过fd得到socket结构地址。  
   
   
       
     
  Top      
     
    回复人:   zhengyun_ustc(郑昀)   (   )   信誉:100     2005-02-03   12:42:00     得分:   0      
     
     
        因为如果我是客户端,当我给服务器端发送数据后,正准备recv接收响应时,服务器突然直接关闭session和socket,于是我这边立刻进入 CLOSE_WAIT状态,并且始终处于这种状态;这是我的猜想,我可以通过检查send和recv的返回值,以找到这种错误。  
  但是为了以防万一,我想主动检查TCP状态。  
  我的OS是Windows。  
       请读一下PlatformSDK的示例  
  Samples/NetDS/IPHelp/IPStat

void   DumpTcpTable(PMIB_TCPTABLE   pTcpTable)  
  {  
          char         strState[MAX_STRLEN];  
          struct     in_addr         inadLocal,   inadRemote;  
          DWORD       dwRemotePort   =   0;  
          char         szLocalIp[MAX_STRLEN];  
          char         szRemIp[MAX_STRLEN];  
   
          if   (pTcpTable   !=   NULL)  
          {  
                  printf("TCP   TABLE/n");  
                  printf("%20s   %10s   %20s   %10s   %s/n",   "Loc   Addr",   "Loc   Port",   "Rem   Addr",  
                                  "Rem   Port",   "State");  
                  for   (UINT   i   =   0;   i   <   pTcpTable->dwNumEntries;   ++i)  
                  {  
                          switch   (pTcpTable->table[i].dwState)  
                          {  
                          case   MIB_TCP_STATE_CLOSED:  
                                  strcpy(strState,   "CLOSED");  
                                  break;  
                          case   MIB_TCP_STATE_TIME_WAIT:  
                                  strcpy(strState,   "TIME_WAIT");  
                                  break;  
                          case   MIB_TCP_STATE_LAST_ACK:  
                                  strcpy(strState,   "LAST_ACK");  
                                  break;  
                          case   MIB_TCP_STATE_CLOSING:  
                                  strcpy(strState,   "CLOSING");  
                                  break;  
                          case   MIB_TCP_STATE_CLOSE_WAIT:  
                                  strcpy(strState,   "CLOSE_WAIT");  
                                  break;  
                          case   MIB_TCP_STATE_FIN_WAIT1:  
                                  strcpy(strState,   "FIN_WAIT1");  
                                  break;  
                          case   MIB_TCP_STATE_ESTAB:  
                                  strcpy(strState,   "ESTAB");  
                                  break;  
                          case   MIB_TCP_STATE_SYN_RCVD:  
                                  strcpy(strState,   "SYN_RCVD");  
                                  break;  
                          case   MIB_TCP_STATE_SYN_SENT:  
                                  strcpy(strState,   "SYN_SENT");  
                                  break;  
                          case   MIB_TCP_STATE_LISTEN:  
                                  strcpy(strState,   "LISTEN");  
                                  break;  
                          case   MIB_TCP_STATE_DELETE_TCB:  
                                  strcpy(strState,   "DELETE");  
                                  break;  
                          default:  
                                  printf("Error:   unknown   state!/n");  
                                  break;  
                          }  
                          inadLocal.s_addr   =   pTcpTable->table[i].dwLocalAddr;  
   
                          if   (strcmp(strState,   "LISTEN")   !=   0)  
                          {  
                                  dwRemotePort   =   pTcpTable->table[i].dwRemotePort;  
                          }  
                          else  
                                  dwRemotePort   =   0;  
   
   
                          inadRemote.s_addr   =   pTcpTable->table[i].dwRemoteAddr;  
                          strcpy(szLocalIp,   inet_ntoa(inadLocal));  
                          strcpy(szRemIp,   inet_ntoa(inadRemote));  
                          printf("%20s   %10u   %20s   %10u   %s/n",    
                                  szLocalIp,     ntohs((unsigned   short)(0x0000FFFF   &   pTcpTable->table[i].dwLocalPort)),  
                                  szRemIp,   ntohs((unsigned   short)(0x0000FFFF   &   dwRemotePort)),  
                                  strState);  
                  }  
          }  
  }

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值