で言及されているさまざまな解決策( this 、 this および他のいくつか)を試した後、これをエレガントな方法で処理できないことに少し腹を立てていますいくつかのSOの質問への回答、それでも(ケーブルを抜くことによって)ソケットの切断を検出することができませんでした。
NIOノンブロッキングソケットを使用していますが、サーバーの切断を検出する方法が見つからないことを除いて、すべてが完全に機能します。
私は次のコードを持っています:
_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の読み取り中にケーブルを抜くと、Selector.select()
が回転を開始して0を返しますが、読み取りする機会がありません。 )またはwriteチャネル、メインの読み取りおよび書き込みコードはif (selectedNum > 0)
によって保護されているため、これが最初の混乱です。私の頭の中では、 この回答 から、チャネルが壊れると、select()が返され、チャネルの選択キーが返されると言われています。読み取り可能/書き込み可能を示しますが、ここでは明らかにそうではなく、キーは選択されていません。select()
は引き続き0を返します。
また、 EJPの回答 から同様の質問へ:
ピアがソケットを閉じる場合:
- read()は-1を返します
- readLine()はnullを返します
- readXXX()は、他のXに対してEOFExceptionをスローします。
ここでもそうではありません。if (selectedNum > 0)
をコメントアウトし、selector.keys().iterator()
を使用して、選択されているかどうかに関係なくすべてのキーを取得しようとしました。これらのキーから読み取っても-1(0)は返されません。代わりに返されます)、これらのキーへの書き込みはEOFException
がスローされません。キーが選択されていなくても、key.isReadable()
はtrueを返し、key.isWritable()
はfalseを返します(これは、OP_WRITEのキーを登録しなかったことが原因である可能性があります)。 。
私の質問は、なぜJavaソケットがこのように動作しているのですか、それとも私が間違ったことがあるのですか?
TCP接続でタイマーとハートビートが必要であることがわかりました。
ネットワークケーブルを抜いてもTCP接続が切れない場合があります。送信するものがない場合、TCP/IPスタックに送信するものがない場合、ケーブルがどこかになくなっていること、またはピアPCが突然炎上したことを認識していません。そのTCP接続は、数年後にサーバーを再起動するまで開いていると見なされる可能性があります。
このように考えてください。 TCP接続は、もう一方の端がネットワークから切断されたことをどのように知ることができますか。ネットワークから離れているため、その事実を伝えることはできません。
サーバーに接続されているケーブルを抜くと、これを検出できるシステムもあれば、検出しないシステムもあります。たとえば、もう一方の端でケーブルを抜いた場合。イーサネットスイッチ。これは検出されません。
そのため、TCP接続には常にスーパーバイザータイマー(たとえば、ハートビートメッセージをピアに送信する、または一定時間アクティビティがないことに基づいてTCP接続を閉じる)が必要です。
データの読み取りのみを行い、書き込みを行わず、何年も継続して使用するTCP接続を少なくとも回避する非常に安価な方法の1つは、TCPソケットでTCPキープアライブを有効にすることです。 -TCPキープアライブのデフォルトのタイムアウトは多くの場合2時間であることに注意してください。
これらの答えはどちらも当てはまりません。 1つ目は接続が切断された場合に関するもので、2つ目(私のもの)はピアが接続を閉じた場合に関するものです。
TCP接続では、データが送受信されていない限り、TCPは意図的に行われるため、接続を切断する必要があるケーブルを引っ張ることについては原則として何もありません。この種のこと全体で堅牢になるように設計されており、ピアクロージングのようにローカルアプリケーションに目を向けるべきものは確かにありません。
TCPで切断された接続を検出する唯一の方法は、接続を介してデータを送信するか、読み取りタイムアウトを適切な間隔の後に失われた接続として解釈することです。これはアプリケーションの決定です。
TCPキープアライブをオンにして、切断された接続の検出を有効にすることもできます。一部のシステムでは、ソケットごとのタイムアウトを制御することもできます。Javaただし、システムのデフォルトのままになります。変更されていない限り、2時間かかるはずです。
KeyIterator.next()を呼び出した後、コードはkeyIterator.remove()を呼び出す必要があります。