web-dev-qa-db-fra.com

Comment ajouter des messages de pulsation au-dessus de ce code Java (pour KnockKnockClient/Server)?

J'étudie le code de socket Java de base suivant ( source ). C'est une application client/serveur Knock-Knock-Joke. 

Dans Client , nous configurons le socket comme d'habitude:

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

Et plus tard, nous lisons et écrivons sur le serveur:

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

Et sur le serveur , nous avons le code correspondant pour obtenir la ligne de frappe.

    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;

Je veux attacher un battement de coeur à la chose entière, qui sera imprimée sur la console chaque fois qu'il détecte que l'autre côté est mort. Parce que ce qui se passe maintenant si je tue l'autre côté est une exception - comme celle ci-dessous:

enter image description here

Donc, si j'exécute KnockKnockClient et KnockKnockServer, j'arrête KnockKnockServer. Ce qui devrait arriver, c'est que sur le client, je vois les résultats suivants:

>The system has detected that KnockKnockServer was aborted

Je cherche des conseils. Jusqu'à présent, j'ai principalement essayé de lancer un thread de démon qui crée périodiquement de nouvelles connexions avec l'autre côté. Mais je ne comprends pas quelle condition vérifier (mais je pense que c'est juste une valeur boolean?). Est-ce la bonne approche? Je viens de découvrir en ligne une bibliothèque appelée JGroups pour la mise en réseau multidiffusion - serait-ce une meilleure solution? Je cherche des conseils.

Mon code serveur jusqu'à présent (désolé, c'est compliqué)

Et

Côté client

merci

30
Coffee

Mais l'exception que vous obtenez est exactement cela! Cela vous dit que l'autre côté vient de mourir. Attrapez simplement l'exception et imprimez sur la console, que "le système a détecté que KnockKnockServer a été abandonné".

Vous utilisez la connexion TCP et TCP a un mécanisme harthbeat (keepalive) intégré qui le fait pour vous. Il suffit de définir setKeepAlive () sur le socket. Cela dit, il est possible de contrôler la fréquence de keepalive pour chaque connexion, mais je ne sais pas comment le faire en Java.

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

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

15
mabn

vous avez une communication synchrone. Pour avoir le message de pulsation, utilisez une communication asynchrone. il y aura 2 discussions. l'un lira sur le socket et un autre continuera à écrire sur le socket. Si vous utilisez une communication asynchrone, le serveur enverra un message toutes les 10 secondes. le thread client lira les messages du serveur et s’il n’y en a pas, cela signifie que le serveur est en panne. dans votre cas, le serveur renvoie le message au client (si le client en a un) ou envoie une réponse automatique. Votre code de serveur peut être modifié comme ceci. 

  1. Créez un thread de serveur qui enverra des messages au client toutes les 10 secondes. 

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

Dans votre code serveur:

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;

Le code client sera également modifié, un thread continuera à lire les messages du socket et s'il n'a pas reçu de message pendant plus de 11 secondes (1 seconde de plus), il déclarera que le serveur n'est pas disponible. 

J'espère que cela t'aides. Il pourrait aussi y avoir une faille dans la logique. Faites le moi savoir. 

13
Bala

Voici les meilleures pratiques que nous appliquons quotidiennement lors de l’interfaçage avec du matériel (à l’aide de sockets).

Bonne pratique 1: SoTimeout

Cette propriété active le délai de lecture. Le but de ceci est d'éviter le problème que Tom avait. Il a écrit quelque chose dans la ligne: "vous devrez attendre que le prochain message du client arrive". Eh bien, cela offre une solution à ce problème. Et c’est aussi la clé pour mettre en œuvre une pulsation et de nombreux autres contrôles .

Par défaut, la méthode InputStream#read() attend pour toujours, jusqu'à ce qu'un message arrive. La setSoTimeout(int timeout) change ce comportement. Il va appliquer un délai d'attente maintenant. Quand il arrive à expiration, il lancera la SocketTimeoutException. Juste attraper l'exception, vérifier quelques choses et continuer à lire (répéter). Donc, fondamentalement, vous mettez votre méthode de lecture dans une boucle (et probablement même dans un thread dédié).

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

Vous pouvez utiliser ces interruptions (causées par le délai) pour valider le statut: E.g. Depuis combien de temps ai-je reçu mon dernier message?.

Voici un exemple pour implémenter la boucle:

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

