2011-03-02 36 views
1

首先原諒我的英語不好。我實現了一個servlet和一個客戶端,據我瞭解,在客戶端讀取超時之後,servlet會在刷新緩衝區時拋出IOException。但是在我的實驗中,有時候我得到了IOException,有時候不是。當客戶端超時時,servlet會拋出IOException嗎?

下面是我的servlet:

public class HttpClientServlet extends HttpServlet { 
Log logger = LogFactory.getLog(HttpClientServlet.class); 
private static final long serialVersionUID = 1L; 

@Override 
public void doPost(HttpServletRequest request, HttpServletResponse response) 
     throws ServletException, IOException { 
    logger.debug("request:" + request); 
    logger.debug("response:" + response); 
    int flag = 1; 
    try{ 
     do{ 
      if (flag > 1) 
       Thread.currentThread().sleep(5 * 1000); 
      PrintWriter writer = response.getWriter(); 
      String resp = "now:" + System.currentTimeMillis(); 
      writer.println(resp); 
      logger.debug("[flag:" + flag + "]1.isCommitted():" + response.isCommitted()); 
      // response.flushBuffer(); //#flushBuffer-1 
      logger.debug("[flag:" + flag + "]2.isCommitted():" + response.isCommitted()); 
      flag ++; 
     } 
     while (flag < 5); 
     response.flushBuffer(); //#flushBuffer-2 
    } 
    catch(Exception e){ 
     logger.error(e.getMessage(), e); 
    } 
} 
} 

下面是我的客戶:

public class HttpCometClient { 
Log logger = LogFactory.getLog(HttpCometClient.class); 

public void comet(String url) throws Exception{ 
    BufferedReader br = null; 
    try{ 
     URL u = new URL(url); 
     HttpURLConnection urlConn = (HttpURLConnection)u.openConnection(); 
     urlConn.setDoInput(true); 
     urlConn.setReadTimeout(1*1000); 


     // read output 
     br = new BufferedReader(new InputStreamReader(urlConn.getInputStream())); 
     while (true){ 
      for(String tmp = br.readLine(); tmp != null;){ 
       System.out.println("[new response] " + tmp); 
       tmp = br.readLine(); 
      } 
     } 
    } 
    catch(SocketTimeoutException e){ 
     e.printStackTrace(); 
    } 
    finally{ 
     logger.debug("close reader."); 
     if (br != null) br.close(); 
    } 

} 

public static void main(String args[]){ 
    try{ 
     HttpCometClient hcc = new HttpCometClient(); 
     hcc.comet("http://localhost:8080/httpclient/httpclient"); 
    } 
    catch(Exception e){ 
     e.printStackTrace(); 
    } 
} 
} 

部署servlet來的tomcat6(如web應用程序),我運行客戶端後,從Tomcat控制檯輸出如下:

 
[2011-03-02 09:33:33,312][http-8080-1][DEBUG][HttpClientServlet] [flag:1]1.isCommitted():false 
[2011-03-02 09:33:33,312][http-8080-1][DEBUG][HttpClientServlet] [flag:1]2.isCommitted():false 
[2011-03-02 09:33:38,312][http-8080-1][DEBUG][HttpClientServlet] [flag:2]1.isCommitted():false 
[2011-03-02 09:33:38,312][http-8080-1][DEBUG][HttpClientServlet] [flag:2]2.isCommitted():false 
[2011-03-02 09:33:43,312][http-8080-1][DEBUG][HttpClientServlet] [flag:3]1.isCommitted():false 
[2011-03-02 09:33:43,312][http-8080-1][DEBUG][HttpClientServlet] [flag:3]2.isCommitted():false 
[2011-03-02 09:33:48,312][http-8080-1][DEBUG][HttpClientServlet] [flag:4]1.isCommitted():false 
[2011-03-02 09:33:48,312][http-8080-1][DEBUG][HttpClientServlet] [flag:4]2.isCommitted():false 

