Проверка, если ClientSocket отключился в зависаниях Java - PullRequest
4 голосов
/ 31 марта 2009

Это продолжение до:

этот вопрос

По сути, у меня есть серверный цикл, который управляет соединением с одним отдельным клиентом. В одной точке цикла, если ClientSocket существует, он пытается выполнить чтение, чтобы проверить, все ли еще подключен клиент:

if (bufferedReader.read()==-1 ) {
       logger.info("CONNECTION TERMINATED!");
       clientSocket.close(); 
       setUpSocket(); //sets up the server to reconnect to the client
}else{
        sendHeartBeat(); //Send a heartbeat to the client
}

Проблема в том, что после того, как сокет был создан, приложение будет зависать при чтении, я предполагаю ожидание данных, которые никогда не поступят, так как клиент никогда не отправляет на сервер. До этого все было в порядке, потому что это правильно обрабатывает разъединения (чтение в конечном итоге не будет выполнено, когда клиент отключится), и цикл попытается восстановить соединение. Однако теперь я добавил вышеупомянутый метод sendHeartBeat (), который периодически позволяет клиенту знать, что сервер все еще работает. Если чтение удерживает поток, то сердцебиение никогда не произойдет!

Итак, я предполагаю, что я тестирую, если соединение все еще работает неправильно. Для быстрого взлома я мог бы запустить bufferedReader.read () в отдельном потоке, но тогда у меня возникнут всевозможные проблемы параллелизма, с которыми я действительно не хочу иметь дело.

Так что вопрос несколько раз: 1) Правильно ли я проверяю соединение клиента? 2) Если нет, то как мне это сделать? 3) Если я делаю это правильно, как я получаю чтение, чтобы не держать процесс в заложниках? Или это единственный способ?

Ответы [ 3 ]

9 голосов
/ 31 марта 2009

Когда вы создаете сокет, сначала установите время ожидания:

private int timeout    = 10000;
private int maxTimeout = 25000;

clientSocket.setSoTimeout(timeout);

При этом, если время чтения истекло, вы получите java.net.SocketTimeoutException (который вы должны поймать). Таким образом, вы можете сделать что-то вроде этого, предполагая, что вы ранее установили SO_TIMEOUT, как показано выше, и предполагая, что сердцебиение всегда будет получать ответ от удаленной системы:

volatile long lastReadTime;

try {
    bufferedReader.read();
    lastReadTime = System.currentTimeMillis();
} catch (SocketTimeoutException e) {
    if (!isConnectionAlive()) {
        logger.info("CONNECTION TERMINATED!");
        clientSocket.close(); 
        setUpSocket(); //sets up the server to reconnect to the client
    } else {
        sendHeartBeat(); //Send a heartbeat to the client
    }
}

public boolean isConnectionAlive() {
    return System.currentTimeMillis() - lastReadTime < maxTimeout;
}

Обычный способ справиться с этим - установить тайм-аут на какое-то число (скажем, 10 секунд), а затем отслеживать последний раз, когда вы успешно читали из сокета. Если истекло время ожидания в 2,5 раза, откажитесь от клиента и закройте сокет (на всякий случай отправив пакет FIN на другую сторону).

Если сердцебиение не получит какой-либо ответ от удаленной системы, но это всего лишь способ в конечном итоге сгенерировать IOException раньше, когда соединение оборвется, тогда вы можете сделать это (при условии, что sendHeartBeat сам не скинет IOException):

try {
    if (bufferedReader.read() == -1) {
        logger.info("CONNECTION TERMINATED with EOF!");
        resetConnection();
    }
} catch (SocketTimeoutException e) {
    // This just means our read timed out ... the socket is still good
    sendHeartBeat(); //Send a heartbeat to the client
} catch (IOException e) {
    logger.info("CONNECTION TERMINATED with Exception " + e.getMessage());
    resetConnection();
}

....

private void resetConnection() {
    clientSocket.close(); 
    setUpSocket(); //sets up the server to reconnect to the client
}
1 голос
/ 31 марта 2009

Вы проверяете правильно, вы можете добавить try catch с IOException, если это произойдет.

Существует способ избежать многопоточности, вы можете использовать селектор с неблокирующим сокетом.

public void initialize(){
  //create selector
  Selector selector = Selector.open();
  ServerSocketChannel acceptSocket = ServerSocketChannel.open();
  acceptSocket.configureBlocking(false);
  String bindIp = "127.0.0.1";
  int bindPort = 80;
  acceptSocket.socket().bind(new InetSocketAddress(bindIp, bindPort));
  //register socket in selector for ACCEPT operation
  acceptSocket.register(selector, SelectionKey.OP_ACCEPT);
  this.selector = selector;
  this.serverSocketChannel = serverSocketChannel;
}

public void serverStuff() {
   selector.select(maxMillisecondsToWait);
   Set<SelectionKey> selectedKeys = selector.selectedKeys();
   if( selectedKeys.size() > 0 )
   {
      if( key.isAcceptable() ){
        //you can accept a new connection
        SocketChannel clientSk = serverSocketChannel.accept();
        clientSk.configureBlocking(false);
        //register your SocketChannel in the selector for READ operations
    clientSk.register(selector, SelectionKey.OP_READ);
      } else if( key.isReadable() ){
        //you can read from your socket.
        //it will return you -1 if the connection has been closed
      }
   }

   if( shouldSendHeartBeat() ){
     SendHeartBeat
   }
}
0 голосов
/ 31 марта 2009

Вы должны добавить проверку ошибок при обнаружении отключения. Иногда IOException может быть брошено, когда соединение с другим концом потеряно.

Боюсь, что многопоточность здесь неизбежна. Если вы не хотите блокировать выполнение вашего кода, вам нужно создать отдельный поток.

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