为什么即使在非阻塞套接字上,SocketChannel 写入也总是完成全部量



在Windows上使用Sun Java VM 1.5或1.6,我连接了一个非阻塞套接字。然后,我用要输出的消息填充ByteBuffer,并尝试write()到套接字通道。

如果要写入的数量大于套接字的 TCP 输出缓冲区中的空间量,我希望写入仅部分完成(这是我直观地期望的,这也是我对文档的理解),但事实并非如此。write()似乎总是返回报告写入的全部内容,即使它是几兆字节(套接字的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);
  }
}

如果运行上述服务器,然后让上述客户端尝试写入 10 个 128 kB 数据块,您将看到每个写入操作都会写入整个缓冲区而不会阻塞。但是,如果修改上述服务器以不从连接读取任何内容,您将看到只有客户端上的第一个写入操作将写入 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附带的源代码。

我也敢打赌,你可能会在任何其他JVM实现中找到你正在寻找的更好的实现,比如IBM的,但我不能保证不亲自看看它们。

我将做出一个很大的信念飞跃,并假设 Java 的底层网络提供程序与 C 相同......O/S 为每个插槽分配的不仅仅是SO_SNDBUF。我敢打赌,如果您将发送代码放入for(1,100000)循环中,您最终会得到一个成功的写入,其值小于请求的值。

你真的应该看看像MINA或Grizzly这样的NIO框架。 我在企业聊天服务器中使用了MINA,取得了巨大的成功。 它也用于Openfire聊天服务器。 Grizzly用于Sun的JavaEE实现。

您将数据发送到何处? 请记住,网络充当缓冲区,其大小至少等于您的SO_SNDBUF加上接收器的SO_RCVBUF。 如亚历山大所述,将其添加到接收器的读取活动中,您可以吸收大量数据。

我在任何地方都找不到它的文档,但是IIRC[1],send()保证a)完全发送提供的缓冲区,或者b)失败。它永远不会部分完成发送。

[1] 我已经编写了多个Winsock实现(用于Win 3.0,Win 95,Win NT等),因此这可能是Winsock特定的(而不是通用套接字)行为。

最新更新