95

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

ピアがTCP接続を正常に閉じると、接続の両側のTCPスタックがその事実を認識します。サーバー側(シャットダウンを開始する側)は状態FIN_WAIT2になり、クライアント側(シャットダウンに明示的に応答しない側)は状態になりCLOSE_WAITます。基盤となるTCP接続が終了したかどうかを確認するためにTCPスタックにクエリを実行できるSocketメソッドがないのはなぜですか?SocketChannelTCPスタックがそのようなステータス情報を提供しないということですか?それとも、カーネルへのコストのかかる呼び出しを回避するための設計上の決定ですか?

この質問に対する回答をすでに投稿しているユーザーの助けを借りて、問題がどこから来ているのかがわかると思います。接続を明示的に閉じない側は、最終的にTCP状態になりますCLOSE_WAIT。つまり、接続はシャットダウンの過程にあり、側が独自のCLOSE操作を発行するのを待ちます。isConnected戻っtrueたりisClosed戻ったりするのは十分公平だと思いますfalseが、なぜそのようなものがないのisClosingですか?

以下は、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
...
4

12 に答える 12

29

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

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

反対側がソケットを閉じるのを待つ必要がある場合は、その FIN を待つ必要があります。それを達成するには、検出する必要があります。つまり、ソケットを閉じてSocket.getInputStream().read() < 0ならないということです。InputStream

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

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

次の 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
}

もちろん、両方の側同じ方法で閉じる必要があります。そうしないと、送信側がwhileループをビジー状態に保つのに十分なデータを常に送信している可能性があります (たとえば、送信側がデータを送信するだけで、接続終了を検出するための読み取りを行わない場合)。ただし、それを制御できない場合があります)。

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

1 : 「Fundamental Networking in Java」より: 図を参照してください。3.3 p.45、および§3.7全体、pp 43-48

于 2012-02-22T17:12:34.437 に答える
19

I think this is more of a socket programming question. Java is just following the socket programming tradition.

From Wikipedia:

TCP provides reliable, ordered delivery of a stream of bytes from one program on one computer to another program on another computer.

Once the handshake is done, TCP does not make any distinction between two end points (client and server). The term "client" and "server" is mostly for convenience. So, the "server" could be sending data and "client" could be sending some other data simultaneously to each other.

The term "Close" is also misleading. There's only FIN declaration, which means "I am not going to send you any more stuff." But this does not mean that there are no packets in flight, or the other has no more to say. If you implement snail mail as the data link layer, or if your packet traveled different routes, it's possible that the receiver receives packets in wrong order. TCP knows how to fix this for you.

Also you, as a program, may not have time to keep checking what's in the buffer. So, at your convenience you can check what's in the buffer. All in all, current socket implementation is not so bad. If there actually were isPeerClosed(), that's extra call you have to make every time you want to call read.

于 2008-10-01T06:06:54.953 に答える
11

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

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

于 2008-09-30T22:11:15.457 に答える
8

これまでのところ、質問に完全に答える答えはないので、この問題についての私の現在の理解を要約しています。

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

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

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

于 2008-10-01T13:17:37.040 に答える
7

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

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

プロトコルで実際に OOB データを使用する場合、マイレージは異なる場合があります。

于 2011-08-10T12:28:09.317 に答える
4

興味深いトピックです。確認するために、今Javaコードを掘り下げました。私の発見から、2 つの明確な問題があります。1 つ目は、リモートで閉じられたソケットが半二重でデータを送信できるようにする TCP RFC 自体であるため、リモートで閉じられたソケットはまだ半分開いています。RFC によると、RST は接続を閉じません。明示的な ABORT コマンドを送信する必要があります。そのため、Java は半分閉じたソケットを介してデータを送信できます

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

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

于 2008-09-30T22:15:35.900 に答える
4

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

...もう 1 つの理由は、私が NIO Java クラスを本当に嫌いになり始めていることです。すべてが少し中途半端なようです。

于 2009-07-06T03:44:06.653 に答える
3

これは、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 */  
}  
于 2009-01-24T21:23:08.730 に答える
2

これが不十分な回避策です。SSL を使用してください ;) SSL はティアダウン時にクローズ ハンドシェイクを行うため、ソケットが閉じられていることが通知されます (ほとんどの実装では、適切なハンドシェイク ティアダウンを行うようです)。

于 2012-08-24T15:47:59.617 に答える
2

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

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

于 2008-10-01T11:09:11.187 に答える
0

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

于 2008-10-10T17:35:44.047 に答える
-3

ハーフオープンの Java ソケットを扱う場合、 isInputShutdown()isOutputShutdown( ) を見たいと思うかもしれません。

于 2011-10-27T10:34:26.283 に答える