首先原諒我的英語不好。我實現了一個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流如下:
- 客戶端讀取超時。
- 客戶端向服務器發出'FIN'信號。
- 服務器返回'FIN'的'ACK'。
- 服務器在response.flushBuffer()時返回一個數據包。
- 客戶端在收到#4的數據包 包後向服務器發出'RST'信號。
讓我們回顧一下servlet的實現。 在第一種情況下,我調用response.flushBuffer()out dowhile循環,它只會被調用一次。一旦調用response.flushBuffer,servlet將立即將內容刷新到客戶端,然後客戶端將發出'RST'來重置套接字連接。
在第二種情況下,我調用響應。flushbuffer()在dowhile循環中,並且總共調用4次。在第一次調用之後,客戶端將發出'RST'來重置套接字連接,然後再次調用response.flushBuffer()時,IOException將被拋出。
所以我的結論是:
- 「FIN」信號將不會關閉連接,僅用於發送沒有更多的數據包時, 連接仍上漲。
- 發出'FIN'後,客戶端收到數據包後會發出'RST'。
- 如果不想捕獲IOException,也許我們應該調用response.flushBuffer()至少2
次。
對嗎?
非常感謝!現在我更清楚地理解它了。只是爲了提醒自己:
1.在客戶端,一旦關閉inputStream(br.close(),在上面的HttpCometClient.java中),就會向另一端發送'FIN',不再有讀取和寫入允許(僅期望來自另一側的'FIN,ACK')。
2.客戶端將發送'RST'來響應接收封閉套接字的數據包(期望'FIN,ACK',但'PSH')。
3.服務器端收到'RST'後,嘗試寫入客戶端時會拋出IOExceptio。
既然你已經寫了關於這方面的書,你一定是對的:)謝謝。 – BalusC 2011-03-02 02:27:45
**我也相信response.flushBuffer()會觸發flush()套接字發送緩衝區 ... 不,我已經在上面覆蓋了** ... 爲什麼客戶端會立即得到返回的內容一旦我每次調用servlet中的response.flushBuffer()?我認爲套接字發送緩衝區沒有滿。 – Ramon 2011-03-02 05:35:23
因爲數據立即發送。這並不意味着flushBuffer()導致它。網絡狀況導致它。在發送任何東西之前,TCP並不一定等待發送緩衝區已滿。 – EJP 2011-03-02 06:40:34