Сначала прости мой плохой английский.Я реализую сервлет и клиента, в моем понимании, после тайм-аута чтения клиента, сервлет будет генерировать IOException при очистке буфера.Но в моем эксперименте иногда я получал IOException, иногда нет.
Ниже мой сервлет:
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();
}
}
}
После развертываниясервлет tomcat6 (как веб-приложение), я запускаю клиент, вывод из консоли 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 секунду, но сервлет все еще продолжает писать содержимоев буфер, даже без IOException при очистке буфера (# flushBuffer-2).
Если я немного модифицирую сервлет, раскомментируйте '# 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 () будет запускать сброс () буфера отправки сокетов, поэтому однажды я вызвал response.flushBuffer() клиент может получить возвращенный контент немедленно (я думаю, что, как и в сервлетном API, существует по крайней мере 2 механизма очистки буфера отправки сокетов, один из них явно вызывает flush (), а другой - размер данных превышает допустимый размер буфера.В моем случае response.flushBuffer () должен явно вызывать socket.flush () ... это только мое понимание).
Теперь я использую wireshark для мониторинга сетевого трафика между клиентом и сервером, иtcp-поток выглядит следующим образом:
- тайм-аут чтения клиента.
- клиент выдает серверу сигнал 'FIN'.
- сервер возвращает ACKсервера 'FIN'.
- возвращает пакет данных, когда response.flushBuffer ().
- клиент выдает сигнал 'RST' серверу после полученияВ пакете данных # 4.
Давайте вернемся к реализации сервлета.В первом случае я вызываю response.flushBuffer () из цикла Dowhile, он будет вызван только один раз.После вызова response.flushBuffer сервлет немедленно сбросит содержимое на клиент, затем клиент выдаст «RST» для сброса соединения с сокетом.
Во втором случае я вызываю response.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 будет выброшено при попытке записи клиенту.