繁体   English   中英

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

[英]Proper way to read (write) through a SocketChannel

我的问题比以下场景更通用,尽管这涵盖了所需的一切。 适用于 Java 和套接字编程的正确做法。

设想:

  • 一台服务器有许多客户端。 非阻塞 I/O 的使用
  • 服务器是另一台服务器的客户端。 阻塞 I/O的使用
  • 每种情况有两种情况:在一种情况下,所有数据都适合分配的字节缓冲区,在第二种情况下,它们不适合(仅适用于一次迭代,不适用于程序的生命周期)。

我为非阻塞 I/O go 找到的所有示例都是这样的:

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

如果缓冲区足够大,这里的读取是否会read来自该特定客户端和该特定请求的所有传入数据,或者我是否需要将它放在一个while循环中? 如果缓冲区不够大?

如果是write ,我是否也只做socketChannel.write(buffer)并且我对 go 很好(至少从程序的角度来看)?

此处的文档未指定所有传入数据都适合缓冲区的情况。 当我有阻塞 I/O 时,它也会让人有点困惑:

但是,可以保证,如果通道处于阻塞模式并且缓冲区中至少有一个字节剩余,则此方法将阻塞,直到读取至少一个字节。

这是否意味着在这里(阻塞 I/O)我需要以任何一种方式read一个while循环(我发现的大多数示例都是这样做的)? write操作呢?

所以,总而言之,我的问题是,从中间服务器(客户端到第二台服务器)的角度来看,在我的场景中读取和写入数据的正确方法是什么?

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

但是......非阻塞套接字的要点是不要挂断等待任何一个套接字,因为这会延迟所有剩余 sockets 的读取,其选定的键尚未被您的迭代器处理。 实际上,如果十个客户端连接,其中一个碰巧连接速度很慢,那么其他一些或所有客户端可能会遇到同样的速度。

(未指定所选密钥集的确切顺序。查看 Selector 实现 class 的来源是不明智的,因为缺乏任何顺序保证意味着 Java SE 的未来版本允许更改顺序。)

为避免等待任何一个套接字,请不要尝试将缓冲区全部填满 go; 相反,您可以通过每次select()调用仅读取一次来读取套接字可以为您提供的任何内容而不会阻塞。

由于每个 ByteBuffer 可能保存部分数据序列,因此您需要记住每个 Socket 的每个 ByteBuffer 的进度。 幸运的是,SelectionKey 有一个方便的方法来做到这一点: 附件

您还想记住从每个套接字读取了多少字节。 因此,现在您需要为每个套接字记住两件事:字节数和 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);
        }

对于阻塞通道,您可以只使用一次write(buffer)调用,但正如您所见,使用阻塞通道可能会限制主服务器使用非阻塞通道的优势。 将与其他服务器的连接也设为非阻塞通道可能是值得的。 这会使事情变得更复杂,所以我不会在这里解决它,除非你想让我这样做。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM