【问题标题】:How do I limit reading a line at a time from a SocketChannel InputStream using Java NIO如何限制使用 Java NIO 从 SocketChannel InputStream 一次读取一行
【发布时间】:2011-08-17 07:01:19
【问题描述】:

我正在尝试编写 Websockets 客户端和服务器。最初连接是 HTTP,Websockets 握手使用 HTTP 标头来指示连接需要升级到新协议。

我想从 SocketChannel 读取 HTTP 标头集,如果指示升级,则切换到不同的库来处理 Websocket,并从那时起以完全不同的方式处理 SocketChannel 流,作为一组帧而不是用 \r\n 分隔的行。

我知道我可以将任意数量的字节读入 ByteBuffer,但 Websockets 帧可能已随握手一起发送,我不想在这些代码段之间传递半消耗的缓冲区。我想要的是只从套接字读取直到并包括序列“\r\n\r\n”的数据。除了我想留在 SocketChannel 对象输入流中的任何数据。

推荐的方法是什么?从 SocketChannel 获取输入流并将其包装在缓冲阅读器中?这会与 NIO 正确交互,尤其是非阻塞使用吗?一旦检测到空行,我是否可以从输入流中删除缓冲读取器,并且在将通道传递给 Websockets 代码时仍然有所有可用的帧数据?

或者也许我需要逐字节读取(或者如果某些目标“\r\n\r\n”字符出现在块的末尾,则为 4 字节块和较小的缓冲区)并建立我的这样的标题字符串。

或者,如果缓冲区是直接分配的,那么操作标记、限制和位置的某种组合可能会允许输入流取回之前读取到 ByteBuffer 中的数据。

任何建议将不胜感激。

【问题讨论】:

  • 你为什么不想传递一个半消耗缓冲区?您必须能够处理无法一次接收所有数据的情况,这会有什么不同?
  • 一个 SocketChannel 没有输入流。它有一个接收缓冲区,这是你的意思吗?
  • Peter Lawley:我不想传递一个半消耗的缓冲区,因为它增加了握手代码和 websocket 帧代码之间的耦合。它们都必须使用 SocketChannel,因此无论如何都必须通过。如果我可以避免在它们之间传递另一个对象(更不用说缓冲区的状态,比如是否需要翻转),我认为这样会更干净。
  • EJP:我相信您可以通过调用 .socket().getInputStream() 从 SocketChannel 获取输入流。这就是我的意思。
  • 但是您必须先将通道置于阻塞模式,然后在此之前从选择器中注销它,因此在单独的线程中读取,然后撤消所有这些......您也可以不完全使用 NIO。

标签: java nio readline websocket bytebuffer


【解决方案1】:

我会使用适当的面向行的阅读器(例如 LineNumberReader)来包装套接字 InputStream。在引擎盖下,这些阅读器一次读取一个字节。出于您所说的原因,我不会为此使用 BufferedReader。

【讨论】:

  • EJP:实际上,我确实提到了这一点,尽管我使用“缓冲阅读器”而不是类名。
  • user726092:感谢您的建议。我不会想到这个看似无关的选项,而且我当然不知道他们一次读取一个字节。那是便携式的吗?
【解决方案2】:

我建议使用 Apache Mina 或 Grizzly 之类的工具。两者都允许您封装问题的协议方面,因此您只需要处理可消耗的数据。

但是,如果您想要一种快速而肮脏的方式: 基本的想法是,是的,你需要在数据进入时读取它。如果它不容易使用,我通常会为选择器中的 SelectionKey 创建一些可附加的结构(简单的 StringBuilder)。每次读取后,我会将数据附加到构建器,如果您检测到可用的标头,请将其从缓冲区中切出并将其向上传递(最好在工作线程上)。继续这样做,上游的任何东西都应该能够做出相应的反应。希望对您有所帮助。

所以通常你有这样的结构:

ByteBuffer reUsableBuffer = ByteBuffer.allocateDirect(5120);
Selector selector = Selector.open();
ServerSocketChannel channel = .. // wherever you get it from 
channel.register(selector, SelectionKey.OP_ACCEPT);
Executor executor = Executors.newThreadPoolExecutor();
while(selector.isOpen()) { 
 int numKey = selector.select();
 for (SelectionKey key: selector.selectedKeys()) {
    if (key.isAcceptable()) {
             /// Sort of included for completeness but you get the idea
           ServerSocketChannel server = (ServerSocketChannel)key.channel();
           SocketChannel channel = server.accept();
           channel.register(selector, SelectionKey.OP_READ | Selection.OP_WRITE, new StringBuilder());
    }    if (key.isReadable()) {
          // READ the data
          reUsableBuffer.clear();
          // You have to keep track of previous state.
          // NIO makes no guarantees of anything
          StringBuilder builder = key.attachment();
          SocketChannel socketChannel = (SocketChannel)key.channel();
          int readCount = socketChannel.read(reUsableBuffer);
          if (readCount > 0) {
             reUsableBuffer.flip();
             byte[] subStringBytes = new byte[readCount];
             reUsableBuffer.read(subStringBytes);
             // Assuming ASCII (bad assumption but simplifies the example)
             builder.append(new String(substringBytes));

             Command[] commands = removeCommands(builder);
             // Deal with your commands in some async manor defined by you
             executor.execute(new Task(commands));
          }
        }
        selector.selectedKeys().clear(); } ....

    }   

//
// Parse out the commands and return them, also remove traces of them in the
// the builder, such that for a string, "COMMAND, COMMAND, COM"
// an array of 2 should be returned with a left over buffer of "COM"
public Command[] parseCommands(StringBuilder s) { ... }

【讨论】:

  • 感谢您对 Mina 或 Grizzly 的建议。最终,我想让库的传输部分可替换,以便它可以在各种环境中使用。但首先,我正在寻找不依赖于其他服务器框架的简单工具。
  • 至于您关于可附加结构的想法,这听起来更像是需要转换的上下文,这是我不喜欢传递 ByteBuffer 的原因。或者您是否建议使用一个单独的线程来构建这些结构,然后将它们传递给另一个线程,将它们转换为握手或帧?
  • Hey sockets-to-me,所以包含了一些粗略的代码(尚未编译我们尝试运行它(不包括错误处理)),这应该让您了解我的想法我在暗示。在 NIO 中,您基本上是在管理状态机,并且您必须固有地管理状态:) 因此,如果没有将 ByteBuffer 附加到键或其他东西来回溯先前的状态,您将丢失数据。如果您想使用流并执行完整的 readLine() ,那么它将阻塞,如果使用选择器在您的线程上,那么这将降低您服务连接的整体能力。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-10-03
  • 1970-01-01
  • 2016-04-29
  • 2011-08-10
相关资源
最近更新 更多