JavaソケットAPI:接続が閉じているかどうかを確認する方法は?


92

JavaソケットAPIでいくつかの問題が発生しています。ゲームに現在接続しているプレーヤーの数を表示しようとしています。プレーヤーがいつ接続したかは簡単に判断できます。ただし、ソケットAPIを使用してプレーヤーがいつ切断されたかを判断するのは不必要に難しいようです。

isConnected()リモートで切断されたソケットを呼び出すと、常に戻るように見えtrueます。同様に、isClosed()リモートで閉じられたソケットを呼び出すと、常に戻るように見えfalseます。実際にソケットが閉じられているかどうかを判断するには、データを出力ストリームに書き込み、例外をキャッチする必要があることを読みました。これは、この状況を処理するための本当に不潔な方法のようです。ソケットがいつ閉じられたかを知るために、ネットワークを介してゴミメッセージを絶えず送信する必要があります。

他の解決策はありますか?

回答:


183

接続の現在の状態を通知するTCP APIはありません。isConnected()そしてisClosed()あなたの現在の状態教えてあなたのソケットのを。同じではありません。

  1. isConnected()このソケットを接続したかどうか通知します。あなたが持っているので、それはtrueを返します。

  2. isClosed()このソケットを閉じたかどうか通知します。あなたが持っているまで、それはfalseを返します。

  3. ピアが通常の方法で接続を閉じた場合

    • read() -1を返します
    • readLine() 戻り値 null
    • readXXX()EOFException他のXXXをスローします。

    • 書き込みIOExceptionにより、「ピアによる接続リセット」がスローされますが、最終的にはバッファリングの遅延の影響を受けます。

  4. 他の理由で接続が切断された場合、書き込みはIOException最終的に上記のようにをスローし、読み取りは同じことを行う可能性があります。

  5. ピアがまだ接続されているが、接続を使用していない場合は、読み取りタイムアウトを使用できます。

  6. あなたが他で読むかもしれないものに反して、これをあなたにClosedChannelException言わないでください。[どちらでもありませんSocketException: socket closed.] チャンネル閉じて、それを使い続けたこと伝えるだけです。つまり、プログラミングエラーです。閉じられた接続を示すものではありません

  7. Windows XPでJava 7を使用して実験を行った結果、次の場合にも表示されます。

    • あなたは選択しています OP_READ
    • select() ゼロより大きい値を返します
    • 関連SelectionKeyは既に無効です(key.isValid() == false

    ピアが接続をリセットしたことを意味します。ただし、これはJREのバージョンまたはプラットフォームに固有の場合があります。


18
接続指向のTCPプロトコルは、その接続の状態さえ知ることができないとは信じがたいです...このプロトコルを思いついた人たちは目を閉じて車を運転しますか?
PedroD 2014

31
@PedroDそれどころか、それは意図的なものでした。SNAなどの以前のプロトコルスイートには「ダイヤルトーン」がありました。TCPは核戦争、さらには自明なことにルータのダウンとアップに耐えるように設計されています。したがって、ダイヤルトーンや接続ステータスなどのようなものはまったくありません。また、TCPキープアライブが論争の的になっている機能としてRFCで説明されている理由でもあり、デフォルトで常にオフになっている理由でもあります。TCPはまだ残っています。SNA?IPX?ISO?ない。彼らはそれを正しく理解しました。
ローン侯爵2014

3
それが私たちからこの情報を隠すための良い言い訳だとは思いません。接続が失われたことを知っていても、必ずしもプロトコルの耐障害性が低いとは限りません。常に、その知識で何をするかに依存します...私にとって、JavaからのisBoundおよびisConnectedメソッドは純粋なモックメソッドであり、役に立たない、しかし接続イベントリスナーの必要性を表現しています...しかし私は繰り返します:接続が失われたことを知っていてもプロトコルを悪化させることはありません。さて、あなたが言っているこれらのプロトコルが接続が失われたことを検出するとすぐに接続を切断した場合、それは別の話です。
PedroD 2014

21
@Pedro分かりません。それは「言い訳」ではありません。差し控える情報はありません。発信音はありません。TCP 、ユーザーが何かをしようとするまで、接続が失敗したかどうかを認識しませんそれが基本的な設計基準でした。
ローン侯爵2014

2
@EJP回答ありがとうございます。「ブロック」せずにソケットが閉じられたかどうかを確認する方法をおそらく知っていますか?readまたはreadLineを使用するとブロックされます。他のソケットが利用できる方法で閉じてしまった場合には実現する方法はあり、返すように思える0の代わりに-1
保険金

9

さまざまなメッセージングプロトコルでは、互いにハートビートを維持し続ける(pingパケットを送信し続ける)ことが一般的です。パケットはそれほど大きくする必要はありません。プローブメカニズムを使用すると、TCPが一般的に把握する前であっても、切断されたクライアントを検出できます(TCPタイムアウトははるかに高い)プローブを送信し、応答を5秒待ちます(たとえば、2-3の応答がない場合)。その後のプローブでは、プレーヤーは切断されています。

また、関連する質問


6
一部のプロトコルでは「一般的な慣習」です。地球上で最も使用されているアプリケーションプロトコルであるHTTPにはPING操作がないことに注意してください。
ローンの侯爵

2
@ user207421 HTTP / 1はワンショットプロトコルであるため。リクエストを送信し、レスポンスを受信します。これで完了です。ソケットが閉じています。Websocket拡張にはPING操作があり、HTTP / 2にもあります。
のMichałZabielski

可能であれば、1バイトでハートビートすることをお勧めします:)
Stefan Reich

