首先原谅我的英语不好。我实现了一个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流如下:
让我们回顾一下servlet的实现。 在第一种情况下,我调用了响应循环的response.flushBuffer(),它只会被调用一次。一旦调用了response.flushBuffer,servlet会立即将内容刷新到客户端,然后客户端将发出'RST'来重置套接字连接。
在第二种情况下,我在dowhile循环中调用response.flushBuffer(),并完全调用4次。第一次调用后,客户端将发出'RST'来重置套接字连接,然后再次调用response.flushBuffer()时,将抛出IOException。
所以我的结论是:
我是对的吗?
非常感谢!现在我更清楚地理解它了。只是提醒自己:
1。在客户端,一旦关闭inputStream(上面的HttpCometClient.java中的br.close()),就会向另一方发出'FIN',不再允许读写(只需要'FIN,ACK'来自另一方)。
2。客户端将发送'RST'以响应接收已关闭套接字的数据包(期望'FIN,ACK',但'PSH')。
第3。一旦服务器端收到'RST',尝试写入客户端时将丢弃IOExceptio。
答案 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