当客户端超时时,servlet会抛出IOException吗?

时间:2011-03-02 02:08:16

标签: java servlets

首先原谅我的英语不好。我实现了一个servlet和一个客户端,据我所知,在客户端读取超时后,servlet会在刷新缓冲区时抛出IOException。但在我的实验中,有时我得到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(作为webapp)后,我运行client,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仍然继续向缓冲区写入内容,甚至在刷新缓冲区时没有IOException(#flushBuffer-2)。

如果我稍微修改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()会将内容从响应缓冲区刷新到socket发送缓冲区,而且我相信response.flushBuffer()会触发套接字发送缓冲区的flush(),这就是为什么一旦我调用了response.flushBuffer ()客户端可以立即获取返回的内容(我认为就像servlet API一样,套接字发送缓冲区至少有2个刷新机制,一个是显式调用flush(),另一个是数据大小超过缓冲区允许的大小。我的情况,response.flushBuffer()应该显式调用socket.flush()...这只是我的理解。)

现在我使用wireshark监控客户端和服务器之间的网络流量,tcp流如下:

  1. 客户端读取超时。
  2. 客户端向服务器发出“FIN”信号。
  3. 服务器返回'FIN'的'ACK'。
  4. server在response.flushBuffer()。
  5. 时返回一个数据包
  6. 客户端在收到数据后向服务器发出'RST'信号 包#4。
  7. 让我们回顾一下servlet的实现。 在第一种情况下,我调用了响应循环的response.flushBuffer(),它只会被调用一次。一旦调用了response.flushBuffer,servlet会立即将内容刷新到客户端,然后客户端将发出'RST'来重置套接字连接。

    在第二种情况下,我在dowhile循环中调用response.flushBuffer(),并完全调用4次。第一次调用后,客户端将发出'RST'来重置套接字连接,然后再次调用response.flushBuffer()时,将抛出IOException。

    所以我的结论是:

    1. 'FIN'信号不会关闭连接,只表示没有更多的数据包用于发送, 连接仍在继续。
    2. 发出'FIN'后,客户收到数据包后会发出'RST'。
    3. 如果不想捕获IOException,也许我们应该调用response.flushBuffer()至少2个 次。
    4. 我是对的吗?


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

      1。在客户端,一旦关闭inputStream(上面的HttpCometClient.java中的br.close()),就会向另一方发出'FIN',不再允许读写(只需要'FIN,ACK'来自另一方)。

      2。客户端将发送'RST'以响应接收已关闭套接字的数据包(期望'FIN,ACK',但'PSH')。

      第3。一旦服务器端收到'RST',尝试写入客户端时将丢弃IOExceptio。

2 个答案:

答案 0 :(得分:3)

flush()将强制它们从 Java流进入内核中的套接字发送缓冲区。从那里数据以异步方式发送。像那样重复冲洗完全没有任何结果;如果套接字发送缓冲区中有空间并且此时连接仍在运行,则堆栈不会报告错误,因此不会抛出任何异常。写入时唯一可以获得异常的方法是,如果已经传输了足够的数据导致TCP堆栈写入超时或从另一端引入RST。这个过程有延迟。

  

我也相信   response.flushBuffer()将触发   套接字发送缓冲区的flush()

没有。我已经涵盖了上述内容。

  

至少有2次潮红   套接字发送缓冲区的机制之一   是明确调用flush(),另一个是   是数据大小超过缓冲区   允许的大小

Java流的确如此。套接字发送缓冲区不是这样。填写将阻止发送应用程序。

重新启动网络跟踪,来自客户端的FIN告诉服务器客户端不再发送任何数据。这是方向的EOS。响应服务器写入的客户端RST告诉服务器客户端已完全关闭此连接。如果服务器在服务器仍处于网络write()调用时收到RST ,则该调用将收到错误并将引发Java异常。但是由于套接字发送缓冲区的写入是异步的,因此该条件不一定成立:它取决于写入的大小,延迟,带宽,所有缓冲区的先前状态......所以IOException不是总是抛出。

  

也许我们应该调用   response.flushBuffer()至少2   次。

没有。我已经涵盖了上述内容。刷新Java流后,重复flush()是一个无操作。

答案 1 :(得分:0)

如果使用tomcat,则可以将[socket.txBufSize]设置为小于响应内容的长度。

这是[socket.txBufSize]属性的描述: (int)套接字发送缓冲区(SO_SNDBUF)的大小,以字节为单位。默认值为43800