33

次の基本的な Java ソケット コード ( source ) を勉強しています。これは、ノック ノック ジョーク クライアント/サーバー アプリです。

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

サーバーには、ジョークのオチを取得するための対応するコードがあります。

    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;

全体にハートビートを付けたいと思います。これは、反対側が死んでいることを検出するたびにコンソールに出力されます。反対側を殺した場合に今何が起こるかは例外です-以下のように:

ここに画像の説明を入力

したがって、KnockKnockClient と KnockKnockServer の両方を実行している場合、KnockKnockServer をシャットダウンすると、クライアントで次の出力が表示されます。

>The system has detected that KnockKnockServer was aborted

何かヒントを探しています。これまでのところ、主に反対側への新しい接続を定期的に作成するデーモン スレッドを実行しようとしてきました。しかし、チェックする条件について混乱しています(しかし、それは単なる値だと思いbooleanますか?)。それは正しいアプローチですか?オンラインで、マルチキャストネットワーキング用の JGroupsというライブラリがあることを知りました。何かヒントを探しています。

これまでの私のサーバーコード(乱雑で申し訳ありません)

&

クライアント側

ありがとう

4

8 に答える 8

16

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

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

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

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

于 2013-04-06T15:20:47.797 に答える
12

同期通信があります。ハートビート メッセージを取得するには、非同期通信を使用します。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 秒余分に) 受信しない場合、サーバーが利用できないと宣言します。

お役に立てれば。ロジックにも欠陥がある可能性があります。お知らせ下さい。

于 2013-03-25T04:46:39.437 に答える
10

以下は、(ソケットを使用して) ハードウェアと接続するときに日常的に適用するベスト プラクティスです。

グッド プラクティス 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これは不可能です。a を実行するsocket.close()か、次のクライアント メッセージを待つ必要があります (これは明らかに意味がありません)。

グッド プラクティス 2 : 組み込みの Keep-Alive

connection.setKeepAlive(true);

基本的に、これはハートビート ロジックが行うこととほとんど同じです。非アクティブな状態が一定時間続くと、自動的にシグナルを送信し、応答をチェックします。ただし、キープアライブ間隔はオペレーティング システムに依存しており、いくつかの欠点があります。

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

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

try
{
  connection.setTcpNoDelay(true);
}
catch (SocketException e)
{
}
于 2013-08-11T17:26:30.677 に答える
4

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

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

サーバー側から:
タイマーを開始し、間隔を超えて要求を受信しない場合は、クライアントがダウンしていると想定します。
または、クライアントでバックグラウンド サーバー スレッドを開始し (クライアントとサーバーをピアにする)、サーバーに "ダミー" ハートビート要求を送信させます (サーバーはクライアントとして動作します)。例外が発生した場合、クライアントはダウンしています。

于 2013-04-19T17:48:43.603 に答える
2

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

これは、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 ...
于 2013-04-03T08:36:57.977 に答える
1

@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;
于 2013-04-04T14:26:56.733 に答える