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


92

最近の質問のフォローアップとして、TCPソケットの読み取り/書き込みを試みない限り、ソケットがピアによって正常に閉じられていることを検出することがJavaで不可能である理由は何ですか?これは、かどうかに関係なく1回の使用前NIOの場合であるように思わSocketまたはNIO SocketChannel

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

この質問に対するいくつかの回答を既に投稿したユーザーの助けを借りて、問題の原因がどこにあるのかがわかります。接続を明示的に閉じない側はTCP状態になりCLOSE_WAIT、接続はシャットダウンの処理中であり、側が独自のCLOSE操作を発行するのを待ちます。私はisConnected戻っtrueisClosed戻ってくるのは十分に公正だと思います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
...

SCTPプロトコルにはこの「問題」はありません。SCTPはTCPのようにハーフクローズ状態ではありません。つまり、もう一方の端が送信ソケットを閉じている間、一方の側はデータの送信を続行できません。それは物事をより簡単にするはずです。
TarnayKálmán

2
2つのメールボックス(ソケット)があります。 ......メールボックスはTCPを忘れてRoyalMail(IP)を使用して互いにメールを送信します............................. ....................すべてが順調であり、ダンディであるので、メールボックスは互いに問題なくメールを送信/受信できます(最近の大量の遅延)。............. 1つのメールボックスがトラックに衝突して失敗した場合、他のメールボックスはどうやって知るのでしょうか。Royal Mailから通知を受ける必要があります。RoyalMailは次に、失敗したメールボックスからのメールの送受信を試行するまでは知らないでしょう。……
erm

ソケットからの読み取りまたはソケットへの書き込みを行わない場合、なぜ気にするのですか?そして、ソケットから読み取るまたはソケットに書き込む場合は、なぜ追加のチェックを行うのですか?ユースケースは何ですか?
David Schwartz

2
Socket.close優雅な終わりではありません。
user253751 2014年

@immibisソケットの受信バッファーに未読のデータがない場合、またはSO_LINGERを使用していない場合を除き、これはおそらく正常なクローズです。
ローン侯爵

回答:


29

私はソケットを頻繁に、主にセレクタと共に使用してきましたが、ネットワークOSIの専門家ではありませんが、私の理解から、shutdownOutput()ソケットを呼び出すと、実際にネットワーク(FIN)に何かが送信され、反対側のセレクタが起動します(Cと同じ動作)言語)。ここでは、持っている検出:実際にあなたがそれをしようとすると失敗します読み取り操作を検出します。

提供するコードでは、ソケットを閉じると、入力ストリームと出力ストリームの両方がシャットダウンされ、使用可能なデータを読み取る可能性がないため、ストリームが失われます。Java Socket.close()メソッドは、出力ストリームに残されたデータが送信され、その後にFIN続くことを通知する「優雅な」切断を実行します(最初に思ったのとは逆です)。FINは、通常のパケットと同様に、反対側からACKされます1

反対側がソケットを閉じるのを待つ必要がある場合、FINを待つ必要があります。そして、それを達成するために、あなたがする必要が検出Socket.getInputStream().read() < 0あなたがすべきことを意味する、ではないことがあろうよう、あなたのソケット近くのクローズInputStream

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

  1. ソケット出力をシャットダウンします(反対側でFINを送信します。これは、このソケットによって送信される最後のものです)。入力はまだ開いているのでread()、リモートを検出できますclose()
  2. InputStream相手側からreply-FINを受信するまでソケットを読み取ります(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:「Javaの基本的なネットワーキング」から:図を参照。3.3 p.45、および全体3.7、pp 43-48


Javaは正常に終了します。「残忍」ではありません。
ローンの侯爵2013

2
@EJP、「正常な切断」はTCPレベルで行われる特定の交換であり、クライアントはサーバーに切断する意思を通知する必要があり、サーバーはサーバーを閉じる前に残りのデータを送信します。「残りのデータを送信する」の部分はプログラムで処理する必要があります(ほとんどの場合、ユーザーは何も送信しません)。呼び出しsocket.close()は、このクライアント/サーバーシグナリングを尊重しない点で「残忍」です。サーバーは、それ自体のソケット出力バッファーがいっぱいになったときにのみクライアントの切断を通知されます(データが閉じられた反対側によって確認されていないため)。
Matthieu

詳細については、MSDNを参照してください。
Matthieu

Javaは、アプリケーションに「残りのデータを送信」する代わりにSocket.close()を強制的に呼び出さず、シャットダウンを最初に使用することを妨げません。Socket.close()は単にclose(sd)が行うことを行います。この点で、JavaはBerkeley Sockets API自体と同じです。確かに、ほとんどの点で。
ローン侯爵2013年

2
@LeonidUsovそれは単に間違っています。Java read()はストリームの終わりに-1を返し、何度も呼び出しますが、そうし続けます。AC read()またはrecv()ストリームの終わりでゼロを返し、それを何度も呼び出しますが、何度も呼び出します。
ローンの侯爵

18

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

ウィキペディアから:

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

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

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

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


1
私はそうは思いません、あなたはウィンドウとLinuxの両方でCコードの状態をテストすることができます!!! 何らかの理由でJavaは一部のものを公開しない可能性があります。たとえば、WindowsとLinuxにあるgetsockopt関数を公開するとよいでしょう。実際、以下の回答にはLinux側のLinux Cコードが含まれています。
ディーンヒラー

「isPeerClosed()」メソッドを使用しても、すべての読み取りを試行する前に、なんとかしてそれを呼び出す必要があるとは思いません。明示的に必要な場合にのみ呼び出すことができます。ソケットのリモート部分が閉じているかどうかを確認したい場合は、出力ストリームに書き込む必要がありますが、現在のソケットの実装はそれほど悪くないことに同意します。そうでない場合は、書き込まれたデータを反対側でも処理する必要があるため、垂直方向のネイルに座っているのと同じくらい大きな喜びです。)
Jan Hruby

