1

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

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

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

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

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

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

任何建议将不胜感激。

4

2 回答 2

3

我建议使用 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) { ... }
于 2011-05-02T21:55:31.503 回答
-1

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

于 2011-05-02T21:52:41.920 回答