web-dev-qa-db-ja.com

I / Oを試行せずに、TCPソケットがピアによって正常に閉じられたことを検出できないのはなぜですか?

最近の質問 のフォローアップとして、なぜJavaでは、TCPソケットで読み取り/書き込みを試みずに、ソケットがピアによって正常に閉じられましたか?これは、pre-NIO SocketまたはNIO SocketChannelのいずれを使用するかに関係なく当てはまるようです。

ピアがTCP=接続を正常に閉じると、接続の両側のTCPスタックは事実を認識します。サーバー側(開始する側)シャットダウン)状態FIN_WAIT2、クライアント側(シャットダウンに明示的に応答しないクライアント側)は状態CLOSE_WAIT。なぜSocketまたはSocketChannelにTCPスタックを照会して、基礎となるTCP =接続が終了しましたか?TCPスタックはそのようなステータス情報を提供しませんか?それとも、コストのかかるカーネルへの呼び出しを避けるための設計上の決定ですか?

この質問への回答をすでに投稿しているユーザーの助けを借りて、問題の原因はどこにあるのかわかりました。接続を明示的に閉じない側は、TCP state CLOSE_WAITは、接続がシャットダウン処理中であり、サイドが独自のCLOSE操作を発行するのを待っていることを意味します。 isConnectedtrueを返し、isClosedfalseを返しますが、なぜisClosingのようなものがないのでしょうか?

以下は、pre-NIOソケットを使用するテストクラスです。ただし、NIOを使用しても同じ結果が得られます。

import Java.net.ServerSocket;
import Java.net.Socket;

public class MyServer {
  public static void main(String[] args) throws Exception {
    final ServerSocket ss = new ServerSocket(12345);
    final Socket cs = ss.accept();
    System.out.println("Accepted connection");
    Thread.sleep(5000);
    cs.close();
    System.out.println("Closed connection");
    ss.close();
    Thread.sleep(100000);
  }
}


import Java.net.Socket;

public class MyClient {
  public static void main(String[] args) throws Exception {
    final Socket s = new Socket("localhost", 12345);
    for (int i = 0; i < 10; i++) {
      System.out.println("connected: " + s.isConnected() + 
        ", closed: " + s.isClosed());
      Thread.sleep(1000);
    }
    Thread.sleep(100000);
  }
}

テストクライアントがテストサーバーに接続すると、サーバーが接続のシャットダウンを開始した後でも出力は変更されません。

connected: true, closed: false
connected: true, closed: false
...
89
Alexander

私はソケットを頻繁に使用していますが、ほとんどはセレクターで、ネットワークOSIの専門家ではありませんが、ソケットのshutdownOutput()を呼び出すと、実際にはネットワーク上の何か(FIN)が送信され、反対側のセレクターが起動します(同じ動作) C言語)。ここには detection があります。実際に、試行すると失敗する読み取り操作を検出します。

指定したコードでは、ソケットを閉じると、入力ストリームと出力ストリームの両方がシャットダウンされ、利用可能なデータを読み取る可能性がないため、それらが失われます。 Java Socket.close()メソッドは、出力ストリームに残ったデータが送信されるという点で、「グレースフル」な切断(当初考えていたのとは反対))を実行しますFINはその終了を通知します。通常のパケットがそうであるように、FINは反対側からACKされます。1

反対側がソケットを閉じるのを待つ必要がある場合は、そのFINを待つ必要があります。それを実現するには、 /Socket.getInputStream().read() < 0を検出する必要があります。つまり、notは、InputStreamを閉じます。

