web-dev-qa-db-ja.com

この上にハートビートメッセージングを追加する方法Java code(for KnockKnockClient / Server)?

私は次の基本的なJavaソケットコード( source )を勉強しています。これは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 for multicast ネットワーキングと呼ばれるライブラリがあることをオンラインで知りました-それはより良い方法でしょうか?ヒントを探しています。

これまでのところ私のサーバーコード (ごちゃごちゃしてごめんなさい)

クライアント側

ありがとう

31
Coffee

しかし、あなたが得ている例外はまさにこれです!それはあなたの反対側がちょうど死んだことを伝えています。例外をキャッチし、コンソールに出力します。「KnockKnockServerが中止されたことをシステムが検出しました」。

TCP接続を使用しており、TCPにはこれを実行する組み込みのハートビート(キープアライブ)メカニズムがあります。ソケットにsetKeepAlive()を設定するだけです。つまり、接続ごとにキープアライブの頻度を制御することは可能ですが、Javaでそれを行う方法がわかりません。

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

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

15
mabn

同期通信があります。ハートビートメッセージを取得するには、非同期通信を使用します。 2つのスレッドがあります。 1つはソケットから読み取り、もう1つはソケットへの書き込みを続けます。非同期通信を使用する場合、サーバーは10秒ごとにメッセージを送信します。クライアントスレッドはサーバーからメッセージを読み取ります。メッセージがない場合は、サーバーがダウンしています。あなたの場合、サーバーはメッセージをクライアントに返信するか(クライアントにメッセージがある場合)、自動返信を送信します。サーバーコードは次のように変更できます。

  1. 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秒余分)を超えてメッセージを受信しなかった場合、サーバーが利用できないことを宣言します。

お役に立てれば。ロジックにもいくつかの欠陥があるかもしれません。お知らせ下さい。

12
Bala

以下は、ハードウェアとのインターフェース(ソケットを使用)の際に日常的に適用するベストプラクティスです。

グッドプラクティス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:組み込みのキープアライブ

_connection.setKeepAlive(true);
_

基本的に、これはハートビートロジックが行うこととほぼ同じです。非アクティブな期間が経過すると自動的に信号を送信し、応答を確認します。ただし、キープアライブ間隔はオペレーティングシステムに依存し、いくつかの欠点があります。

グッドプラクティス3:Tcp No-Delay

迅速に処理する必要がある小さなコマンドを頻繁にインターフェイスする場合は、次の設定を使用します。

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

あなたは物事を複雑にしています。

クライアント側から:
クライアントが接続リセットのためにIOExceptionを取得した場合、これはサーバーが停止していることを意味します。スタックトレースを出力する代わりに、サーバーがダウンしていることがわかったら、必要な処理を実行してください。例外のためにサーバーがダウンしていることはすでに知っています。

サーバー側から:
タイマーを開始し、間隔よりも長い時間リクエストが得られない場合は、クライアントがダウンしていると想定します。
またはクライアントでバックグラウンドサーバースレッドを開始し(クライアントとサーバーをピアにする)、サーバーに「ダミー」のハートビート要求を送信させます(サーバーはクライアントとして機能します)。例外が発生した場合、クライアントはダウンしています。

3
Cratylus

これを試してみました...(元の質問の)Javaサイトにある KnockKnockServerKnockKnockClient から始めました。

スレッディングやハートビートは追加しませんでした。私は単に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 Webサイトの例を変更しましたが-うまくいけば、それが接続されている場所を確認できるでしょう)。私はあなたが説明したケースでそれをテストしました(クライアントが接続している間はサーバーをシャットダウンしてください)。

これの欠点は、クライアントがユーザー入力を待っている間、サーバーが停止したことがわかりません。クライアント入力を入力する必要があり、その後サーバーが停止したことがわかります。これが希望する動作でない場合は、コメントを投稿してください(おそらくそれが質問の要点でした-目的の場所に到達するために必要以上に長い道のりを進んでいたようです) )。

2
Tom

これがクライアントのわずかな変更です。明示的なハートビートは使用しませんが、サーバーから読み取りを続けている限り、とにかくすぐに切断を検出します。

これは、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 ...
2
Nuoji

アデル、あなたのコードを見ていた http://Pastebin.com/53vYaECK

次の解決策を試してください。それが機能するかどうかわからない。 inputstreamでbufferedreaderを一度作成する代わりに、毎回BufferedReaderのインスタンスを作成できます。 kkSocket.getInputStreamがnullの場合、whileループから抜け出し、completeLoopをfalseに設定して、whileループを終了します。 2つのwhileループがあり、オブジェクトは毎回作成されます。接続が開いているがデータが含まれていない場合、inputstreamはnullにならず、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');
}
1
Bala

@Balaの答えはサーバー側では正しいと思います。クライアント側で補足をお願いします。

クライアント側では、次のことを行う必要があります。

  1. 変数を使用して、サーバーからの最後のメッセージのタイムスタンプを保持します。
  2. 現在のタイムスタンプと最後のメッセージのタイムスタンプを比較するために定期的に(たとえば、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 */

sesScheduledExecutorServiceです:

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