HttpClient 與 Close_Wait

服務器A須要經過HttpClient去鏈接另外一個系統B提供的服務,運行一段時間後拋出如下異 常:java.net.SocketException: Connection reset by peer: socket write error close_wait java

在服務器B上運行netstat命令,發現大量鏈接處於CLOSE_WAIT 狀態。 apache

問題分析: 服務器

簡單來講CLOSE_WAIT數目過大是因爲被動關閉鏈接處理不當致使的。 網絡

我說一個場景,服務器A會去請求服務器B上面的apache獲取文件資源,正常狀況下,若是請求成功,那麼在抓取完資源後服務器A會主動發出關閉連 接的請求,這個時候就是主動關閉鏈接,鏈接狀態咱們能夠看到是TIME_WAIT。若是一旦發生異常呢?假設請求的資源服務器B上並不存在,那麼這個時候 就會由服務器B發出關閉鏈接的請求,服務器A就是被動的關閉了鏈接,若是服務器A被動關閉鏈接以後本身並無釋放鏈接,那就會形成CLOSE_WAIT的 狀態了。 socket

因此很明顯,問題仍是處在程序裏頭。 tcp

原始代碼塊: url

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

HttpClient使用咱們經常使用的InputStream.close()來確認鏈接關閉,分析上面的代碼,一旦出現非200的鏈接,這個鏈接將永遠僵死在鏈接池裏頭,由於inputStream得不到初始化,永遠不會調用close()方法了。 spa

經過代碼稍微修改,更嚴謹的處理異常狀況就能夠解決問題了: .net

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

顯示調用HttpGet的abort,這樣就會直接停止此次鏈接,咱們在遇到異常的時候應該顯示調用,由於誰能保證異常是在InputStream in賦值以後才拋出的呢。 code

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代碼 

 
  1. closeIdleConnections(long idleTimeout)  

這樣的方法。

若是不須要重用連接,則直接在httpmethod建立時,設置一個http頭信息就能夠了

Java代碼

 
  1. httpmethod.setRequestHeader("Connection", "close");   

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

相關文章
相關標籤/搜索