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

时间:2020-03-06 14:58:49  来源:igfitidea点击:

使用Windows上的Sun Java VM 1.5或者1.6,我连接了一个非阻塞套接字。然后,我将要输出的消息填充到" ByteBuffer"中,并尝试对SocketChannel进行" write()"操作。

我希望,如果要写入的数量大于套接字的TCP输出缓冲区中的空间数量,写入将仅部分完成(这是我的直觉期望,这也是我对文档的理解),但是那不是什么发生。即使是几兆字节(套接字的SO_SNDBUF为8KB,比我的多兆字节输出消息少很多),write()似乎总是返回报告已写入的全部数量。

这里的问题是我无法测试处理部分写入输出的代码(将兴趣集" WRITE"注册到选择器,并执行" select()"以等待其余部分可以写入) ),因为这种情况似乎从未发生过。我不明白什么?

解决方案

我将信心十足,并假定Java的基础网络提供程序与C相同。...O / S为每个套接字分配的不仅仅是" SO_SNDBUF"。我敢打赌,如果将发送代码放入for(1,100000)循环中,则最终我们将获得成功的写入,且写入的值小于请求的值。

我一直在用Java进行UDP处理,并且在Java NIO中看到了一些真正"有趣"且完全未记录的行为。确定正在发生的事情的最好方法是查看Java附带的源代码。

我也非常打赌,我们可能会在其他JVM实现(例如IBM的实现)中找到所需的更好的实现,但是我不能保证自己不用自己看一下。

我设法重现了一种可能与我们相似的情况。我认为,具有讽刺意味的是,收件人使用数据的速度比编写数据的速度快。

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
...

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

我们要在哪里发送数据?请记住,网络充当的缓冲区的大小至少等于SO_SNDBUF加上接收者的SO_RCVBUF。如亚历山大所说,将其添加到接收器的阅读活动中,我们可以吸收大量数据。