Une autre utilisation de cette temporisation: Il peut être utilisé pour arrêter proprement votre session en définissant active = false. Utilisez le délai pour vérifier si ce champ est true. Si c'est le cas, alors break la boucle. Sans la logique SoTimeout, cela ne serait pas possible. Vous seriez obligé de faire un socket.close() ou d'attendre le prochain message du client (ce qui n'a aucun sens).

Bonne pratique 2: Keep-Alive intégré

connection.setKeepAlive(true);

En gros, c’est à peu près ce que fait votre logique du cœur. Il envoie automatiquement un signal après une période d'inactivité et recherche une réponse. L’intervalle de maintien en vie dépend toutefois du système d’exploitation et présente quelques inconvénients.

Bonne pratique 3: Tcp No-Delay

Utilisez le paramètre suivant lorsque vous vous connectez souvent à de petites commandes devant être traitées rapidement.

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

Je pense que vous compliquez les choses. 

Du côté client:
Si le client obtient une IOException pour la réinitialisation de la connexion, cela signifie que le serveur est mort. Au lieu d’imprimer la trace de pile, faites ce que vous avez à faire une fois que vous savez que le serveur est en panne. Vous savez déjà que le serveur est en panne à cause de l'exception. 

Du côté serveur:
Commencez par une minuterie et si vous ne recevez pas de demande plus longtemps que l’intervalle, supposez que le client est en panne.
OU démarrez un thread de serveur d’arrière-plan sur le client (en faisant des homologues client et serveur) et demandez au serveur d’envoyer une demande de répétition "factice" (le serveur agit maintenant comme un client). Si vous obtenez une exception, le client est en panne.

3
Cratylus

Je pensais comprendre cela ... J'ai commencé avec le KnockKnockServer et KnockKnockClient qui se trouvent sur le site Java (dans votre question initiale).

Je n'ai ajouté aucun fil, ni pulsation cardiaque; J'ai simplement changé le KnockKnockClient comme suit:

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

Cela semble faire ce que vous voulez (même si j'ai modifié l'exemple du site Web Java original, plutôt que votre code - j'espère que vous pourrez voir où il se connecte). Je l'ai testé avec le cas que vous avez décrit (arrêtez le serveur lorsque le client est connecté).

L'inconvénient à ceci est que, pendant que le client attend l'entrée de l'utilisateur, vous ne voyez pas que le serveur est mort; vous devez entrer une entrée client et puis vous verrez que le serveur est mort. Si ce n'est pas le comportement que vous souhaitez, merci de poster un commentaire (peut-être était-ce là le but de la question - il me semblait que vous aviez peut-être emprunté un chemin plus long que nécessaire pour arriver là où vous vouliez être ).

2
Tom

Voici une légère modification au client. Il n'utilise pas de pulsation explicite, mais, tant que vous continuez à lire sur le serveur, vous détectez immédiatement la déconnexion.

En effet, readLine détectera immédiatement toutes les erreurs de lecture.

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

Dans ce cas, nous autorisons les écritures client même lorsque nous attendons une réponse du serveur. Pour une application plus stable, nous souhaitons verrouiller l'entrée pendant l'attente d'une réponse en ajoutant un sémaphore contrôlant le début de la lecture.

Ce sont les modifications que nous ferions pour contrôler l'entrée:

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, regardait votre code http://Pastebin.com/53vYaECK

Pouvez-vous essayer la solution suivante? Je ne sais pas si cela fonctionnera. Au lieu de créer un bufferedreader avec le flux d'entrée une fois, nous pouvons créer une instance de BufferedReader à chaque fois. lorsque kkSocket.getInputStream a la valeur null, il sort de la boucle while et attribue la valeur false à completeLoop afin de quitter la boucle while il a 2 boucles while et les objets sont créés à chaque fois. si la connexion est ouverte mais ne contient pas de données, le flux d'entrée ne sera pas nul, BufferedReader.readLine serait nul.

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

Je pense que la réponse de @ Bala est correcte côté serveur. Je voudrais faire une question supplémentaire côté client.

Du côté du client, vous devriez: 

  1. utilisez une variable pour conserver l'horodatage du dernier message du serveur;
  2. démarrez un thread qui s'exécute périodiquement (par exemple, toutes les secondes) pour comparer l'horodatage actuel et l'horodatage du dernier message, s'il est plus long que le délai d'expiration souhaité (10 secondes, par exemple), une déconnexion doit être signalée.

Voici quelques extraits de code:

La classe TimeoutChecker (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!");
        }
    }
}

Démarrez la TimeoutChecker après l’établissement de la connexion:

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

La ses est une ScheduledExecutorService:

ScheduledExecutorService ses = Executors.newScheduledThreadPool(1);

Et n'oubliez pas de mettre à jour l'horodatage lors de la réception des messages du serveur:

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