1
それ「飛行中のパケットがなくなった」ことを意味します。FINは、データの送信後に受信さます。ただし、ピアが入力用のソケット閉じたという意味ではありませんあなたはする必要があります**送信 *何か、それを検出するために、「接続リセット」を取得します。FINは、出力のシャットダウンを意味するだけでした。
ローンの侯爵、2014年

11

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

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


私のテスト例では(おそらくここに1つ提供する必要があります...)接続を介して意図的に送信/受信されたデータはありません。したがって、スタックはFIN(正常)またはRST(一部の正常でないシナリオ)を受信すると確信しています。これはnetstatでも確認されています。
アレクサンダー

1
確かに-何もバッファリングされていない場合、FINはすぐに送信され、それ以外は空のパケット(ペイロードなし)で送信されます。ただし、FINの後は、その接続の終わりまでに送信されたデータパケットはありません(送信されたものはすべてACKします)。
マイクディミック

1
何が起こるかというと、接続の両側が最終的に<code> CLOSE_WAIT </ code>および<code> FIN_WAIT_2 </ code>になり、この状態で<code> isConcected()</ code>および<code> isClosed()</ code>は、接続が終了したことをまだ認識していません。
アレクサンダー

あなたの提案をありがとう!私は問題をよりよく理解していると思います。私は質問をより具体的にしました(3番目の段落を参照):ハーフクローズ接続をテストするための "Socket.isClosing"がないのはなぜですか?
アレクサンダー

8

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

TCP接続が確立され、1つのピアがコールするclose()shutdownOutput()、そのソケット上で、接続の反対側のソケットがCLOSE_WAIT状態に移行します。原則として、ソケットがCLOSE_WAIT呼び出しなしで状態にあるかどうかをTCPスタックから確認することは可能です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は余分な方法を提供することを期待していないために。


3
また、Javaは移植可能な追加のメソッドを提供できません。プラットフォームがサポートしていない場合はfalseを返すisCloseWait()メソッドを作成できるかもしれませんが、サポートされているプラ​​ットフォームでのみテストした場合、何人のプログラマーがその問題にかまれるでしょうか?
2008年

1
それは私に移植可能であるように見えます... windowsにはmsdn.microsoft.com/en-us/library/windows/desktop/…があり、linuxにはpubs.opengroup.org/onlinepubs/009695399/functions/…があります
Dean Hiller

1
プログラマーがそれに慣れているわけではありません。それは、ソケット・インターフェースがプログラマーにとって役立つものであることです。ソケット抽象化は、TCPプロトコル以外にも使用されていることに注意してください。
ウォーレンデュー

isCloseWait()すべてのプラットフォームでサポートされているわけではないため、Javaのようなメソッドはありません。
ローンズオブローン

ident(RFC 1413)プロトコルを使用すると、サーバーは応答を送信した後も接続を開いたままにするか、データを送信せずに接続を閉じることができます。Java IDクライアントは、次のルックアップで3ウェイハンドシェイクを回避するために接続を開いたままにすることを選択する場合がありますが、接続がまだ開いていることをどのようにして知るのでしょうか。接続を再度開いて、エラーに応答してみてください。それともプロトコル設計エラーですか?
デビッド

7

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

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

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


4

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

...別の理由で、NIO Javaクラスが本当に嫌われ始めています。すべてが少しお尻のようです。


1
また、FINが存在する場合、読み取り時にストリームの終わり(-1リターン)しか取得しないようです。したがって、これは私が読み取り側のシャットダウンを検出するために確認できる唯一の方法です。

