简体   繁体   English

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

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

My questions is more generic than the following scenario, though this covers everything needed.我的问题比以下场景更通用,尽管这涵盖了所需的一切。 It is for Java and the correct practices of socket programming.适用于 Java 和套接字编程的正确做法。

Scenario:设想:

  • One server with many clients.一台服务器有许多客户端。 Usage of non-blocking I/O非阻塞 I/O 的使用
  • The server is a client to another server.服务器是另一台服务器的客户端。 Usage of blocking I/O阻塞 I/O的使用
  • Two cases for each: in one case all data fit inside the allocated bytebuffer, in the second case they do not fit (for one iteration only, not for the lifespan of the program).每种情况有两种情况:在一种情况下,所有数据都适合分配的字节缓冲区,在第二种情况下,它们不适合(仅适用于一次迭代,不适用于程序的生命周期)。

All examples that I have found for a non-blocking I/O go something like this:我为非阻塞 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
           }
        }
    }

Does the read here reads all incoming data from that particular client and that particular request, if the buffer is large enough, or do I need to have it inside a while loop?如果缓冲区足够大,这里的读取是否会read来自该特定客户端和该特定请求的所有传入数据,或者我是否需要将它放在一个while循环中? If the buffer is not large enough?如果缓冲区不够大?

In case of a write , do I also just do socketChannel.write(buffer) and I am good to go (at least from the programs point of view)?如果是write ,我是否也只做socketChannel.write(buffer)并且我对 go 很好(至少从程序的角度来看)?

The doc here does not specify the case when all incoming data fit in the buffer. 此处的文档未指定所有传入数据都适合缓冲区的情况。 It also makes it a bit confusing when I have a blocking I/O:当我有阻塞 I/O 时,它也会让人有点困惑:

It is guaranteed, however, that if a channel is in blocking mode and there is at least one byte remaining in the buffer then this method will block until at least one byte is read.但是,可以保证,如果通道处于阻塞模式并且缓冲区中至少有一个字节剩余,则此方法将阻塞,直到读取至少一个字节。

Does this mean that here (blocking I/O) I need to read through a while loop either way (most examples that I found does this)?这是否意味着在这里(阻塞 I/O)我需要以任何一种方式read一个while循环(我发现的大多数示例都是这样做的)? What about a write operation? write操作呢?

So, to sum it up, my question is, what is the proper way to read and write the data in my scenario, from the point of view of the middle server (client to the second server)?所以,总而言之,我的问题是,从中间服务器(客户端到第二台服务器)的角度来看,在我的场景中读取和写入数据的正确方法是什么?

If you had not called configureBlocking(false) , then yes, you would use a loop to fill the buffer.如果您没有调用configureBlocking(false) ,那么是的,您将使用循环来填充缓冲区。

However… the point of a non-blocking socket is not to get hung up waiting on any one socket, since that would delay the reading from all the remaining sockets whose selected keys haven't yet been processed by your Iterator.但是......非阻塞套接字的要点是不要挂断等待任何一个套接字,因为这会延迟所有剩余 sockets 的读取,其选定的键尚未被您的迭代器处理。 Effectively, if ten clients connect, and one of them happens to have a slow connection, some or all of the others might experience the same slowness.实际上,如果十个客户端连接,其中一个碰巧连接速度很慢,那么其他一些或所有客户端可能会遇到同样的速度。

(The exact order of the selected key set is not specified. It would be unwise to look at the source of the Selector implementation class, since the lack of any guarantee of order means future versions of Java SE are permitted to change the order.) (未指定所选密钥集的确切顺序。查看 Selector 实现 class 的来源是不明智的,因为缺乏任何顺序保证意味着 Java SE 的未来版本允许更改顺序。)

To avoid waiting for any one socket, you don't try to fill up the buffer all in one go;为避免等待任何一个套接字,请不要尝试将缓冲区全部填满 go; rather, you read whatever the socket can give you without blocking, by reading only once per select() call.相反,您可以通过每次select()调用仅读取一次来读取套接字可以为您提供的任何内容而不会阻塞。

Since each ByteBuffer might hold a partial data sequence, you'll need to remember each ByteBuffer's progress for each Socket.由于每个 ByteBuffer 可能保存部分数据序列,因此您需要记住每个 Socket 的每个 ByteBuffer 的进度。 Luckily, SelectionKey has a convenient way to do that: the attachment .幸运的是,SelectionKey 有一个方便的方法来做到这一点: 附件

You also want to remember how many bytes you've read from each socket.您还想记住从每个套接字读取了多少字节。 So, now you have two things you need to remember for each socket: the byte count, and the 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);
        }

For a blocking channel, you can just use one write(buffer) call, but as you can see, the use of a blocking channel may limit the advantages of the primary server's use of non-blocking channels.对于阻塞通道,您可以只使用一次write(buffer)调用,但正如您所见,使用阻塞通道可能会限制主服务器使用非阻塞通道的优势。 It may be worth making the connection to the other server a non-blocking channel as well.将与其他服务器的连接也设为非阻塞通道可能是值得的。 That will make things more complicated, so I won't address it here unless you want me to.这会使事情变得更复杂,所以我不会在这里解决它,除非你想让我这样做。

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

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