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

我的问题比以下场景更通用,尽管这涵盖了所需的一切。这是针对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?如果缓冲区不够大?

如果是 a write,我也可以这样做吗socketChannel.write(buffer)(至少从程序的角度来看)?

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

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

这是否意味着这里(阻塞 I/O)我需要以任何一种方式read通过while循环(我发现的大多数示例都是这样做的)?做手术怎么办write

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


红糖糍粑
浏览 184回答 1
1回答

白板的微信

如果您没有调用configureBlocking(false),那么是的,您将使用循环来填充缓冲区。然而……非阻塞套接字的要点是不要挂起等待任何一个套接字,因为这会延迟从所有剩余套接字(其选定的键尚未被迭代器处理)的读取。实际上,如果十个客户端连接,并且其中一个碰巧连接速度较慢,则其他部分或全部客户端可能会遇到同样的缓慢情况。(未指定所选键集的确切顺序。查看 Selector 实现类的源代码是不明智的,因为缺乏任何顺序保证意味着 Java SE 的未来版本可以更改顺序。)为了避免等待任何一个套接字,您不要尝试一次性填满缓冲区;相反,您可以在不阻塞的情况下读取套接字可以提供的任何内容,每次调用只读取一次select()。由于每个 ByteBuffer 可能保存部分数据序列,因此您需要记住每个 Socket 的每个 ByteBuffer 的进度。幸运的是,SelectionKey 有一个方便的方法来做到这一点:附件。您还想记住从每个套接字读取了多少字节。因此,现在您需要记住每个套接字的两件事:字节数和字节缓冲区。class ReadState {&nbsp; &nbsp; final ByteBuffer buffer = ByteBuffer.allocate(BUFFER_SIZE);&nbsp; &nbsp; long count;}while (true) {&nbsp; &nbsp; // ...&nbsp; &nbsp; &nbsp; &nbsp; if (key.isAcceptable()) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; SocketChannel socketChannel = serverSocketChannel.accept();&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; socketChannel.configureBlocking(false);&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // Attach the read state for this socket&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // to its corresponding key.&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; socketChannel.register(selector, SelectionKey.OP_READ,&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; new ReadState());&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; if (key.isReadable()) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; SocketChannel socketChannel = (SocketChannel) key.channel();&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ReadState state = (ReadState) key.attachment();&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ByteBuffer buffer = state.buffer;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; state.count += socketChannel.read(buffer);&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (state.count >= DATA_LENGTH) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; socketChannel.close();&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; buffer.flip();&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // Caution: The speed of this connection will limit your ability&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // to process the remaining selected keys!&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; anotherServerChannel.write(buffer);&nbsp; &nbsp; &nbsp; &nbsp; }对于阻塞通道,您可以只使用一次write(buffer)调用,但是正如您所看到的,使用阻塞通道可能会限制主服务器使用非阻塞通道的优势。将与其他服务器的连接也设置为非阻塞通道可能是值得的。这会让事情变得更加复杂,所以除非你希望我这样做,否则我不会在这里解决这个问题。
打开App,查看更多内容
随时随地看视频慕课网APP

相关分类

Java