SocketChannelの書き込みが、非ブロッキングソケットでも常に全量完了するのはなぜですか?
-
03-07-2019 - |
質問
WindowsでSun Java VM 1.5または1.6を使用して、非ブロッキングソケットを接続します。次に、出力するメッセージを ByteBuffer
に入力し、SocketChannelに write()
を試みます。
書き込まれる量がソケットのTCP出力バッファ内のスペースの量よりも大きい場合、書き込みが部分的にのみ完了することを期待します(これは直感的に期待していることであり、 docs )、しかしそれは起こりません。 write()
always は、たとえ数メガバイト(ソケットのSO_SNDBUFが8KBで、マルチメガバイト出力よりもはるかに少ない場合でも)書き込まれた全量のレポートを返すようですメッセージ)。
ここでの問題は、出力が部分的に書き込まれるケースを処理するコードをテストできないことです( WRITE
のインタレストセットをセレクタに登録し、 select( )
は、残りが書き込まれるまで待機します)、そのようなケースは決して発生しないようです。わからないことは何ですか?
解決
私はあなたに似た状況を再現することができました。皮肉なことに、受信者はあなたが書いているよりも速くデータを消費していると思います。
import java.io.InputStream;
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");
final InputStream in = cs.getInputStream();
final byte[] tmp = new byte[64 * 1024];
while (in.read(tmp) != -1);
Thread.sleep(100000);
}
}
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
public class MyNioClient {
public static void main(String[] args) throws Exception {
final SocketChannel s = SocketChannel.open();
s.configureBlocking(false);
s.connect(new InetSocketAddress("localhost", 12345));
s.finishConnect();
final ByteBuffer buf = ByteBuffer.allocate(128 * 1024);
for (int i = 0; i < 10; i++) {
System.out.println("to write: " + buf.remaining() + ", written: " + s.write(buf));
buf.position(0);
}
Thread.sleep(100000);
}
}
上記のサーバーを実行してから、上記のクライアントが128 kBのデータの10チャンクを書き込もうとすると、すべての書き込み操作がブロックせずにバッファー全体を書き込むことがわかります。ただし、接続から何も読み取らないように上記のサーバーを変更すると、クライアントでの最初の書き込み操作のみが128 kBを書き込み、その後のすべての書き込みは 0
を返します。
サーバーが接続から読み取っているときに出力します:
to write: 131072, written: 131072
to write: 131072, written: 131072
to write: 131072, written: 131072
...
サーバーが接続から読み取っていない場合の出力:
to write: 131072, written: 131072
to write: 131072, written: 0
to write: 131072, written: 0
...
他のヒント
私はJavaでUDPを使用してきましたが、本当に「興味深い」ものを見てきました。 Java NIO全般での完全に文書化されていない動作。何が起こっているのかを判断する最良の方法は、Javaに付属のソースを調べることです。
また、IBMのような他のJVM実装で探しているもののより良い実装が見つかるかもしれないとかなり強く賭けますが、私はそれらを自分で見ないでそれを保証することはできません。
どこにも文書化されていませんが、IIRC [1]、send()はa)提供されたバッファを完全に送信するか、b)失敗することが保証されています。送信を部分的に完了することはありません。
[1]複数のWinsock実装(Win 3.0、Win 95、Win NTなど)を記述したため、これはWinsock固有の(汎用ソケットではなく)動作になる可能性があります。
私は大きな飛躍を遂げ、Javaの基礎となるネットワークプロバイダーがCの場合と同じであると仮定します...送信コードをfor(1,100000)ループに入れると、最終的には、要求された値よりも小さい値で成功する書き込みを取得することになります。
どこにデータを送信していますか?ネットワークは、少なくともSO_SNDBUFとレシーバーのSO_RCVBUFのサイズが等しいバッファーとして機能することに注意してください。 Alexanderが述べたように、これを受信者による読み取りアクティビティに追加すると、大量のデータを取得できます。