3
あなたはそれを検出することができます。読むときにEOSを取得します。また、JavaはFINを送信しません。TCPはそれを行います。JavaはTCP / IPを実装せず、プラットフォーム実装を使用するだけです。
ローン侯爵2013

4

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

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

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


1
RFC 793(faqs.org/rfcs/rfc793.html)のセクション3.5接続のクローズについて話していると思います。どちらの側も接続の正常なシャットダウンを完了し、データを送受信してはならない状態になるため、問題が説明されているかどうかはわかりません。
アレクサンダー

依存します。ソケットにはいくつのFINがありますか?また、プラットフォーム固有の問題である可能性があります。Windowsが各FINでFINを返し、接続が両端で閉じている可能性がありますが、他のオペレーティングシステムはこのように動作しない可能性があり、ここで問題2が発生します
Lorenzo Boccaccia

1
いいえ、残念ながらそうではありません。isOutputShutdownとisInputShutdownは、この「発見」に直面したときに誰もが最初に試みることですが、どちらのメソッドもfalseを返します。Windows XPとLinux 2.6でテストしたところです。4つのメソッドすべての戻り値は、読み取りを試みた後も同じままです
Alexander

1
記録としては、これは半二重ではありません。半二重とは、同時に送信できるのは片側だけであることを意味します。両側はまだ送ることができます。
rbp 2013年

1
isInputShutdownおよびisOutputShutdown は、接続のローカルエンドをテストします。これらは、このソケットでshutdownInputまたはshutdownOutputのどちらを呼び出したかを確認するためのテストです。接続のリモートエンドについては何も通知されません。
マイクディミック2013

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 */  
}  

同じことをで行うことができますgetsocketop(... SOL_SOCKET, SO_ERROR, ...)
David Schwartz

1
エラーファイル記述子セットは、閉じられた接続を示しません。選択マニュアルを読んでください: 'exceptfds-このセットは「例外的な条件」で監視されています。実際には、このような例外的な状態が1つだけ一般的です。それは、TCPソケットから読み取るための帯域外(OOB)データの可用性です。FINはOOBデータではありません。
Jan Wrobel 2012

1
'Selector'クラスを使用して、 'select()' syscallにアクセスできます。NIOを使用します。
Matthieu 2013

1
反対側によってシャットダウンされた接続に例外はありません。
David Schwartz

1
多くのプラットフォーム、などの Javaは、やる select()システムコールへのアクセスを提供します。
ローンの侯爵2014年

2

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


2
JavaでSSLを使用しているときに、ソケットが閉じられたことを「通知」するにはどうすればよいですか?
Dave B 14

2

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

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


2
REALbasicソケット(Mac OS XおよびLinuxの場合)はBSDソケットに基づいていますが、RBは、接続が相手側によってドロップされたときに素敵なエラー102を発生させます。だから私は元のポスターに同意します、これは可能であるはずであり、Java(およびCocoa)がそれを提供していないのは不自然です。
ジョーストラウト2010年

1
@JoeStrout RBは、いくつかのI / Oを行うときにのみそれを実行できます。I / Oを行わずに接続のステータスを取得するAPIはありません。限目。これはJavaの欠陥ではありません。実際には、TCPには「ダイヤルトーン」がありません。これは、意図的な設計機能です。
ローン侯爵2013

select() ブロックせずに読み取ることができるデータまたはEOSがあるかどうかを示します。「ブロックせずに試すことができる信号」は無意味です。非ブロックモードの場合は、ブロックせずにいつでも試すことができます。select()ソケット受信バッファー内のデータ、またはソケット送信バッファー内の保留中のFINまたはスペースによって駆動されます。
ローンの侯爵

@EJPどうgetsockopt(SO_ERROR)ですか?実際にgetpeernameは、ソケットがまだ接続されているかどうかさえわかります。
David Schwartz

0

書き込みのみがパケットの交換を必要とするため、接続の切断を判別できます。一般的な回避策は、KEEP ALIVEオプションを使用することです。


エンドポイントは、ペイロードを書き込まずに、FINが設定されたパケットを送信することにより、適切な接続のシャットダウンを開始できると思います。
アレクサンダー

@Alexanderもちろんそうですが、これはこの回答には関係がありません。これは、接続の検出を減らすことに関するものです。
ローン侯爵

-3

ハーフオープンのJavaソケットを処理する場合は、 isInputShutdown()isOutputShutdown()確認することをお勧めします。


いいえ、それはあなたが何を呼んだかをあなたに伝えるだけであり、ピアが呼んだものではありません。
ローン侯爵2013

その声明の出典を共有したいですか?
JimmyB 2013

3
反対のソースを共有することに気をつけますか?それはあなたの声明です。証拠があればそれを手に入れましょう。私はあなたが間違っていると断言します。実験をして、私が間違っていることを証明してください。
ローン侯爵2013

3年後、実験なし。QED
ローン侯爵
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.