Java NIO ( Java NIO ):Быстрая отправка больших сообщений приводит к сокращению пакетов и потере данных

StackOverflow https://stackoverflow.com/questions/499070

Вопрос

У меня неприятная проблема, из-за которой отправка нескольких больших сообщений в быстрой последовательности с сервера Java (NIO) (под управлением Linux) клиенту приведет к усечению пакетов.Сообщения должны быть большими и отправляться очень быстро, чтобы возникла проблема.Вот в основном, что делает мой код (не фактический код, но более или менее то, что происходит):

//-- setup stuff: --
Charset charset = Charset.forName("UTF-8");
CharsetEncoder encoder = charset.newEncoder();
String msg = "A very long message (let's say 20KB)...";

//-- inside loop to handle incoming connections: --
ServerSocketChannel ssc = (ServerSocketChannel)key.channel();
SocketChannel sc = ssc.accept();
sc.configureBlocking(false);
sc.socket().setTcpNoDelay(true);
sc.socket().setSendBufferSize(1024*1024);

//-- later, actual sending of messages: --
for (int n=0; n<20; n++){
  ByteBuffer bb = encoder.encode(CharBuffer.wrap(msg+'\0'));
  sc.write(bb);
  bb.rewind();
}

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

[COMPLETE PACKET 1]
[COMPLETE PACKET 2]
[COMPLETE PACKET 3]
[START OF PACKET 4][SOME OR ALL OF PACKET 5]

Происходит потеря данных, и пакеты начинают выполняться вместе, так что начало пакета 5 (в этом примере) поступает в том же сообщении, что и начало пакета 4.Это не просто усечение, это совместное выполнение сообщений.

Я предполагаю, что это связано с буфером TCP или "размером окна", или что сервер здесь просто предоставляет данные быстрее, чем это может обработать ОС, или сетевой адаптер, или что-то еще.Но как мне проверить и предотвратить это?Если я уменьшу длину сообщения при использовании sc.write(), но затем увеличу количество повторений, я все равно столкнусь с той же проблемой.Похоже, это просто проблема с объемом данных за короткий промежуток времени.Я также не вижу, чтобы sc.write() выдавал какие-либо исключения (я знаю, что в моем примере выше я не проверяю, но есть в моих тестах).

Я был бы рад, если бы мог программно проверить, не готов ли он еще к получению дополнительных данных, и установить задержку, и подождать, пока он не будет готов.Я также не уверен, имеет ли "sc.socket().setSendBufferSize(1024 * 1024);" какой-либо эффект, или мне нужно будет настроить это на стороне Linux.Есть ли способ действительно "очистить" SocketChannel?В качестве слабого обходного пути я мог бы попытаться явно принудительно выполнить полную отправку всего, что находится в буфере, например, каждый раз, когда я пытаюсь отправить сообщение объемом более 10 КБ (что не так часто встречается в моем приложении).Но я не знаю ни о каком способе принудительной отправки буфера (или подождать, пока он не будет отправлен).Спасибо за любую помощь!

Это было полезно?

Решение

Существует много причин, по которым sc.write() не отправила бы некоторые или все данные.Вы должны проверить возвращаемое значение и / или количество байтов, оставшихся в буфере.

for (int n=0; n<20; n++){
  ByteBuffer bb = encoder.encode(CharBuffer.wrap(msg+'\0'));
  if(sc.write(bb) > 0 && bb.remaining() == 0) {
    // all data sent
  } else {
    // could not send all data.
  }
  bb.rewind();
}

Другие советы

Вы не проверяете возвращаемое значение:

sc.write(bb);

Это возвращает количество записанных байт, которое может быть меньше, чем данные, доступные в вашем буфере.Из-за того, как работает nio, вы, вероятно, можете просто вызвать remaining() в вашем bytebuffer, чтобы посмотреть, остались ли какие-либо.

Я не занимался программированием NIO, но, согласно Javadocs, sc.write() не будет записывать весь ByteBuffer, если SocketChannel находится в неблокирующем режиме (как у вас) и выходной буфер сокета заполнен.

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

Я был бы счастлив, если бы мог программно проверить, не готов ли он еще к получению дополнительных данных

Вам нужно проверить возвращаемое значение sc.write(), чтобы узнать, заполнен ли ваш выходной буфер.

Не думайте, что у вас есть какой-либо контроль над тем, какие данные попадают в тот или иной пакет.

Подробнее здесь: Каков наилучший способ отслеживать сокет на наличие новых данных, а затем обрабатывать эти данные?

Вы используете неблокирующий режим:sc.Настройка блокировки(ложь);

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top