在這種情況下,客戶端在1秒後已經讀取超時,但servlet仍然保持將內容寫入緩衝區,甚至在刷新緩衝區(#flushBuffer-2)時無IOException。

如果我修改Servlet一點,取消 '#flushBuffer-1',並評論 '#flushBuffer-2':

do{ 
... 
String resp = "now:" + System.currentTimeMillis(); 
writer.println(resp); 
logger.debug("[flag:" + flag + "]1.isCommitted():" + response.isCommitted()); 
response.flushBuffer(); //#flushBuffer-1 
logger.debug("[flag:" + flag + "]2.isCommitted():" + response.isCommitted()); 
flag ++; 
} 
while (flag < 5); 
// response.flushBuffer(); //#flushBuffer-2 
... 

現在則拋出IOException出來:

 
[2011-03-02 10:02:14,609][http-8080-1][DEBUG][HttpClientServlet] [flag:1]1.isCommitted():false 
[2011-03-02 10:02:14,609][http-8080-1][DEBUG][HttpClientServlet] [flag:1]2.isCommitted():true 
[2011-03-02 10:02:19,609][http-8080-1][DEBUG][HttpClientServlet] [flag:2]1.isCommitted():true 
[2011-03-02 10:02:19,609][http-8080-1][DEBUG][HttpClientServlet] [flag:2]2.isCommitted():true 
[2011-03-02 10:02:24,609][http-8080-1][DEBUG][HttpClientServlet] [flag:3]1.isCommitted():true 
[2011-03-02 10:02:24,625][http-8080-1][ERROR][HttpClientServlet] ClientAbortException: java.net.SocketException: Software caused connection abort: socket write error 
     at org.apache.catalina.connector.OutputBuffer.doFlush(OutputBuffer.java:319) 
     at org.apache.catalina.connector.OutputBuffer.flush(OutputBuffer.java:288) 
     at org.apache.catalina.connector.Response.flushBuffer(Response.java:549) 
    ... 

一般來說,當客戶端關閉連接時,我想要捕獲一個IOException,但是如何保證IOexception將被拋出?你能解釋爲什麼在第一種情況下沒有IOException,但是在第二種情況下拋出IOException? 在此先感謝!


感謝@BalusC,但我仍然有一些問題。我們知道response.flushBuffer()會將響應緩衝區中的內容刷新到套接字發送緩衝區,並且我相信response.flushBuffer()會觸發套接字發送緩衝區的flush(),這就是爲什麼一旦我調用response.flushBuffer ()客戶端可以立即獲得返回的內容(我認爲就像servlet API一樣,至少有2個套接字發送緩衝區的刷新機制,一個是顯式調用flush(),另一個是數據大小超過緩衝區允許的大小。我的情況,response.flushBuffer()應明確呼叫socket.flush()...這只是我的理解)。

現在我使用Wireshark來監視客戶端和服務器之間的網絡流量,以及TCP流如下:

  1. 客戶端讀取超時。
  2. 客戶端向服務器發出'FIN'信號。
  3. 服務器返回'FIN'的'ACK'。
  4. 服務器在response.flushBuffer()時返回一個數據包。
  5. 客戶端在收到#4的數據包 包後向服務器發出'RST'信號。

讓我們回顧一下servlet的實現。 在第一種情況下,我調用response.flushBuffer()out dowhile循環,它只會被調用一次。一旦調用response.flushBuffer,servlet將立即將內容刷新到客戶端,然後客戶端將發出'RST'來重置套接字連接。

在第二種情況下,我調用響應。flushbuffer()在dowhile循環中,並且總共調用4次。在第一次調用之後,客戶端將發出'RST'來重置套接字連接,然後再次調用response.flushBuffer()時,IOException將被拋出。

所以我的結論是:

  1. 「FIN」信號將不會關閉連接,僅用於發送沒有更多的數據包時, 連接仍上漲。
  2. 發出'FIN'後,客戶端收到數據包後會發出'RST'。
  3. 如果不想捕獲IOException,也許我們應該調用response.flushBuffer()至少2
    次。

對嗎?


非常感謝!現在我更清楚地理解它了。只是爲了提醒自己:

1.在客戶端,一旦關閉inputStream(br.close(),在上面的HttpCometClient.java中),就會向另一端發送'FIN',不再有讀取和寫入允許(僅期望來自另一側的'FIN,ACK')。

2.客戶端將發送'RST'來響應接收封閉套接字的數據包(期望'FIN,ACK',但'PSH')。

3.服務器端收到'RST'後,嘗試寫入客戶端時會拋出IOExceptio。

回答

3

沖洗()將迫使他們離開Java流到內核的套接字發送緩衝區。從那裏數據被異步發送。重複像這樣的沖洗完全沒有什麼;並且如果在套接字發送緩衝區中有空間並且連接仍處於啓動狀態,那麼堆棧不會報告錯誤,因此不會拋出異常。寫入時發生異常的唯一方法是,是否有足夠的數據已經傳輸,導致TCP堆棧寫入超時或從另一端引發RST。這個過程存在延遲。

也相信 response.flushBuffer()將觸發 套接字發送緩衝區的刷新()

號我已經涵蓋了以上。 socket發送緩衝區的

至少有2沖洗 機制,一個 是調用flush()明確,其他 是數據的大小超過緩衝區 允許大小

這是真正的Java流。它不是套接字發送緩衝區。填充將阻止發送應用程序。

重新建立您的網絡跟蹤,來自客戶端的FIN告訴服務器客戶端不再發送任何數據。在這個方向是EOS。響應服務器寫入的客戶端RST告訴服務器客戶端已完全關閉此連接。如果服務器在服務器仍處於網絡寫入()調用而服務器收到RST 該調用將收到錯誤並將引發Java異常。但是,由於來自套接字發送緩衝區的寫入是異步的,所以該條件不一定成立:它取決於寫入的大小,延遲,帶寬,所有緩衝區的先前狀態......所以IOException不是總是拋出。

也許我們應該調用 response.flushBuffer()至少2 倍。

不,我已經在上面介紹過了。在刷新Java流之後,重複flush()是無操作。

+0

既然你已經寫了關於這方面的書,你一定是對的:)謝謝。 – BalusC 2011-03-02 02:27:45

+0

**我也相信response.flushBuffer()會觸發flush()套接字發送緩衝區 ... 不,我已經在上面覆蓋了** ... 爲什麼客戶端會立即得到返回的內容一旦我每次調用servlet中的response.flushBuffer()?我認爲套接字發送緩衝區沒有滿。 – Ramon 2011-03-02 05:35:23

+0

因爲數據立即發送。這並不意味着flushBuffer()導致它。網絡狀況導致它。在發送任何東西之前,TCP並不一定等待發送緩衝區已滿。 – EJP 2011-03-02 06:40:34