Когда тайм-аут клиента, сервлет сгенерирует IOException? - PullRequest
1 голос
/ 02 марта 2011

Сначала прости мой плохой английский.Я реализую сервлет и клиента, в моем понимании, после тайм-аута чтения клиента, сервлет будет генерировать 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-поток выглядит следующим образом:

  1. тайм-аут чтения клиента.
  2. клиент выдает серверу сигнал 'FIN'.
  3. сервер возвращает ACKсервера 'FIN'.
  4. возвращает пакет данных, когда response.flushBuffer ().
  5. клиент выдает сигнал 'RST' серверу после полученияВ пакете данных # 4.

Давайте вернемся к реализации сервлета.В первом случае я вызываю response.flushBuffer () из цикла Dowhile, он будет вызван только один раз.После вызова response.flushBuffer сервлет немедленно сбросит содержимое на клиент, затем клиент выдаст «RST» для сброса соединения с сокетом.

Во втором случае я вызываю response.flushBuffer () в цикле Dowhile,и вызвать всего 4 раза.После первого вызова клиент выдаст RST для сброса соединения с сокетом, затем при повторном вызове response.flushBuffer () будет выдано исключение IOException.

Итак, мой вывод:

  1. Сигнал 'FIN' не будет закрывать соединение, только означает, что больше нет пакета для отправки, соединение все еще установлено.
  2. После выдачи 'FIN' клиент выдаст 'RST' после полученияпакет данных.
  3. Если вы не хотите перехватывать IOException, возможно, нам следует вызывать response.flushBuffer () как минимум 2
    раз.

Я прав?


Большое спасибо!Теперь я понимаю это более четко.Просто чтобы напомнить себе:

1.На стороне клиента, как только закроете inputStream (br.close () в вышеупомянутом HttpCometClient.java), на другую сторону будет выдано 'FIN', чтение и запись не разрешены (ожидайте только 'FIN, ACK' отс другой стороны).

2.Клиент отправит «RST» в ответ на получение пакета для закрытого сокета (ожидайте «FIN, ACK», но «PSH»).

3.Как только серверная сторона получила 'RST', IOExceptio будет выброшено при попытке записи клиенту.

Ответы [ 2 ]

3 голосов
/ 02 марта 2011

flush () принудительно выводит их из потока Java в буфер отправки сокета в ядре. Оттуда данные отправляются асинхронно. Подобное повторение флеша ничего не дает; и если в буфере отправки сокета есть место, и в этот момент соединение все еще установлено, то стек не сможет сообщить об ошибке, поэтому исключение не будет выдано. Единственный способ получить исключение при записи - это если уже было передано достаточно данных, чтобы вызвать тайм-аут записи в стек TCP или вызвать RST с другого конца. В этом процессе есть задержка.

и я верю, что response.flushBuffer () сработает сброс () буфера отправки сокета

Нет. Я рассмотрел это выше.

есть как минимум 2 промывки механизмы буфера отправки сокетов, один это вызов flush () явно, другой размер данных превышает буфер допустимый размер

Это верно для потока Java. Это не верно для буфера отправки сокета. Заполнение, которое заблокирует отправку заявки.

По вашей сетевой трассировке FIN от клиента сообщает серверу, что клиент больше не отправляет данные. Это EOS в этом направлении. RST клиента в ответ на запись на сервер сообщает серверу, что клиент полностью закрыл это соединение. Если сервер получает RST , пока сервер еще находится в сети, вызов write () , этот вызов получит ошибку и будет сгенерировано исключение Java. Но поскольку запись из буфера отправки сокета является асинхронной, это условие не обязательно выполняется: оно зависит от размера записи, задержки, пропускной способности, предшествующего состояния всех буферов ... поэтому IOException не всегда брошен.

может быть, нам следует призвать response.flushBuffer () как минимум 2 раз.

Нет. Я уже рассмотрел это выше. После сброса потока Java повторение flush () не выполняется.

0 голосов
/ 15 марта 2019

Если вы используете tomcat, вы можете установить [socket.txBufSize] меньше, чем длина содержимого ответа.

вот описание атрибута [socket.txBufSize]: (int) Буфер отправки сокета (SO_SNDBUF)размер в байтах.Значение по умолчанию 43800

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...