Правильный способ чтения (записи) через SocketChannel - PullRequest
2 голосов
/ 31 октября 2019

Мои вопросы являются более общими, чем следующий сценарий, хотя это охватывает все необходимое. Это для Java и правильной практики программирования сокетов.

Сценарий:

  • Один сервер с множеством клиентов. Использование неблокирующего ввода / вывода
  • Сервер является клиентом для другого сервера. Использование блокирования ввода / вывода
  • Два случая для каждого: в одном случае все данные помещаются в выделенный байт-буфер, во втором случае они не подходят (только для одной итерации, не длявремя жизни программы).

Все примеры, которые я нашел для неблокирующего ввода / вывода, выглядят примерно так:

InetAddress host = InetAddress.getByName("localhost");
Selector selector = Selector.open();
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.configureBlocking(false);
serverSocketChannel.bind(new InetSocketAddress(host, 1234));
serverSocketChannel.register(selector, SelectionKey. OP_ACCEPT);
while (true) {
   if (selector.select() <= 0)
       continue;
   Set<SelectionKey> selectedKeys = selector.selectedKeys();
   Iterator<SelectionKey> iterator = selectedKeys.iterator();
   while (iterator.hasNext()) {
       key = (SelectionKey) iterator.next();
       iterator.remove();
       if (key.isAcceptable()) {
           SocketChannel socketChannel = serverSocketChannel.accept();
           socketChannel.configureBlocking(false);
           socketChannel.register(selector, SelectionKey.OP_READ);
           // Do something or do nothing
       }
       if (key.isReadable()) {
           SocketChannel socketChannel = (SocketChannel) key.channel();
           ByteBuffer buffer = ByteBuffer.allocate(BUFFER_SIZE);
           socketChannel.read(buffer);
           // Something something dark side
           if (result.length() <= 0) {
               sc.close();
               // Something else
           }
        }
    }

Здесь readчитает все входящие данные от этого конкретного клиента и этого конкретного запроса, если буфер достаточно большой, или мне нужно, чтобы он был внутри цикла while? Если буфер недостаточно велик?

В случае write, я тоже просто делаю socketChannel.write(buffer), и мне хорошо идти (по крайней мере, с точки зрения программ)?

В документе здесь не указан случай, когда все входящие данные помещаются в буфер. Это также немного сбивает с толку, когда у меня блокирующий ввод / вывод:

Однако гарантируется, что если канал находится в режиме блокировки и в буфере остается хотя бы один байттогда этот метод будет блокироваться, пока не будет прочитан хотя бы один байт.

Означает ли это, что здесь (блокируя ввод / вывод) мне нужно read через цикл while в любом случае (большинство примеровчто я нашел делает это)? А как насчет операции write?

Итак, чтобы подвести итог, мой вопрос: как правильно читать и записывать данные в моем сценарии с точки зрения промежуточного сервера (клиент на второй сервер)?

1 Ответ

0 голосов
/ 31 октября 2019

Если бы вы не вызвали configureBlocking(false), тогда да, вы бы использовали цикл для заполнения буфера.

Однако ... смысл неблокирующего сокета не в том, чтобы зависнуть в ожидании какого-либоодин сокет, так как это приведет к задержке чтения из всех остальных сокетов, чьи выбранные ключи еще не были обработаны вашим итератором. Фактически, если десять клиентов соединяются, и у одного из них медленное соединение, некоторые или все другие могут испытывать ту же медлительность.

(Точный порядок выбранного набора ключей не указан. было бы неразумно смотреть на источник класса реализации Selector, поскольку отсутствие какой-либо гарантии порядка означает, что будущие версии Java SE могут изменять порядок.)

Чтобы избежать ожидания какого-либо одного сокета,Вы не пытаетесь заполнить буфер все за один раз;скорее, вы читаете все, что сокет может дать вам без блокировки, читая только один раз за select() вызов.

Поскольку каждый ByteBuffer может содержать частичную последовательность данных, вам нужно будет помнить прогресс каждого ByteBuffer для каждогоРазъем. К счастью, у SelectionKey есть удобный способ сделать это: вложение .

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

class ReadState {
    final ByteBuffer buffer = ByteBuffer.allocate(BUFFER_SIZE);
    long count;
}

while (true) {

    // ...

        if (key.isAcceptable()) {
            SocketChannel socketChannel = serverSocketChannel.accept();
            socketChannel.configureBlocking(false);

            // Attach the read state for this socket
            // to its corresponding key.
            socketChannel.register(selector, SelectionKey.OP_READ,
                new ReadState());
        }

        if (key.isReadable()) {
            SocketChannel socketChannel = (SocketChannel) key.channel();
            ReadState state = (ReadState) key.attachment();
            ByteBuffer buffer = state.buffer;
            state.count += socketChannel.read(buffer);

            if (state.count >= DATA_LENGTH) {
                socketChannel.close();
            }

            buffer.flip();

            // Caution: The speed of this connection will limit your ability
            // to process the remaining selected keys!
            anotherServerChannel.write(buffer);
        }

Для канала блокировки вы можете просто использовать один вызов write(buffer), но, как выМожно видеть, что использование канала блокировки может ограничить преимущества использования неблокирующими каналами основного сервера. Возможно, стоит сделать подключение к другому серверу неблокирующим каналом. Это усложнит ситуацию, поэтому я не буду говорить об этом здесь, если вы не хотите, чтобы я это сделал.

...