it-swarm.com.de

Wie fügt man Heartbeat-Messaging zu diesem Java-Code hinzu (für KnockKnockClient/Server)?

Ich studiere den folgenden grundlegenden Java-Socket-Code ( source ). Es ist eine Knock-Knock-Joke-Client/Server-App. 

Im Client richten wir den Socket wie gewohnt ein:

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 */

Und später lesen und schreiben wir einfach auf Server:

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);
  }

Und auf dem Server haben wir den entsprechenden Code, um die Scherzlinie zu erhalten.

    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;

Ich möchte einen Herzschlag an das Ganze anfügen, der auf der Konsole ausgedruckt wird, sobald er erkennt, dass die andere Seite gestorben ist. Denn was jetzt passiert, wenn ich die andere Seite töte, ist eine Ausnahme - wie die folgende:

enter image description here

Wenn ich also sowohl KnockKnockClient als auch KnockKnockServer betreibe, fahre ich KnockKnockServer herunter. Auf dem Client sehe ich folgende Ausgabe:

>The system has detected that KnockKnockServer was aborted

Ich suche nach Tipps. Bisher habe ich hauptsächlich versucht, einen Daemon-Thread auszuführen, der periodisch neue Verbindungen zur anderen Seite herstellt. Aber ich bin verwirrt, welche Bedingung ich prüfen sollte (aber ich denke, es ist nur ein boolean-Wert?). Ist das der richtige Ansatz? Ich habe gerade online herausgefunden, dass es eine Bibliothek mit dem Namen JGroups für Multicast Networking gibt - wäre das ein besserer Weg? Ich suche nach Tipps.

Mein Server-Code bisher (sorry, es ist chaotisch)

&

Client-Seite

vielen Dank

30
Coffee

Aber die Ausnahme, die Sie bekommen, ist genau das! Es sagt Ihnen, dass die andere Seite gerade gestorben ist. Fangen Sie einfach die Ausnahme und drucken Sie auf der Konsole, dass "das System festgestellt hat, dass KnockKnockServer abgebrochen wurde".

Sie verwenden die Verbindung TCP und TCP verfügt über einen integrierten Harthbeat (Keepalive) -Mechanismus, der dies für Sie erledigt. Setzen Sie einfach setKeepAlive () auf den Sockel. Davon abgesehen - Es ist möglich, die Keepalive-Frequenz für jede Verbindung zu steuern, aber ich weiß nicht, wie das in Java gemacht wird.

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

https://stackoverflow.com/a/1480259/706650

15
mabn

sie haben eine synchrone Kommunikation. Verwenden Sie für die Heartbeat-Nachricht eine asynchrone Kommunikation. Es wird 2 Threads geben. einer liest den Sockel und ein anderer schreibt weiter in den Sockel. Wenn Sie die asynchrone Kommunikation verwenden, sendet der Server alle 10 Sekunden eine Nachricht. Der Client-Thread liest Nachrichten vom Server. Wenn keine Nachricht vorliegt, bedeutet dies, dass der Server inaktiv ist. In Ihrem Fall sendet der Server die Nachricht entweder an den Client zurück (wenn der Client eine Nachricht hat) oder sendet eine automatische Antwort. Ihr Servercode kann auf diese Weise geändert werden. 

  1. Erstellen Sie einen Server-Thread, der alle 10 Sekunden Nachrichten an den Client sendet. 

    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");
    
          }
        }            
      }
    }
    

In Ihrem Servercode:

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;

Der Client-Code wird ebenfalls geändert. Ein Thread liest weiterhin Nachrichten aus dem Socket. Wenn er keine Nachricht mehr als 11 Sekunden (1 Sekunde) erhalten hat, wird der Server als nicht verfügbar angezeigt. 

Hoffe das hilft. Es könnte auch ein Fehler in der Logik sein. Gib mir Bescheid. 

13
Bala

Im Folgenden finden Sie Best Practices, die wir täglich anwenden, wenn Sie eine Schnittstelle zu Hardware herstellen (mithilfe von Sockets).

Gute Praxis 1: SoTimeout

Diese Eigenschaft ermöglicht ein Lese-Timeout. Ziel ist es, das Problem zu vermeiden, das Tom hatte. Er schrieb etwas in der Zeile: "Sie müssen warten, bis die nächste Client-Nachricht eintrifft". Nun, das bietet eine Lösung für dieses Problem. Und es ist auch der Schlüssel zum Implementieren eines Heartbeats und vieler anderer Überprüfungen .

Die InputStream#read()-Methode wartet standardmäßig immer, bis eine Nachricht eingeht. Die setSoTimeout(int timeout) ändert dieses Verhalten. Es wird jetzt ein Timeout angewendet. Bei Zeitüberschreitungen wird der SocketTimeoutException ausgegeben. Fangen Sie einfach die Ausnahme ab, prüfen Sie ein paar Dinge und lesen Sie weiter (wiederholen Sie). Im Grunde nehmen Sie Ihre Lesemethode in eine Schleife (und wahrscheinlich sogar in einen dedizierten Thread).

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

Sie können diese Unterbrechungen (verursacht durch das Zeitlimit) verwenden, um den Status zu überprüfen: E.g. wie lange ist es her, seit ich meine letzte nachricht erhalten habe.

Hier ist ein Beispiel zum Implementieren der Schleife:

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
  }
}

Eine andere Verwendung dieser Zeitüberschreitung: Sie kann verwendet werden, um Ihre Sitzung sauber zu beenden, indem Sie active = false einstellen. Verwenden Sie das Zeitlimit, um zu überprüfen, ob dieses Feld true ist. Wenn dies der Fall ist, dann break die Schleife. Ohne die SoTimeout-Logik wäre dies nicht möglich. Sie wären entweder gezwungen, eine socket.close() durchzuführen oder auf die nächste Client-Nachricht zu warten (was eindeutig keinen Sinn ergibt).

