天天看點

HttpClient 與 Close_Wait

伺服器A需要通過HttpClient去連接配接另一個系統B提供的服務,運作一段時間後抛出以下異常:

java.net.SocketException: Connection reset by peer: socket write error close_wait

在伺服器B上運作netstat指令,發現大量連接配接處于CLOSE_WAIT 狀态。

問題分析:

簡單來說CLOSE_WAIT數目過大是由于被動關閉連接配接處理不當導緻的。

我說一個場景,伺服器A會去請求伺服器B上面的apache擷取檔案資源,正常情況下,如果請求成功,那麼在抓取完資源後伺服器A會主動發出關閉連接配接的請求,這個時候就是主動關閉連接配接,連接配接狀态我們可以看到是TIME_WAIT。如果一旦發生異常呢?假設請求的資源伺服器B上并不存在,那麼這個時候就會由伺服器B發出關閉連接配接的請求,伺服器A就是被動的關閉了連接配接,如果伺服器A被動關閉連接配接之後自己并沒有釋放連接配接,那就會造成CLOSE_WAIT的狀态了。

是以很明顯,問題還是處在程式裡頭。

原始代碼塊:

  1. try  
  2.         {  
  3.             client = HttpConnectionManager.getHttpClient();  
  4.             HttpGet get = new HttpGet();  
  5.             get.setURI(new URI(urlPath));  
  6.             HttpResponse response = client.execute(get);  
  7.             if (response.getStatusLine ().getStatusCode () != 200) {  
  8.                 return null;  
  9.             }  
  10.             HttpEntity entity =response.getEntity();  
  11.             if( entity != null ){  
  12.                 in = entity.getContent();  
  13.                 .....  
  14.             }  
  15.             return sb.toString ();  
  16.         }  
  17.         catch (Exception e)  
  18.         {  
  19.             e.printStackTrace ();  
  20.             return null;  
  21.         }  
  22.         finally  
  23.         {  
  24.             if (isr != null){  
  25.                 try  
  26.                 {  
  27.                     isr.close ();  
  28.                 }  
  29.                 catch (IOException e)  
  30.                 {  
  31.                     e.printStackTrace ();  
  32.                 }  
  33.             }  
  34.             if (in != null){  
  35.                 try  
  36.                 {  
  37.                     <span style="color:#ff0000;">in.close ();</span>  
  38.                 }  
  39.                 catch (IOException e)  
  40.                 {  
  41.                     e.printStackTrace ();  
  42.                 }  
  43.             }  
  44.         } 

HttpClient使用我們常用的InputStream.close()來确認連接配接關閉,分析上面的代碼,一旦出現非200的連接配接,這個連接配接将永遠僵死在連接配接池裡頭,因為inputStream得不到初始化,永遠不會調用close()方法了。

通過代碼稍微修改,更嚴謹的處理異常情況就可以解決問題了:

  1. public static String readNet (String urlPath)  
  2.     {  
  3.         StringBuffer sb = new StringBuffer ();  
  4.         HttpClient client = null;  
  5.         InputStream in = null;  
  6.         InputStreamReader isr = null;  
  7.         HttpGet get = new HttpGet();  
  8.         try  
  9.         {  
  10.             client = HttpConnectionManager.getHttpClient();  
  11.             get.setURI(new URI(urlPath));  
  12.             HttpResponse response = client.execute(get);  
  13.             if (response.getStatusLine ().getStatusCode () != 200) {  
  14.                 get.abort();  
  15.                 return null;  
  16.             }  
  17.             HttpEntity entity =response.getEntity();  
  18.             if( entity != null ){  
  19.                 in = entity.getContent();  
  20.                 ......  
  21.             }  
  22.             return sb.toString ();  
  23.         }  
  24.         catch (Exception e)  
  25.         {  
  26.             get.abort();  
  27.             e.printStackTrace ();  
  28.             return null;  
  29.         }  
  30.         finally  
  31.         {  
  32.             if (isr != null){  
  33.                 try  
  34.                 {  
  35.                     isr.close ();  
  36.                 }  
  37.                 catch (IOException e)  
  38.                 {  
  39.                     e.printStackTrace ();  
  40.                 }  
  41.             }  
  42.             if (in != null){  
  43.                 try  
  44.                 {  
  45.                     in.close ();  
  46.                 }  
  47.                 catch (IOException e)  
  48.                 {  
  49.                     e.printStackTrace ();  
  50.                 }  
  51.             }  
  52.         }  
  53.     }  

顯示調用HttpGet的abort,這樣就會直接中止這次連接配接,我們在遇到異常的時候應該顯示調用,因為誰能保證異常是在InputStream in指派之後才抛出的呢。

more:

首先我們知道,如果我們的伺服器程式處于CLOSE_WAIT狀态的話,說明套接字是被動關閉的!

因為如果是CLIENT端主動斷掉目前連接配接的話,那麼雙方關閉這個TCP連接配接共需要四個packet:

Client –-> FIN  –-> Server

Client <–- ACK  <–- Server

這時候Client端處于FIN_WAIT_2狀态;而Server 程式處于CLOSE_WAIT狀态。

Client <–- FIN  <–- Server

這時Server 發送FIN給Client,Server 就置為LAST_ACK狀态。

Client –-> ACK  –-> Server

Client回應了ACK,那麼Server 的套接字才會真正置為CLOSED狀态。

Server 程式處于CLOSE_WAIT狀态,而不是LAST_ACK狀态,說明還沒有發FIN給Client,那麼可能是在關閉連接配接之前還有許多資料要發送或者其他事要做,導緻沒有發這個FIN packet。

通常來說,一個CLOSE_WAIT會維持至少2個小時的時間(這個時間外網伺服器通常會做調整,要不然太危險了)。如果有個流氓特地寫了個程式,給你造成一堆的CLOSE_WAIT,消耗

你的資源,那麼通常是等不到釋放那一刻,系統就已經解決崩潰了。

隻能通過修改一下TCP/IP的參數,來縮短這個時間:修改tcp_keepalive_*系列參數有助于解決這個問題。

但是實際上,還是主要是因為我們的程式代碼有問題,

more:

最近做httpclient做轉發服務,發現伺服器上總是有很多close_wait狀态的連接配接,而且這些連接配接都不會關閉,最後導緻伺服器沒法建立新的網絡連接配接,進而停止響應。 

        後來在網上搜尋了一下,發現解決的方法也很簡單,如果想重用連接配接,那就使用連接配接管理器,從連接配接管理器裡擷取連接配接,然後定時的用連接配接管理器來釋放空閑連接配接。httpclient自帶了SimpleHttpConnectionManager,提供了

Java代碼  

HttpClient 與 Close_Wait
  1. closeIdleConnections(long idleTimeout)   

這樣的方法。 

        如果不需要重用連結,則直接在httpmethod建立時,設定一個http頭資訊就可以了 

Java代碼  

HttpClient 與 Close_Wait
  1. httpmethod.setRequestHeader("Connection", "close");  

這樣就不會有惱人的close_wait了。

more:

http://blog.sina.com.cn/s/blog_4c2edbc70100o713.html