私がCで、そして今はJavaで行ったことから、このような同期化されたクローズを実現するには、次のようにします。

  1. ソケット出力をシャットダウンします(もう一方の端にFINを送信します。これは、このソケットによって送信される最後のものです)。入力はまだ開いているので、read()を実行して、リモートのclose()を検出できます
  2. 相手から応答-FINを受け取るまで、ソケットInputStreamを読み取ります(FINを検出するため、同じ正常な接続解除プロセスを実行します)。一部のOSでは、バッファの1つにデータが含まれている限り、実際にはソケットを閉じないため、これは重要です。それらは「ゴースト」ソケットと呼ばれ、OSで記述子番号を使い果たします(これは、現在のOSではもう問題にならないかもしれません)
  3. ソケットを閉じます(Socket.close()を呼び出すか、そのInputStreamまたはOutputStreamを閉じます)。

以下に示すようにJavaスニペット:

public void synchronizedClose(Socket sok) {
    InputStream is = sok.getInputStream();
    sok.shutdownOutput(); // Sends the 'FIN' on the network
    while (is.read() > 0) ; // "read()" returns '-1' when the 'FIN' is reached
    sok.close(); // or is.close(); Now we can close the Socket
}

もちろん、両側have toは同じ方法で閉じるか、送信側が常にwhileループをビジーに保つのに十分なデータを送信している可能性があります(送信側が接続の終了を検出するためにデータを送信するだけで読み取りはしません。これは不器用ですが、制御できない可能性があります。

@WarrenDewがコメントで指摘したように、プログラム(アプリケーションレイヤー)のデータを破棄すると、アプリケーションレイヤーで非正常な切断が発生します。ただし、すべてのデータはTCPレイヤー(whileループ)、それらは破棄されます。

1:「 Javaの基本的なネットワーク 」から:図を参照3.3 p.45、および§3.7全体、43-48ページ

29
Matthieu

これは、ソケットプログラミングの問題だと思います。 Javaは、ソケットプログラミングの伝統に従っています。

From Wikipedia

TCPは、あるコンピューター上の1つのプログラムから別のコンピューター上の別のプログラムへのバイトストリームの信頼性の高い順序付き配信を提供します。

ハンドシェイクが完了すると、TCPは2つのエンドポイント(クライアントとサーバー)を区別しません。「クライアント」と「サーバー」という用語は、ほとんどの場合、便宜上のものです。 「データを送信している可能性があり、「クライアント」が他のデータを同時に送信している可能性があります。

「閉じる」という用語も誤解を招きます。 FIN宣言のみがあります。これは、「これ以上は送信しません」という意味です。しかし、これは、飛行中のパケットがないこと、または他に言う必要がないことを意味しません。カタツムリメールをデータリンクレイヤーとして実装する場合、またはパケットが異なるルートを移動する場合、受信者が間違った順序でパケットを受信する可能性があります。 TCPはこれを修正する方法を知っています。

また、あなたはプログラムとして、バッファにあるものをチェックし続ける時間がないかもしれません。そのため、都合の良いときに、バッファの内容を確認できます。全体として、現在のソケットの実装はそれほど悪くありません。実際にisPeerClosed()があった場合、readを呼び出すたびに行う必要がある余分な呼び出しです。

18
Eugene Yokota

基礎となるソケットAPIには、このような通知はありません。

送信側のTCPスタックは最後のパケットまでFINビットを送信しないため、送信側アプリケーションがそのデータが送信される前にソケットを論理的に閉じたときから多くのデータがバッファリングされる可能性があります同様に、ネットワークが受信側のアプリケーションよりも高速であるためにバッファリングされたデータ(私は知らないかもしれませんが、遅い接続で中継しているかもしれません)は受信側にとって重要であり、受信側のアプリケーションが破棄したくないでしょうスタックがFINビットを受信したからです。

11
Mike Dimmick

これまでの回答のいずれも完全に質問に答えていないため、この問題に対する私の現在の理解を要約しています。

TCP接続が確立され、一方のピアがそのソケットでclose()またはshutdownOutput()を呼び出すと、接続の反対側のソケットはCLOSE_WAIT状態に移行します。原則として、ソケットがCLOSE_WAIT状態にあるかどうかをTCPスタックから調べることは、read/recvを呼び出さなくても可能です(たとえば、Linuxのgetsockopt()http://www.developerweb.net /forum/showthread.php?t=4395 )しかし、それは移植性がありません。

JavaのSocketクラスは、BSD TCPソケットに匹敵する抽象化を提供するように設計されているようです。これはおそらく、TCP/IPアプリケーションをプログラミングするときに慣れる抽象化レベルだからです。 BSDソケットは、INET(たとえば、TCP)ソケット以外のソケットをサポートする一般化であるため、ソケットのTCP状態を見つけるポータブルな方法を提供しません。

BSDソケットによって提供される抽象化レベルでTCPアプリケーションをプログラミングするのに慣れている人は、Javaが追加のメソッドを提供することを期待しないため、isCloseWait()のようなメソッドはありません。

8
Alexander

(TCP)ソケット接続のリモート側が閉じているかどうかを検出するには、Java.net.Socket.sendUrgentData(int)メソッドを使用し、リモート側がダウンしている場合にスローされるIOExceptionをキャッチします。これは、Java-JavaとJava-Cの間でテストされています。

これにより、ある種のpingメカニズムを使用するように通信プロトコルを設計する問題を回避できます。ソケットでOOBInlineを無効にすると(setOOBInline(false)、受信したOOBデータはすべて破棄されますが、OOBデータは引き続き送信できます。リモート側が閉じられると、接続リセットが試行され、失敗し、IOExceptionがスローされます。 。

プロトコルで実際にOOBデータを使用する場合、走行距離は異なる場合があります。

7
Uncle Per

Java IOスタックは突然の分解で破壊されると、FINを確実に送信します。これを検出できないという意味はありません。b/ cほとんどのクライアントは、接続をシャットダウンしている場合にのみFINを送信します。

... NIO Javaクラスを本当に嫌い始めているもう1つの理由。

4
Sean

興味深いトピックです。確認するためにJavaコードを今すぐ掘り下げました。私の発見から、2つの明確な問題があります。1つ目はTCP RFC自体です。 RFCによると、RSTは接続を閉じないため、明示的にABORTコマンドを送信する必要があります; Javaハーフクローズソケットを介したデータ送信を許可

(両方のエンドポイントでクローズ状態を読み取る方法は2つあります。)

他の問題は、実装がこの動作がオプションであると言うことです。 Javaは移植性を追求しているため、最も一般的な機能を実装しました。(OS、半二重の実装)のマップを維持するのは問題だったでしょう。

4

これはJava(および私が見た他のすべて)の欠陥ですOOソケットクラス-selectシステムコールへのアクセスなし。

Cの正解:

struct timeval tp;  
fd_set in;  
fd_set out;  
fd_set err;  

FD_ZERO (in);  
FD_ZERO (out);  
FD_ZERO (err);  

FD_SET(socket_handle, err);  

tp.tv_sec = 0; /* or however long you want to wait */  
tp.tv_usec = 0;  
select(socket_handle + 1, in, out, err, &tp);  

if (FD_ISSET(socket_handle, err) {  
   /* handle closed socket */  
}  
3
Joshua

ここに不完全な回避策があります。 SSLを使用します;)とSSLはティアダウンでクローズハンドシェイクを行うため、ソケットがクローズされたことが通知されます(ほとんどの実装は、適切なハンドシェイクティアダウンを行うようです)。

2
Dean Hiller

この動作の理由(Java固有ではありません)は、TCPスタックからステータス情報を取得できないという事実です。結局、ソケットは単なる別のファイルハンドルであり、実際に試行せずにそこから読み取る実際のデータがあるかどうかを見つけることはできません(select(2)はそこでは役に立ちません。ブロックせずに試行できることを示すだけです) 。

詳細については、 nixソケットFAQ を参照してください。

2
WMR

書き込みの場合のみ、パケットの交換が必要です。これにより、接続の損失を判別できます。一般的な回避策は、KEEP ALIVEオプションを使用することです。

0
Ray