Gute Praxis 2: Integriertes Keep-Alive

connection.setKeepAlive(true);

Nun, im Grunde ist das so ziemlich das, was deine Herzschlag-Logik tut. Er sendet nach einer Inaktivitätsphase automatisch ein Signal und prüft, ob eine Antwort vorliegt. Das Keep-Alive-Intervall ist jedoch vom Betriebssystem abhängig und hat einige Nachteile.

Gute Praxis 3: Tcp No-Delay

Verwenden Sie die folgende Einstellung, wenn Sie häufig kleine Befehle verwenden, die schnell gehandhabt werden müssen.

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

Ich denke, Sie machen die Dinge zu kompliziert. 

Vom Client:
Wenn der Client eine IOException für das Zurücksetzen der Verbindung erhält, bedeutet dies, dass der Server tot ist. Anstatt den Stack-Trace zu drucken, machen Sie einfach das, was Sie tun müssen, wenn Sie wissen, dass der Server inaktiv ist. Sie wissen bereits, dass der Server aufgrund einer Ausnahme inaktiv ist. 

Von der Serverseite:
Starten Sie entweder einen Timer, und wenn Sie länger als eine bestimmte Zeit keine Anforderung erhalten, gehen Sie davon aus, dass der Client ausgefallen ist.
ODER einen Hintergrund-Server-Thread am Client starten (Client- und Server-Peers) und den Server eine "Dummy" -Anforderung senden (der Server fungiert jetzt als Client). Wenn Sie eine Ausnahme erhalten, ist der Client inaktiv.

3
Cratylus

Ich dachte, ich würde einen Horror nehmen ... Ich begann mit dem KnockKnockServer und KnockKnockClient , die sich auf der Java-Site befinden (in Ihrer ursprünglichen Frage).

Ich habe kein Einfädeln oder Herzschläge hinzugefügt; Ich habe den KnockKnockClient einfach wie folgt geändert:

    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();
    }

Dies scheint zu tun, was Sie wollen (obwohl ich das ursprüngliche Java-Website-Beispiel und nicht Ihren Code modifiziert habe - hoffentlich können Sie sehen, wo es eingefügt wird). Ich habe es mit dem von Ihnen beschriebenen Fall getestet (den Server heruntergefahren, während der Client verbunden ist).

Der Nachteil dabei ist, dass der Client während des Wartens auf Benutzereingaben nicht feststellt, dass der Server gestorben ist. Sie müssen die Client-Eingabe eingeben und dann sehen Sie, dass der Server gestorben ist. Wenn dies nicht das gewünschte Verhalten ist, schreiben Sie bitte einen Kommentar (vielleicht war dies der Kernpunkt der Frage) - es schien, als ob Sie einen längeren Weg gegangen wären, als Sie brauchten, um dorthin zu gelangen, wo Sie sein wollten ).

2
Tom

Hier ist eine geringfügige Änderung am Client. Es wird kein expliziter Heartbeat verwendet, aber solange Sie vom Server lesen, werden Sie die Unterbrechung der Verbindung trotzdem sofort erkennen.

Dies liegt daran, dass readLine Lesefehler sofort erkennt.

// 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);
   }
}

In diesem Fall erlauben wir das Schreiben von Clients, selbst wenn wir auf eine Antwort vom Server warten. Für eine stabilere Anwendung möchten wir die Eingabe sperren, während wir auf eine Antwort warten, indem wir beim Lesen eine Semaphor-Steuerung hinzufügen.

Dies sind die Änderungen, die wir vornehmen würden, um die Eingabe zu steuern:

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 ...
2
Nuoji

Adel, hat sich Ihren Code angesehen http://Pastebin.com/53vYaECK

Kannst du die folgende Lösung ausprobieren? nicht sicher, ob es funktionieren wird. Anstatt einmal einen gepufferten Leser mit dem Eingabestrom zu erstellen, können jedes Mal eine Instanz von BufferedReader erstellt werden. Wenn kkSocket.getInputStream den Wert null hat, kommt es aus der while-Schleife und setzt completeLoop auf false, sodass die while-Schleife beendet wird. Es hat 2 while-Schleifen und die Objekte werden jedes Mal erstellt. Wenn die Verbindung geöffnet ist, aber keine Daten enthalten sind, wird der Eingabestrom nicht null sein.... BufferedReader.readLine wäre 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');
}
1
Bala

Ich denke, @ Balas Antwort ist serverseitig korrekt. Ich möchte auf Kundenseite eine Ergänzung geben.

Auf Kundenseite sollten Sie: 

  1. verwenden Sie eine Variable, um den Zeitstempel der letzten Nachricht vom Server aufzubewahren.
  2. starten Sie einen Thread, der in regelmäßigen Abständen (z. B. alle 1 Sekunde) ausgeführt wird, um den aktuellen Zeitstempel und den letzten Nachrichtenzeitstempel zu vergleichen. Wenn er länger als das gewünschte Zeitlimit (10 Sekunden) ist, sollte eine Unterbrechung gemeldet werden.

Nachfolgend einige Codeausschnitte:

Die TimeoutChecker-Klasse (Thread):

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!");
        }
    }
}

Starten Sie TimeoutChecker, nachdem die Verbindung hergestellt wurde:

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 */

Die ses ist eine ScheduledExecutorService:

ScheduledExecutorService ses = Executors.newScheduledThreadPool(1);

Denken Sie daran, den Zeitstempel zu aktualisieren, wenn Sie Nachrichten vom Server erhalten:

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;
1
ericson