私は次の基本的な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;
ハートビートを全体に接続したいのですが、反対側が死んだことを検出するとコンソールに出力されます。私が反対側を殺した場合、今何が起こるかは例外です-以下のように:
したがって、KnockKnockClientとKnockKnockServerの両方を実行している場合、KnockKnockServerをシャットダウンすると、クライアントで次のように出力されます。
>The system has detected that KnockKnockServer was aborted
ヒントを探しています。これまで私は主に、定期的に反対側への新しい接続を作成するデーモンスレッドを実行しようとしてきました。しかし、チェックする条件について混乱しています(しかし、それは単なるboolean
値だと思いますか?)。それは正しいアプローチですか?私はオンラインで JGroups for multicast ネットワーキングと呼ばれるライブラリがあることをオンラインで知りました-それはより良い方法でしょうか?ヒントを探しています。
これまでのところ私のサーバーコード (ごちゃごちゃしてごめんなさい)
&
ありがとう
しかし、あなたが得ている例外はまさにこれです!それはあなたの反対側がちょうど死んだことを伝えています。例外をキャッチし、コンソールに出力します。「KnockKnockServerが中止されたことをシステムが検出しました」。
TCP接続を使用しており、TCPにはこれを実行する組み込みのハートビート(キープアライブ)メカニズムがあります。ソケットにsetKeepAlive()を設定するだけです。つまり、接続ごとにキープアライブの頻度を制御することは可能ですが、Javaでそれを行う方法がわかりません。
同期通信があります。ハートビートメッセージを取得するには、非同期通信を使用します。 2つのスレッドがあります。 1つはソケットから読み取り、もう1つはソケットへの書き込みを続けます。非同期通信を使用する場合、サーバーは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秒余分)を超えてメッセージを受信しなかった場合、サーバーが利用できないことを宣言します。
お役に立てれば。ロジックにもいくつかの欠陥があるかもしれません。お知らせ下さい。
以下は、ハードウェアとのインターフェース(ソケットを使用)の際に日常的に適用するベストプラクティスです。
このプロパティは、読み取りタイムアウトを有効にします。この目標は、トムが抱えていた問題を回避することです。彼は「次のクライアントメッセージが到着するまで待つ必要がある」という行に何かを書いた。まあ、これはその問題の解決策を提供します。そして、それもハートビートと他の多くのチェックを実装するための鍵です。
デフォルトでは、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()
を実行するか、次のクライアントメッセージを待つ必要があります(これは明らかに意味がありません)。
_connection.setKeepAlive(true);
_
基本的に、これはハートビートロジックが行うこととほぼ同じです。非アクティブな期間が経過すると自動的に信号を送信し、応答を確認します。ただし、キープアライブ間隔はオペレーティングシステムに依存し、いくつかの欠点があります。
迅速に処理する必要がある小さなコマンドを頻繁にインターフェイスする場合は、次の設定を使用します。
_try
{
connection.setTcpNoDelay(true);
}
catch (SocketException e)
{
}
_
あなたは物事を複雑にしています。
クライアント側から:
クライアントが接続リセットのためにIOException
を取得した場合、これはサーバーが停止していることを意味します。スタックトレースを出力する代わりに、サーバーがダウンしていることがわかったら、必要な処理を実行してください。例外のためにサーバーがダウンしていることはすでに知っています。
サーバー側から:
タイマーを開始し、間隔よりも長い時間リクエストが得られない場合は、クライアントがダウンしていると想定します。
またはクライアントでバックグラウンドサーバースレッドを開始し(クライアントとサーバーをピアにする)、サーバーに「ダミー」のハートビート要求を送信させます(サーバーはクライアントとして機能します)。例外が発生した場合、クライアントはダウンしています。
これを試してみました...(元の質問の)Javaサイトにある KnockKnockServer と KnockKnockClient から始めました。
スレッディングやハートビートは追加しませんでした。私は単に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サイトの例を変更しましたが-うまくいけば、それが接続されている場所を確認できるでしょう)。私はあなたが説明したケースでそれをテストしました(クライアントが接続している間はサーバーをシャットダウンしてください)。
これの欠点は、クライアントがユーザー入力を待っている間、サーバーが停止したことがわかりません。クライアント入力を入力する必要があり、その後サーバーが停止したことがわかります。これが希望する動作でない場合は、コメントを投稿してください(おそらくそれが質問の要点でした-目的の場所に到達するために必要以上に長い道のりを進んでいたようです) )。
これがクライアントのわずかな変更です。明示的なハートビートは使用しませんが、サーバーから読み取りを続けている限り、とにかくすぐに切断を検出します。
これは、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 ...
アデル、あなたのコードを見ていた 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');
}
@Balaの答えはサーバー側では正しいと思います。クライアント側で補足をお願いします。
クライアント側では、次のことを行う必要があります。
以下は、いくつかのコードスニペットです。
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;