Java NIO свойственный недостаток при чтении из одного сокета и записи в другой? - PullRequest
0 голосов
/ 30 октября 2018

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

Вещи отлично работают на полной скорости, быстрые ссылки и т. Д. Вещи ужасно терпят неудачу и потребляют массу процессора, когда одна сторона быстрее, чем другая. Вещи работают, просто работают очень неэффективно.

Пример:

Iterator keys = selector.selectedKeys().iterator();
while (keys.hasNext()) {
    SelectionKey key = (SelectionKey) keys.next();
    keys.remove();
    try {
        if (!key.isValid()) continue;
        if (key.isReadable()) read(key);
    }
    catch (Exception e) {
        e.printStackTrace();
        key.cancel();
    }
}

Вызов read происходит каждый раз, когда в сокете есть данные, которые можно прочитать. Но что, если записываемая часть этого сокета не может быть записана, потому что клиентская сторона имеет большую задержку или просто не читает данные быстро? Мы в конечном итоге зациклились с безумной скоростью, ничего не делая, пока не освободится немного более записываемый буфер:

public void read(SelectionKey key) throws IOException {
    ByteBuffer b = (ByteBuffer) buffers.get(readable); //prior buffer that may or may not have been fully used from the prior write attempt
    int bytes_read = readable.read(b);
    b.flip();
    if (bytes_read > 0 || b.position() > 0) writeable.write(b);
    b.compact();
}

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

Если бы я сделал это с потоком, не было бы никаких проблем, поскольку мы блокировали бы вызов write. Но что делать с NIO, чтобы пропустить уведомления о прочтении?

1 Ответ

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

Я понял это. Я до сих пор не нашел документов, указывающих на это, но после более подробного рассмотрения я понял, для чего предназначен сценарий OP_WRITEABLE.

Поэтому, когда записываемое возвращается, что оно приняло 0 байтов, я отменяю регистрацию OP_READ на читаемом канале и регистрируюсь для OP_WRITEABLE на записываемом канале. Как только я получаю уведомление о том, что он доступен для записи, я переключаю каналы чтения / записи, включая те, которые они зарегистрированы для удаления OP_WRITEABLE сейчас.

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

Теперь все работает нормально.

...