@MichałZabielskiHTTPの設計者が指定しなかったためです。なぜだか分からない。HTTP / 1.1はワンショットプロトコルではありません。ソケットは閉じていません。HTTP接続はデフォルトで永続的です。FTP、SMTP、POP3、IMAP、TLS、...ハートビートはありません。
ローン侯爵

2

他の回答が投稿されたところですが、ゲームをプレイしているクライアントと対話していると思いますので、別のアプローチを提案するかもしれません(BufferedReaderが確実に有効な場合もあります)。

必要な場合は、「登録」の責任をクライアントに委任できます。つまり、各ユーザーから受信した最後のメッセージのタイムスタンプが付いた接続ユーザーのコレクションが存在します...クライアントがタイムアウトした場合、クライアントの再登録を強制することになりますが、以下の見積もりとアイデアにつながります。

ソケットが閉じられているかどうかを実際に判断するには、データを出力ストリームに書き込み、例外をキャッチする必要があることを確認しました。これは、この状況を処理するための本当に不自然な方法のようです。

Javaコードがソケットを閉じたり切断したりしなかった場合、リモートホストが接続を閉じたことを他にどのように通知しますか?最終的に、try / catchは、ACTUALソケットでイベントをリッスンするポーラーが行うのとほぼ同じことを行います。以下を検討してください。

  • ローカルシステムが通知せずにソケットを閉じる可能性があります...それは単なるソケットの実装です(つまり、ハードウェア/ドライバ/ファームウェア/状態の変化については何もポーリングしません)。
  • 新しいSocket(Proxy p)...接続を閉じる可能性のある複数のパーティ(実際には6つのエンドポイント)があります...

抽象化言語の特徴の1つは、マニューシャから抽象化されていることです。SqlConnectionなどのC#でのキーワードの使用(トライ/ファイナル)を考えてみてください...ビジネスを行うためのコストにすぎません...ソケットの使用には、try / catch / finallyが受け入れられ、必要なパターンだと思います。


ローカルシステムは、通知の有無にかかわらず、「ソケットを閉じる」ことはできません。「Javaコードがソケットを閉じたり切断したりしなかった場合...?」意味もありません。
ローンの侯爵2012

1
システム(Linuxなど)がソケットを強制的に閉じるのを正確に防ぐものは何ですか?「call close」コマンドを使用して「gdb」からそれを行うことはまだ可能ですか?
Andrey Lebedenko

@AndreyLebedenko「正確にそれを防ぐ」ことは何もありませんが、それはそれをしません。
ローン侯爵

@EJBは誰ですか?
Andrey Lebedenko 2017年

@AndreyLebedenko誰もそれをしません。終了せずに終了しない限り、アプリケーションのみが独自のソケットを閉じることができます。その場合、OSはクリーンアップします。
ローン侯爵

1

これはtcp接続の性質であると私は思います。その標準では、接続がなくなったと結論付ける前に、転送に約6分の無音時間がかかります。そのため、この問題の正確な解決策を見つけることができないと思います。たぶん、より良い方法は、サーバーがユーザー接続が閉じられていると想定する必要があるときを推測するための便利なコードを書くことです。


2
それよりもはるかに多く、最大2時間かかる場合があります。
ローンの侯爵2012

0

@ user207421が言うように、TCP / IPプロトコルアーキテクチャモデルのため、接続の現在の状態を知る方法はありません。そのため、サーバーは接続を閉じる前に通知する必要があります。または、自分で確認する必要があります。
これは、ソケットがサーバーによって閉じられていることを確認する方法を示す簡単な例です。

sockAdr = new InetSocketAddress(SERVER_HOSTNAME, SERVER_PORT);
socket = new Socket();
timeout = 5000;
socket.connect(sockAdr, timeout);
reader = new BufferedReader(new InputStreamReader(socket.getInputStream());
while ((data = reader.readLine())!=null) 
      log.e(TAG, "received -> " + data);
log.e(TAG, "Socket closed !");

0

私は同様の問題に直面しました。私の場合、クライアントは定期的にデータを送信する必要があります。同じ要件があることを願っています。次に、指定された時間が経過socket.setSoTimeout(1000 * 60 * 5);するとスローされるSO_TIMEOUTを設定java.net.SocketTimeoutExceptionします。そうすれば、死んだクライアントを簡単に検出できます。


-2

それは私がそれを扱う方法です

 while(true) {
        if((receiveMessage = receiveRead.readLine()) != null ) {  

        System.out.println("first message same :"+receiveMessage);
        System.out.println(receiveMessage);      

        }
        else if(receiveRead.readLine()==null)
        {

        System.out.println("Client has disconected: "+sock.isClosed()); 
        System.exit(1);
         }    } 

result.code == nullの場合


readLine()2回呼び出す必要はありません。あなたはすでにそれがelseブロックでnullだったことを知っています。
ローンの侯爵

-4

Linuxでは、あなたには知られていない反対側が閉じているソケットにwrite()すると、SIGPIPEシグナル/例外が呼び出されますが、呼び出したいと思います。ただし、SIGPIPEに捕まりたくない場合は、フラグMSG_NOSIGNALを指定してsend()を使用できます。send()呼び出しは-1で戻ります。この場合、errnoをチェックして、壊れたパイプ(この場合はソケット)をEPIPEの値で書き込もうとしたことがわかります。 32. EPIPEへの対応として、2倍にしてソケットを再度開き、情報をもう一度送信してみます。


send()コールは-1を返します発信データが期限切れに送信タイマーの十分な長さのためにバッファリングされてしまった場合にのみ。両端でのバッファリングと内部の非同期の性質により、切断後の最初の送信ではほぼ確実に発生しませんsend()
ローンの侯爵
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.