天天看點

TCP TIME_WAIT解決方案

寫過TCP伺服器的人都知道,要解決主動關閉後的TIME_WAIT狀态是件很麻煩的事情,如果伺服器設定Linger生效且延遲為0秒,則伺服器發送給Client的最後一個資料包極可能丢失。Server端TIME_WAIT過多會導緻伺服器效率急劇下降,Client端TIME_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()實作。

TCP TIME_WAIT解決方案

  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生效,且延遲為0秒XSetSocketLinger(),設定為非阻塞模式XSetSocketBlockingMode(),便于快速connect到SERVER ,異步檢測是否連接配接成功。

 将句柄壓入寫檢測隊列。 

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内完成響應,平均每秒約能完成1422個TCP并發請求。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狀态,太漂亮了。

TCP TIME_WAIT解決方案

 接下來我會在Linux環境下進行繼續驗證。 

方案關鍵點:

 将通常情況下應該由SERVER端主動關閉的句柄逆向思維轉由CLIENT端主動關閉,則SERVER端socket 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,    

      TCP_MAX_STATES    

  };  

  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);  

                  }  

          }  

  }