16

いくつかの SO の質問への回答で言及されているさまざまな解決策 ( thisthis 、およびその他のいくつか)を試した後、これをエレガントな方法で処理できないことに少し腹を立てていますが、それでもソケットの切断を検出できませんでした (ケーブルを抜いて)。

サーバーの切断を検出する方法が見つからないことを除いて、すべてが完全に機能します。

次のコードがあります。

while (true) {
    handlePendingChanges();

    int selectedNum = selector.select(3000);
    if (selectedNum > 0) {
        SelectionKey key = null;
        try {
            Iterator<SelectionKey> keyIterator = selector.selelctedKeys().iterator();
            while (keyIterator.hasNext()) {
                key = keyIterator.next();
                if (!key.isValid())
                    continue;

                System.out.println("key state: " + key.isReadable() + ", " + key.isWritable());

                if (key.isConnectable()) {
                    finishConnection(key);
                } else if (key.isReadable()) {
                    onRead(key);
                } else if (key.isWritable()) {
                    onWrite(key);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
            System.err.println("I am happy that I can catch some errors.");
        } finally {
            selector.selectedKeys().clear();
        }
    }
}

SocketChannels が読み取られている間、ケーブルのプラグを抜き、回転を開始して 0 を返します。メインの読み取りと書き込みコードが によって保護されているため、チャネルの読み取りまたは書き込みSelector.select()を行う機会がありません。これが最初の混乱の原因です。私の頭の中で、この回答から、チャネルが壊れているとselect()が返され、チャネルの選択キーが読み取り可能/書き込み可能を示すと言われていますが、ここでは明らかにそうではなく、キーはありません選択しても 0 を返します。if (selectedNum > 0)select()

また、同様の質問に対するEJPの回答から:

ピアがソケットを閉じる場合:

  • read() は -1 を返します
  • readLine() は null を返します
  • readXXX() は、その他の X に対して EOFException をスローします。

ここでもそうではありません。コメントアウトして、選択されているかどうかに関係なくすべてのキーを取得するためif (selectedNum > 0)に使用しようとしましたselector.keys().iterator()が、それらのキーからの読み取りは -1 を返さず (代わりに 0 が返されます)、それらのキーへの書き込みはEOFExceptionスローされません。key.isReadable()キーが選択されていなくてもtrueを返し、falseを返すという1つのことだけに注意しましkey.isWritable()た(これは、OP_WRITEのキーを登録しなかったためだと思います)。

私の質問は、なぜJavaソケットがこのように動作しているのか、それとも私が何か間違ったことをしたのでしょうか?

4

2 に答える 2

22

TCP 接続でタイマーとハートビートが必要であることを発見しました。

ネットワーク ケーブルを抜いても、TCP 接続が切断されない場合があります。送信するものが何もない場合、TCP/IP スタックは送信するものがなく、ケーブルがどこかになくなったことや、ピア PC が突然炎上したことを知りません。その TCP 接続は、数年後にサーバーを再起動するまで開いていると見なすことができます。

このように考えてください。TCP 接続は、相手側がネットワークから切断されたことをどのように知ることができますか? ネットワークから外れているため、その事実を伝えることはできません。

サーバーに接続されているケーブルを抜くと、これを検出できるシステムもあれば、検出できないシステムもあります。イーサネット スイッチなどのもう一方の端でケーブルを抜いても、それは検出されません。

そのため、TCP 接続には常にスーパーバイザー タイマーが必要です (たとえば、ハートビート メッセージをピアに送信したり、一定時間アクティビティがないことに基づいて TCP 接続を閉じたりします)。

少なくとも、データを読み取るだけで書き込みを行わない TCP 接続を回避する非常に安価な方法の 1 つは、TCP ソケットで TCP キープアライブを有効にすることです。TCP キープアライブのデフォルトのタイムアウトは、多くの場合、 2時間。

于 2012-12-23T21:43:21.960 に答える
8

これらの答えはどちらも当てはまりません。1つ目は接続が切断された場合に関するもので、2つ目(私のもの)はピアが接続を閉じた場合に関するものです。

TCP接続では、データが送受信されていない限り、TCPはこの種のこと全体で堅牢になるように意図的に設計されているため、接続を切断する必要のあるケーブルを引っ張ることについては原則として何もありません。ピアクロージングのようにローカルアプリケーションに目を向ける必要があります。

TCPで切断された接続を検出する唯一の方法は、TCPを介してデータを送信するか、読み取りタイムアウトを適切な間隔の後に失われた接続として解釈することです。これはアプリケーションの決定です。

TCPキープアライブをオンに設定して、切断された接続の検出を有効にすることもできます。一部のシステムでは、ソケットごとのタイムアウトを制御することもできます。ただし、Java経由ではないため、システムのデフォルトのままになります。変更されていない限り、2時間かかるはずです。

keyIterator.next()を呼び出した後、コードはkeyIterator.remove()を呼び出す必要があります。

于 2012-12-23T20:43:58.290 に答える