Как ограничить чтение строки за раз из SocketChannel InputStream с помощью Java NIO - PullRequest
1 голос
/ 03 мая 2011

Я пытаюсь написать клиент и сервер Websockets.Первоначально это HTTP-соединение, а Websockets Handshake использует HTTP-заголовки, чтобы указать, что для подключения необходимо обновить новый протокол.

Я хочу прочитать набор HTTP-заголовков из SocketChannel и, еслиуказано обновление, переключитесь на другую библиотеку для обработки веб-сокетов и с этого момента обрабатывайте потоки SocketChannel совершенно по-разному, как набор кадров, а не строк, разделенных \ r \ n.

Я знаю, что могучитать произвольное количество байтов в ByteBuffer, но кадр Websockets мог быть отправлен с рукопожатием, и я не хочу выделять частично использованные буферы между этими разделами кода.То, что я хочу, это читать из сокета только данные до и включая последовательность "\ r \ n \ r \ n".Любые данные, которые я хочу оставить во входном потоке объекта SocketChannel.

Каков рекомендуемый способ сделать это?Получить поток ввода из SocketChannel и обернуть его в буфере чтения?Будет ли это правильно взаимодействовать с NIO, особенно с неблокирующим использованием?Могу ли я удалить буферизованный считыватель из входного потока, как только будет обнаружена пустая строка, и при этом все данные кадра доступны при передаче канала к коду Websockets?

Или, возможно, мне нужно читать побайтовобайт (или 4-байтовые чанки с меньшими буферами, если некоторые из целевых символов "\ r \ n \ r \ n" появляются в конце чанка) и таким образом создают строки моего заголовка.

Или, может бытьнекоторая комбинация манипулирования меткой, лимитом и позицией позволила бы входному потоку вернуть данные, которые он ранее считал в ByteBuffer, при условии, что буфер был выделен напрямую.

Любая рекомендация будет принята с благодарностью.

Ответы [ 2 ]

3 голосов
/ 03 мая 2011

Я бы порекомендовал использовать что-то вроде Apache Mina или Grizzly. И то, и другое позволяет вам инкапсулировать аспект вашей проблемы, связанный с протоколом, поэтому вам нужно только обрабатывать расходуемые данные.

Однако, если вы хотите быстрый и грязный путь: Однако основная идея заключается в том, что вам нужно читать данные по мере их поступления. Если их нелегко использовать, я обычно создаю некоторую добавляемую структуру (StringBuilder для чего-то простого) для SelectionKey, который находится в селекторе. После каждого чтения я добавляю данные в компоновщик, и если вы обнаружите пригодный для использования заголовок, вырежьте его из буфера и передайте по потоку (предпочтительно в рабочем потоке). Продолжайте делать это, и все, что находится вверх по течению, должно реагировать соответствующим образом. Надеюсь, это поможет.

Так что обычно у вас есть такая структура:

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) { ... }
0 голосов
/ 03 мая 2011

Я бы обернул сокет InputStream соответствующим читателем, ориентированным на строки, например LineNumberReader. Под капотом эти читатели читают байт за раз. Я бы не стал использовать BufferedReader для этого по той причине, что вы указали.

...