【问题标题】:Proper way to read (write) through a SocketChannel通过 SocketChannel 读取(写入)的正确方法
【发布时间】:2020-02-26 08:19:32
【问题描述】:

我的问题比以下情况更笼统,尽管这涵盖了所需的一切。 是针对Java和socket编程的正确做法。

场景:

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

我为非阻塞 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
           }
        }
    }

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

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

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

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

这是否意味着在这里(阻塞 I/O)我需要通过while 循环来read(我发现的大多数示例都是这样做的)? write 操作呢?

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

【问题讨论】:

    标签: java loops nonblocking writing socketchannel


    【解决方案1】:

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

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

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

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

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

    您还想记住从每个套接字读取了多少字节。因此,现在您需要为每个套接字记住两件事:字节数和 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) 调用,但正如您所见,使用阻塞通道可能会限制主服务器使用非阻塞通道的优势。将与其他服务器的连接也设为非阻塞通道可能是值得的。这会让事情变得更复杂,所以我不会在这里解决它,除非你想让我这样做。

    【讨论】:

    • 我知道有什么区别和好处(阻塞/非阻塞),我只是在询问每种情况下正确的读写方式。缓冲区也可以很大,适合所有东西,也可以不适合。我想要 2(读/写)* 2(阻塞/非阻塞)* 2(缓冲区是否足够大)= 8 种不同的情况。我是否需要循环(单读/单写,例如获取一个客户端请求/发送一个服务器请求)?
    • 我试图解决这个问题。答案是否定的,你不应该使用循环。这样做会破坏非阻塞套接字通道的好处。阻塞写入不需要循环,因为it will always write the entire buffer.
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-08-21
    • 2013-07-21
    • 1970-01-01
    • 1970-01-01
    • 2016-10-19
    • 1970-01-01
    相关资源
    最近更新 更多