提问人:sleekster 提问时间:8/6/2023 最后编辑:Peter Mortensensleekster 更新时间:8/7/2023 访问量:86
Java NIO:如何多次读取缓冲区?
Java NIO: How can I read into a buffer possibly multiple times?
问:
我正在做一个 Java 项目。 我有以下代码,它是选择器的 while 循环的一部分。
响应应该小于,并以换行符结尾。
但据我了解,有可能(即使我没有观察到这一点,因为消息很小)只有部分消息可以用于.256
OP_READ
正如你所看到的,在读取的情况下,我正在分配一个 ,读取它并转换为字符串。Buffer
处理部分消息的适当方法是什么?理想情况下,我想在阅读之前等待整个消息准备就绪。一种幼稚的方法是在连接关闭之前进行 and 读取。Map<SocketChannel, Buffer>
if (key.isReadable()) {
SocketChannel socketChannel = (SocketChannel) key.channel();
// Read the response from the server
ByteBuffer buffer = ByteBuffer.allocate(256);
int bytesRead = socketChannel.read(buffer);
if (bytesRead == -1) {
// The server closed the connection
socketChannel.close();
key.cancel();
channels.remove(socketChannel);
} else {
buffer.flip();
byte[] responseBytes = new byte[buffer.remaining()];
buffer.get(responseBytes);
String response = new String(responseBytes);
if (response.endsWith("\n")) {
socketChannel.close();
key.cancel();
channels.remove(socketChannel);
responses.add(response);
System.out.println(response);
} else {
System.out.println("need to read more!!!!");
}
}
}
答:
处理部分消息的适当方法是什么?
是的,确实,这种情况可能会发生,异步系统需要处理这种“我们已经完成了一半”的概念。
作为一个广泛的概述,嗯,你说了:缓冲区。
如果一条消息已经接收了一半,那么到目前为止您收到的数据只需要存储在某个地方。当你不使用异步,而是使用线程时(你可能应该这样做 - 异步的性能改进几乎总是比粉丝异步博客倾向于兜售的要少得多,以及 Project Loom - 这是 OpenJDK 团队,致力于添加新的语言功能和库以改进 JDK 中的线程概念,其中大部分已经是 JDK20 的一部分, 添加了虚拟线程的概念,可以关闭剩余的一点距离)——所有这些“状态”(例如“半条消息”)实际上都自动存储在堆栈上。使用异步,您必须手动管理所有这些东西。
存储这些东西的预期位置是在缓冲区中 - 而朋友旨在让您有效地做到这一点。ByteBuffer
通过聊天应用程序,以下是使用低级异步 NIO 基元的工作方式:、通道和选择器。ByteBuffer
情境:您是一个聊天系统,可以让 20 人在一个群组中一起聊天。所有 20 个客户端连接一次并保持长时间运行的连接;客户端可以发送一条消息“SEND stuff to show to show to the other 19 participant goes here”,这将导致其他 19 个连接将一些数据从服务器传输到这 19 个客户端,并带有“SHOW stuff to show to the other 19 participant go here”。
我们目前的状态是所有 20 个连接都发送了它们需要发送的所有内容;他们现在都在等待有人输入一些东西。
这 20 个连接的配置方式都相同
- 您在 nio 选择器中注册,您目前对发送数据不感兴趣(因为没有什么可发送的),但您对读取数据感兴趣。然后你告诉你的选择器去睡觉,如果有什么“有趣”的事情发生,就叫醒你。你为所有 20 个连接注册这个,有趣的定义是:有一些东西要读 - 现在你的系统进入睡眠状态,因为真的没有什么可做的。
- Jane 输入了一些东西,因此选择器唤醒了线程并说:给你,这个套接字(Jane 的套接字)现在能够做一些你告诉我你感兴趣的事情:你可以从中读取。
- 您要求该套接字填充您的缓冲区。缓冲区充满了“SEND Hello ever”,仅此而已。Jane 显然还没有完成输入——数据不会以换行符结尾。
- 你。。。只需返回睡眠状态 - 无需更改“您感兴趣的事件”列表:如果 20 个套接字中的任何一个有数据要读取,您仍然感兴趣,并且您仍然不感兴趣 20 个套接字中的任何一个是否能够接收写入。这非常重要;如果你说你对此感兴趣,选择器会持续唤醒,你的 CPU 会变为 100%(因为所有 20 个插槽都能够发送数据。我们不在乎,因为还没有任何东西可以发送)。Jane 仍在打字(或者她已经完成了,但网络连接速度很慢),所以选择器会重新进入睡眠状态,因为您感兴趣的 20 件事都不可用。Jane 的插座还没有准备好阅读 - 你阅读了到目前为止的所有内容。那个半生不熟的消息挂在.
ByteBuffer
- 最终,你的 NIO 选择器再次醒来,并告诉你,再一次,Jane 的套接字还有更多要读的。你的代码应该告诉那个套接字:这里有一个字节缓冲区(它的位置在它的一半,就像在里面一样),请尽可能地填充它。插座写字,我们第二次做这个歌舞套路。
SEND Hello ever
ybody, how's li
- NIO 选择器第三次唤醒,你做同样的事情,套接字向它写入“fe!\n”并完成。你做了同样的事情,你已经做过两次了,并检查你的缓冲区是否包含一整条消息,这一次,它确实如此(你看到那个换行符,你就知道它的含义了)。
- 您现在更新选择器的“我对什么感兴趣”列表(选择器键): 您现在对 20 个套接字中的 19 个套接字的“准备写入”感兴趣,假设在这个系统中,jane 在数据发布之前无法发送更多数据(我不会真正以这种方式设计它,但为了示例的缘故), 所以你更新了 Jane 的套接字,你对任何事情都不感兴趣。即使有更多数据要读取,也不会。它只会停留在网络缓冲区中。
- 你告诉你的选择器睡觉。该调用会立即结束,因为您对某些事件感兴趣,即:所有这 19 个套接字都已准备好发送,并且您说您对此感兴趣。
- 你浏览了你的清单,“我感兴趣的东西已经准备好了”,然后你开始发送“SHOW,大家好,生活怎么样!然而,虽然你能够一次性发送第一个套接字的整个东西,但第二个套接字在它返回之前只会发出“SHOW Hello everybod”——它不能再发送了(出于某种原因,这个人在一个很小的网络缓冲区上,谁知道呢)。你做同样的事情:'y, how's life!\n' 仍然在 中,你仍然需要将其发送到套接字 2。
ByteBuffer
- 您仍然对套接字 2 的“准备写入数据”事件感兴趣。最终,你通过所有 19 个套接字,对于其中两个,你无法将整个信息传出去。对于这 2 个,您仍然对“准备发送”感兴趣,对于其他 17 个,您清除这些密钥(您对它们已准备好发送不感兴趣)。最终,你再次醒来,那 2 个准备发送更多;您最终会获得所有数据。在您重新启用的过程中的某个地方,或者对于一个好的聊天应用程序,在所有这些过程中,它一直处于启用状态,您也有兴趣从他们那里阅读,以防有人在不是所有内容都出来的情况下输入。
这里发生了各种非常复杂的毛茸茸的情况,你必须处理所有这些情况。例如,想象一下人们如此疯狂地打字,而 Jane 的网络连接是如此不稳定,以至于人们只是设法以比 Jane 的网络所能处理的更高的数量发送数据。包含仍然需要发送给 Jane 的所有数据的数据只会越来越大(每次有人输入消息时,所有 20 个“要发送的东西”缓冲区都会被编辑以附加这些消息。它们不时被“后台处理”(仍然要发送的数据被移到前面,因此在末尾有空间添加更多“也发送这个”)。ByteBuffer
最终,Jane 的缓冲区完全满了。当然,你可以编写一些代码来扩展它(创建一个新的、更大的代码,将原来的较小数据复制到它上面,用这个新的更大的字节缓冲区替换哈希图中的条目),但如果你这样做,最终你会有一个 1 GB 大的“发送”字节缓冲区。在某些时候,你必须做出决定。这是你的应用程序,没有一个标准的答案。您是否要阻止其他 19 个再键入,直到 jane 的缓冲区中有足够的空间?是否要断开 Jane 的连接?你想停止把消息放在 Jane 的缓冲区里吗(她最终不会看到这些东西;一旦她的网络赶上了,她就会看到新消息,但这些消息会丢失给她)。是否要将它们缓存到磁盘?你想放弃 jane 并告诉其他人系统正在这样做吗?无穷无尽的答案 - 你编写代码来做你认为适合你的应用程序的事情。ByteBuffer
如果这听起来非常复杂,那是因为它确实如此!优点是单个线程可以同时读取和写入所有 20 个连接,尽管性能提升很小,而且可能是负的(线程不是什么危险的妖怪;20 个线程不算什么,它们的开销以及它们之间的切换是无限的。上面的 NIO 设置也有开销 - 所有这些缓冲区)。
有各种像 Grizzly 这样的框架应该可以更轻松地编写这样的应用程序。
评论