1

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 バイトのチャンク) を読み取り、ヘッダー文字列を構築する必要があるかもしれません。仕方。

あるいは、バッファが直接割り当てられていれば、mark、limit、および position の操作の組み合わせによって、入力ストリームが以前に 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

I would wrap the socket InputStream with an appropriate line oriented reader, such as LineNumberReader. Under the hood these readers read a byte at a time. I would not use a BufferedReader for this, for the reason you state.

于 2011-05-02T21:52:41.920 に答える