Java 通过SocketChannel读取(写入)的正确方法

Java 通过SocketChannel读取(写入)的正确方法,java,loops,nonblocking,writing,socketchannel,Java,Loops,Nonblocking,Writing,Socketchannel,我的问题比下面的场景更一般,尽管这涵盖了所需的一切。 它适用于Java和正确的套接字编程实践 场景: 一台服务器和多个客户端。非阻塞I/O的使用 服务器是另一台服务器的客户端。阻塞I/O的使用 每种情况下有两种情况:一种情况下,所有数据都适合分配的bytebuffer,另一种情况下,它们不适合(仅适用于一次迭代,而不适用于程序的生命周期) 我发现的所有非阻塞I/O示例如下所示: InetAddress host = InetAddress.getByName("localhost"); Se

我的问题比下面的场景更一般,尽管这涵盖了所需的一切。 它适用于Java和正确的套接字编程实践

场景:

  • 一台服务器和多个客户端。非阻塞I/O的使用
  • 服务器是另一台服务器的客户端。阻塞I/O的使用
  • 每种情况下有两种情况:一种情况下,所有数据都适合分配的bytebuffer,另一种情况下,它们不适合(仅适用于一次迭代,而不适用于程序的生命周期)
我发现的所有非阻塞I/O示例如下所示:

InetAddress host = InetAddress.getByName("localhost");
Selector selector = Selector.open();
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.configureBlocking(false);
serverSocketChannel.bind(new InetSocketAddress(host, 1234));
serverSocketChannel.register(selector, SelectionKey. OP_ACCEPT);
while (true) {
   if (selector.select() <= 0)
       continue;
   Set<SelectionKey> selectedKeys = selector.selectedKeys();
   Iterator<SelectionKey> iterator = selectedKeys.iterator();
   while (iterator.hasNext()) {
       key = (SelectionKey) iterator.next();
       iterator.remove();
       if (key.isAcceptable()) {
           SocketChannel socketChannel = serverSocketChannel.accept();
           socketChannel.configureBlocking(false);
           socketChannel.register(selector, SelectionKey.OP_READ);
           // Do something or do nothing
       }
       if (key.isReadable()) {
           SocketChannel socketChannel = (SocketChannel) key.channel();
           ByteBuffer buffer = ByteBuffer.allocate(BUFFER_SIZE);
           socketChannel.read(buffer);
           // Something something dark side
           if (result.length() <= 0) {
               sc.close();
               // Something else
           }
        }
    }
InetAddress host=InetAddress.getByName(“localhost”);
选择器=选择器。打开();
ServerSocketChannel ServerSocketChannel=ServerSocketChannel.open();
serverSocketChannel.configureBlocking(false);
绑定(新的InetSocketAddress(主机,1234));
serverSocketChannel.register(选择器,SelectionKey.OP_ACCEPT);
while(true){

如果(selector.select()如果您没有调用
configureBlocking(false)
,那么是的,您将使用一个循环来填充缓冲区

但是…非阻塞套接字的要点是不要挂起等待任何一个套接字,因为这会延迟从所有剩余套接字读取的数据,这些剩余套接字的选定密钥尚未被迭代器处理。实际上,如果有十个客户端连接,其中一个连接速度较慢,则其他一些或所有客户端都会失败我们也会经历同样的缓慢

(未指定所选键集的确切顺序。查看选择器实现类的源代码是不明智的,因为缺少任何顺序保证意味着允许Java SE的未来版本更改顺序。)

为了避免等待任何一个套接字,您不会尝试一次性填满缓冲区;相反,您可以通过每次
select()
调用只读取一次套接字提供给您的内容而不阻塞

由于每个ByteBuffer可能包含一部分数据序列,因此您需要记住每个套接字的每个ByteBuffer的进度

您还需要记住从每个套接字读取的字节数。因此,现在您需要记住每个套接字的两件事:字节计数和字节缓冲

class ReadState {
    final ByteBuffer buffer = ByteBuffer.allocate(BUFFER_SIZE);
    long count;
}

while (true) {

    // ...

        if (key.isAcceptable()) {
            SocketChannel socketChannel = serverSocketChannel.accept();
            socketChannel.configureBlocking(false);

            // Attach the read state for this socket
            // to its corresponding key.
            socketChannel.register(selector, SelectionKey.OP_READ,
                new ReadState());
        }

        if (key.isReadable()) {
            SocketChannel socketChannel = (SocketChannel) key.channel();
            ReadState state = (ReadState) key.attachment();
            ByteBuffer buffer = state.buffer;
            state.count += socketChannel.read(buffer);

            if (state.count >= DATA_LENGTH) {
                socketChannel.close();
            }

            buffer.flip();

            // Caution: The speed of this connection will limit your ability
            // to process the remaining selected keys!
            anotherServerChannel.write(buffer);
        }

对于阻塞通道,您可以只使用一个
写入(缓冲区)
调用,但正如您所看到的,使用阻塞通道可能会限制主服务器使用非阻塞通道的优势。将与其他服务器的连接设置为非阻塞通道也可能是值得的。这将使事情更加复杂,因此除非您要我这样做,否则我不会在这里讨论它。

我知道什么是非阻塞通道区别和好处(阻塞/非阻塞),我只是问每种情况下读写的正确方式。缓冲区也可以很大,适合所有情况,或者不适合。我想要2(读/写)*2(阻塞/非阻塞)*2(缓冲区足够大或不够大)=8种不同情况。我需要循环吗(单读/单写,例如获取一个客户端请求/发送一个服务器请求)?我试图回答这个问题。答案是否定的,你不应该使用循环。这样做会破坏非阻塞套接字通道的好处。阻塞写入不需要循环,因为