Java NIO: отправка больших сообщений быстро приводит к усеченным пакетам и потере данных - PullRequest
3 голосов
/ 31 января 2009

У меня есть эта неприятная проблема, когда отправка нескольких больших сообщений с быстрой последовательностью с сервера 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 КБ, например (что не часто в моем приложении). Но я не знаю ни одного способа принудительной отправки буфера (или ожидания, пока он не будет отправлен). Спасибо за любую помощь!

Ответы [ 5 ]

6 голосов
/ 31 января 2009

Есть много причин, по которым 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();
}
5 голосов
/ 31 января 2009

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

sc.write(bb);

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

3 голосов
/ 31 января 2009

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

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

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

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

1 голос
/ 25 марта 2009

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

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

1 голос
/ 31 января 2009

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...