Подтвердить что ты не робот

Как добавить сообщения heartbeat поверх этого Java-кода (для KnockKnockClient/Server)?

Я изучаю следующий базовый код Java-сокета (источник). Это приложение клиент/сервер Knock-Knock-Joke.

В Client мы установили сокет как обычно:

try {
  kkSocket = new Socket("localhost", 4444);
  out = new PrintWriter(kkSocket.getOutputStream(), true);
  in = new BufferedReader(new InputStreamReader(kkSocket.getInputStream()));
} catch( UnknownHostException uhe ){ /*...more error catching */

А потом мы просто читаем и пишем на сервер:

BufferedReader stdIn = new BufferedReader(new InputStreamReader(System.in));
String fromServer;
String fromUser;

while ((fromServer = in.readLine()) != null) {
  System.out.println("Server: " + fromServer);
  if (fromServer.equals("bye."))
      break;

  fromUser = stdIn.readLine();

  if (fromUser != null){
      System.out.println("Client: " + fromUser);
      out.println(fromUser);
  }

И на server, у нас есть соответствующий код, чтобы получить шуточную строку.

    KnockKnockProtocol kkp = new KnockKnockProtocol();

    outputLine = kkp.processInput(null);
    out.println(outputLine);

    while ((inputLine = in.readLine()) != null) {
         outputLine = kkp.processInput(inputLine);
         out.println(outputLine);
         if (outputLine.equals("Bye."))
            break;

Я хочу приложить сердцебиение ко всему тому, что выйдет на консоль, когда он обнаружит, что другая сторона умерла. Потому что то, что происходит сейчас, если я убиваю другую сторону, является исключением - как показано ниже:

enter image description here

Итак, если я запускаю как KnockKnockClient, так и KnockKnockServer, то я закрываю KnockKnockServer, что должно произойти, так это то, что на клиенте я вижу это:

>The system has detected that KnockKnockServer was aborted

Я ищу любые советы. До сих пор я в основном пытался запустить поток демона, который периодически создает новые подключения к другой стороне. Но я смущен о том, какое условие проверить (но я думаю, что это просто значение boolean?). Это правильный подход? Я только что узнал, что в сети есть библиотека под названием JGroups для многоадресной сети - это было бы лучше? Я ищу любые советы.

Мой серверный код до сих пор (извините, что это грязно)

&

Клиентская сторона

спасибо

4b9b3361

Ответ 1

Но исключение, которое вы получаете, именно это! Это говорит вам, что другая сторона просто умерла. Просто поймайте исключение и распечатайте на консоли, что "система обнаружила, что KnockKnockServer был прерван".

Вы используете TCP-соединение, а TCP имеет встроенный механизм harthbeat (keepalive), который сделает это за вас. Просто установите setKeepAlive() в сокет. Это сказано - можно контролировать частоту keepalive для каждого соединения, но я не знаю, как это сделать в java.

http://tldp.org/HOWTO/TCP-Keepalive-HOWTO/overview.html

fooobar.com/questions/92188/...

Ответ 2

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

  • Создайте поток сервера, который будет отправлять сообщения клиенту каждые 10 секунд.

    public class receiver extends Thread{
    
      public static bool hearbeatmessage=true;
    
      Socket clientSocket=new Socket();
      PrintWriter out=new PrintWriter();
      public receiver(Socket clientsocket){
      clientSocket=clientsocket;
      out = new PrintWriter(clientSocket.getOutputStream(), true);
    }
    
      public void run(){
    
        while(true)
        {
    
          if(heartbeatmessage){
            thread.sleep(10000);
            out.println("heartbeat");
    
          }
        }            
      }
    }
    

В коде сервера:

KnockKnockProtocol kkp = new KnockKnockProtocol();

outputLine = kkp.processInput(null);
out.println(outputLine);
receiver r=new reciver(clientSocket);
r.run(); /*it will start sending hearbeat messages to clients */

while ((inputLine = in.readLine()) != null) {
     outputLine = kkp.processInput(inputLine);
     reciver.hearbeatMessage=false; /* since you are going to send a message to client now, sending the heartbeat message is not necessary */
     out.println(outputLine);
     reciver.hearbeatMessage=true; /*start the loop again*/
     if (outputLine.equals("Bye."))
        break;

Клиентский код также будет изменен, поток будет продолжать читать сообщения из сокета, и если он не получил сообщение более 11 секунд (дополнительно 1 секунда), он объявит, что сервер недоступен.

Надеюсь, это поможет. В логике может быть и недостаток. Дай мне знать.

Ответ 3

Ниже приведены рекомендации, которые мы применяем на ежедневной основе при взаимодействии с оборудованием (с использованием сокетов).

Хорошая практика 1: SoTimeout

Это свойство позволяет таймауту чтения. Цель этого - избежать проблемы, с которой столкнулся Том. Он написал что-то в строке: "вам нужно подождать, пока не появится следующее сообщение клиента". Ну, это предлагает решение этой проблемы. И это также ключ к реализации биения и многих других проверок.

По умолчанию метод InputStream#read() будет ждать до тех пор, пока не поступит сообщение. setSoTimeout(int timeout) меняет это поведение. Он будет применять таймаут сейчас. Когда он перейдет в таймауты, он вытащит SocketTimeoutException. Просто поймайте исключение, проверьте пару вещей и продолжайте чтение (повторите). Таким образом, вы ставите свой метод чтения в цикл (и, возможно, даже в выделенном потоке).

// example: wait for 200 ms
connection.setSoTimeout(200);

Вы можете использовать эти прерывания (вызванные таймаутом) для проверки состояния:. как долго это было, так как я получил последнее сообщение.

Вот пример реализации цикла:

while (active)
{
  try
  {
    // some function that parses the message
    // this method uses the InputStream#read() method internally.
    code = readData();

    if (code == null) continue; 
    lastRead = System.currentTimeMillis();

    // the heartbeat message itself should be ignored, has no functional meaning.
    if (MSG_HEARTBEAT.equals(code)) continue;

    //TODO FORWARD MESSAGE TO ACTION LISTENERS

  }
  catch (SocketTimeoutException ste)
  {
    // in a typical situation the soTimeout should be about 200ms
    // the heartbeat interval is usually a couple of seconds.
    // and the heartbeat timeout interval a couple of seconds more.
    if ((heartbeatTimeoutInterval > 0) &&
        ((System.currentTimeMillis() - lastRead) > heartbeatTimeoutInterval))
    {
      // no reply to heartbeat received.
      // end the loop and perform a reconnect.
      break;
    }
    // simple read timeout
  }
}

Другое использование этого таймаута: его можно использовать для чистой остановки сеанса, установив active = false. Используйте таймаут, чтобы проверить, включено ли это поле true. Если это случай, то break цикл. Без логики SoTimeout это было бы невозможно. Вы либо должны были бы сделать socket.close(), либо ждать следующего сообщения клиента (что явно не имеет смысла).

Хорошая практика 2: Встроенный Keep-Alive

connection.setKeepAlive(true);

Ну, в основном, это в значительной степени то, что делает ваша сердечная логика. Он автоматически отправляет сигнал после периода бездействия и проверяет ответ. Интервал поддержания активности зависит от операционной системы, хотя и имеет некоторые недостатки.

Хорошая практика 3: Tcp No-Delay

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

try
{
  connection.setTcpNoDelay(true);
}
catch (SocketException e)
{
}

Ответ 4

Я думаю, что ты слишком усложняешь вещи.

Со стороны клиента:
Если клиент получает IOException для подключения reset, значит, сервер мертв. Вместо того, чтобы печатать трассировку стека, просто делайте то, что вам нужно сделать, когда вы знаете, что сервер не работает. Вы уже знаете, что сервер отключен из-за исключения.

Со стороны сервера:
Либо запустите таймер, и если вы не получите запрос на какое-то время больше, чем интервал, предположите, что клиент не работает.
ИЛИ запустите фоновый серверный поток на клиенте (создайте узлы клиента и сервера), и сервер отправит запрос "dummy" слышимого звонка (сервер теперь действует как клиент). Если вы получаете исключение, клиент не работает.

Ответ 5

Здесь небольшое изменение для клиента. Он не использует явное сердцебиение, но до тех пор, пока вы продолжаете читать с сервера, вы все равно сразу обнаружите отключение.

Это потому, что readLine сразу обнаруживает ошибки чтения.

// I'm using an anonymous class here, so we need 
// to have the reader final.
final BufferedReader reader = in;

// Decouple reads from user input using a separate thread:
new Thread()
{
   public void run()
   {
      try
      {
         String fromServer;
         while ((fromServer = reader.readLine()) != null)
         {
            System.out.println("Server: " + fromServer);
            if (fromServer.equals("Bye."))
            {
                System.exit(0);
            }
         }
      }
      catch (IOException e) {}

      // When we get an exception or readLine returns null, 
      // that will be because the server disconnected or 
      // because we did. The line-break makes output look better if we 
      // were in the middle of writing something.
      System.out.println("\nServer disconnected.");
      System.exit(0);
   }
}.start();

// Now we can just read from user input and send to server independently:
while (true)
{
   String fromUser = stdIn.readLine();
   if (fromUser != null)
   {
      System.out.println("Client: " + fromUser);
      out.println(fromUser);
   }
}

В этом случае мы разрешаем запись клиентом, даже если мы ожидаем ответа с сервера. Для более стабильного приложения мы хотим заблокировать ввод, пока мы ждем ответа, добавив контроль семафора, когда мы начнем читать.

Это изменения, которые мы будем делать для управления вводом:

final BufferedReader reader = in;

// Set up a shared semaphore to control client input.
final Semaphore semaphore = new Semaphore(1);

// Remove the first permit.
semaphore.acquireUninterruptibly();

new Thread()

... code omitted ...

           System.out.println("Server: " + fromServer);
           // Release the current permit.
           semaphore.release();
           if (fromServer.equals("Bye."))

... code omitted ...

while (true)
{
    semaphore.acquireUninterruptibly();
    String fromUser = stdIn.readLine();

... rest of the code as in the original ...

Ответ 6

Понял, что я взломал это... Я начал с KnockKnockServer и KnockKnockClient, которые находятся на сайте Java (в исходном вопросе).

Я не добавлял ни одной нити или сердечных сокращений; Я просто изменил KnockKnockClient на следующее:

    try {    // added try-catch-finally block
      while ((fromServer = in.readLine()) != null) {
        System.out.println("Server: " + fromServer);
        if (fromServer.equals("Bye."))
          break;

        fromUser = stdIn.readLine();
        if (fromUser != null) {
          System.out.println("Client: " + fromUser);
          out.println(fromUser);
        }
      }
    } catch (java.net.SocketException e) {   // catch java.net.SocketException
      // print the message you were looking for
      System.out.println("The system has detected that KnockKnockServer was aborted");
    } finally {
      // this code will be executed if a different exception is thrown,
      // or if everything goes as planned (ensure no resource leaks)
      out.close();
      in.close();
      stdIn.close();
      kkSocket.close();
    }

Это похоже на то, что вы хотите (хотя я модифицировал оригинальный пример веб-сайта Java, а не ваш код, надеюсь, вы сможете увидеть, где он подключается). Я протестировал его с описанным вами случаем (выключите сервер, пока клиент подключен).

Недостатком этого является то, что, пока клиент ждет ввода пользователя, вы не видите, что сервер умер; вам нужно ввести клиентский ввод, а затем вы увидите, что сервер умер. Если это не то поведение, которое вы хотите, пожалуйста, напишите комментарий (возможно, это был весь смысл вопроса - казалось, что вы, возможно, шли по более длинной дороге, чем вам нужно, чтобы добраться туда, где вы хотели быть).

Ответ 7

Я думаю, что ответ на @Bala правильный на стороне сервера. Я хотел бы предоставить дополнительную информацию на стороне клиента.

На стороне клиента вы должны:

  • использовать переменную, чтобы сохранить метку последнего сообщения с сервера;
  • запустите поток, который периодически запускается (каждые 1 секунду, например), чтобы сравнить текущую временную метку и временную метку последнего сообщения, если она длиннее желаемого таймаута (10 секунд, например), следует сообщить об отключении.

Ниже приведен фрагмент кода:

Класс TimeoutChecker (поток):

static class TimeoutChecker implements Runnable {

    // timeout is set to 10 seconds
    final long    timeout = TimeUnit.SECONDS.toMillis(10);
    // note the use of volatile to make sure the update to this variable thread-safe
    volatile long lastMessageTimestamp;

    public TimeoutChecker(long ts) {
        this.lastMessageTimestamp = ts;
    }

    @Override
    public void run() {
        if ((System.currentTimeMillis() - lastMessageTimestamp) > timeout) {
            System.out.println("timeout!");
        }
    }
}

Запустите TimeoutChecker после установления соединения:

try {
  kkSocket = new Socket("localhost", 4444);
  // create TimeoutChecker with current timestamp.
  TimeoutChecker checker = new TimeoutChecker(System.currentTimeMillis());
  // schedule the task to run on every 1 second.
  ses.scheduleAtFixedRate(, 1, 1,
            TimeUnit.SECONDS);
  out = new PrintWriter(kkSocket.getOutputStream(), true);
  in = new BufferedReader(new InputStreamReader(kkSocket.getInputStream()));
} catch( UnknownHostException uhe ){ /*...more error catching */

ses - это ScheduledExecutorService:

ScheduledExecutorService ses = Executors.newScheduledThreadPool(1);

И не забудьте обновить метку времени при получении сообщений с сервера:

BufferedReader stdIn = new BufferedReader(new InputStreamReader(System.in));
String fromServer;
String fromUser;

while ((fromServer = in.readLine()) != null) {
  // update the message timestamp
  checker.lastMessageTimestamp = System.currentTimeMillis();
  System.out.println("Server: " + fromServer);
  if (fromServer.equals("bye."))
    break;

Ответ 8

Адель, смотрел ваш код http://pastebin.com/53vYaECK

Можете ли вы попробовать следующее решение. не уверен, будет ли это работать. вместо создания буферизованного считывателя с входным потоком один раз, мы можем создавать экземпляр BufferedReader каждый раз. когда kkSocket.getInputStream имеет значение null, он выходит из цикла while и устанавливает completeLoop в false, так что мы выходим из цикла while. он имеет 2 цикла, а объекты создаются каждый раз. если соединение открыто, но в нем нет данных, входной поток не будет нулевым, BufferedReader.readLine будет null.

bool completeLoop=true;
while(completeLoop) {

while((inputstream is=kkSocket.getInputStream())!=null) /*if this is null it means the socket is closed*/
{  
BufferedReader in = new BufferedReader( new InputStreamReader(is));
while ((fromServer = in.readLine()) != null) {
            System.out.println("Server: " + fromServer);
            if (fromServer.equals("Bye."))
                break;
            fromUser = stdIn.readLine();
        if (fromUser != null) {
                System.out.println("Client: " + fromUser);
                out.println(fromUser);
           }
        } 
}
completeLoop=false;
System.out.println('The connection is closed');
}