开发者

When client timeout, will servlet throw IOException?

开发者 https://www.devze.com 2023-02-14 10:32 出处:网络
First forgive my bad english. I implement a servlet and a client, in my understanding, after client read timeout, servlet will throw IOException when flush buffer. But in my experiment, sometimes I go

First forgive my bad english. I implement a servlet and a client, in my understanding, after client read timeout, servlet will throw IOException when flush buffer. But in my experiment, sometimes I got IOException, sometimes not.

Below is my 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);
    }
}
}

Below is my client:

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();
    }
}
}

After deploying the servlet to tomcat6(as a webapp), I run client, the output from tomcat console as below:

[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

In this case, client is already read-timeout after 1 second, but servlet still keep writing content to buffer, even no IOException when flush buffer(#flushBuffer-2).

If I modify the servlet a little, uncomment '#flushBuffer-1', and comment '#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
...

Now a IOException will be thrown out:

[2011-03-02 10:02:14,609][http-8080-1][DEBUG][HttpClientSer开发者_Go百科vlet] [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)
     ...

In general, I want to catch a IOException when client closed the connection, but how can I guarantee that a IOexception will be thrown out?? Can you explain why in the first case no IOException, but in the second, a IOException is thrown out? Thanks in advance!


Thanks @BalusC, but I still have some questions. We know that response.flushBuffer() will flush the content from response buffer to socket send buffer, and also I believe that response.flushBuffer() will trigger the flush() of socket send buffer, that is why once I called response.flushBuffer() the client can get the returned content immediately(I think just like servlet API, there are at least 2 flushing mechanisms of socket send buffer, one is call flush() explicitly, the other is the data size exceed the buffer allowed size. I my case, response.flushBuffer() should call socket.flush() explicitly...this is just my understanding).

Now I use wireshark to monitor the net traffic between the client and server, and the tcp stream is as below:

  1. the client read timeout.
  2. the client issue a 'FIN' signal to the server.
  3. server return a 'ACK' of the 'FIN'.
  4. server return a data package when response.flushBuffer().
  5. client issue a 'RST' signal to the server after receiving the data package of #4.

Let's look back to the servlet implementation. In the first case, I invoke response.flushBuffer() out the dowhile loop, it will be invoked only once. Once invoked response.flushBuffer, servlet will flush content to client immediately, then client will issue the 'RST' to reset socket connection.

In the second case, I invoke response.flushBuffer() in the dowhile loop, and invoke totaly 4 times. After the first invoke, the client will issue 'RST' to reset the socket connection, then when invoke response.flushBuffer() again, a IOException will be thrown out.

So my conclusion is:

  1. 'FIN' signal won't close the connection, only means no more packet for sending, the connection is still up.
  2. After issuing 'FIN', client will issue 'RST' once received a data package.
  3. If want't catch IOException, maybe we should invoke response.flushBuffer() at least 2

    times.

Am I right?


Thanks very much! Now I understand it more clearly. Just to remind myself:

1. At the client side, once close the inputStream(br.close() in above HttpCometClient.java), a 'FIN' will be issued to the other side, no more reading and writing permitted(only expect a 'FIN,ACK' from the other side).

2. The client will send a 'RST' to response to receiving a packet for a closed socket(expect 'FIN,ACK', but 'PSH').

3. Once the server side received 'RST', a IOExceptio will be thrown out when try to write to the client.


The flush() will force them out of the Java stream into the socket send buffer in the kernel. From there the data is sent asynchronously. Repeating the flush like that accomplishes precisely nothing; and if there is room in the socket send buffer and the connection is still up at that point there is no possibility of an error being reported by the stack, so no exception will be thrown. The only way you can get an exception when writing is if there has been enough data already transmitted to cause a TCP stack write timeout or induce an RST from the other end. There is latency in this process.

also I believe that response.flushBuffer() will trigger the flush() of socket send buffer

No. I've covered that above.

there are at least 2 flushing mechanisms of socket send buffer, one is call flush() explicitly, the other is the data size exceed the buffer allowed size

That's true of the Java stream. It isn't true of the socket send buffer. Filling that will block the sending application.

Re your network trace, the FIN from the client tells the server the client isn't sending any more data. It is EOS in that direction. The client's RST in response to the server's write tells the server that the client has completely closed this connection. If the server receives RST while the server is still in the network write() call that call will receive an error and a Java exception will be thrown. But as writing from the socket send buffer is asynchronous, that condition doesn't necessarily hold: it depends on the size of the write, the latency, the bandwidth, the prior state of all the buffers ... so the IOException isn't always thrown.

maybe we should invoke response.flushBuffer() at least 2 times.

No. I have already covered that above. After the Java stream is flushed, repeating the flush() is a no-operation.


If you use tomcat, you can set [socket.txBufSize] small than response contents length.

here is the description of [socket.txBufSize] attribute: (int)The socket send buffer (SO_SNDBUF) size in bytes. Default value is 43800

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号