Java NIO Threading проблема с SocketChannel.write () - PullRequest
3 голосов
/ 18 июля 2009

Иногда при отправке большого объема данных через SocketChannel.write () базовый буфер TCP заполняется, и мне приходится постоянно повторять попытку write (), пока все данные не будут отправлены.

Итак, у меня может быть что-то вроде этого:

public void send(ByteBuffer bb, SocketChannel sc){
   sc.write(bb);
   while (bb.remaining()>0){
      Thread.sleep(10);
      sc.write(bb);          
   }
}

Проблема в том, что случайная проблема с большим ByteBuffer и переполнением основного буфера TCP означает, что этот вызов send () будет блокироваться на неожиданное количество времени. В моем проекте одновременно подключены сотни клиентов, и одна задержка, вызванная одним соединением с сокетом, может привести к обходу всей системы до тех пор, пока эта задержка с одним SocketChannel не будет решена. Когда происходит задержка, она может вызвать цепную реакцию замедления в других областях проекта, и важно иметь низкую задержку.

Мне нужно решение, которое позаботится об этой проблеме переполнения буфера TCP прозрачно и не заставляя все блокировать, когда требуются множественные вызовы SocketChannel.write (). Я подумал о том, чтобы поместить send () в отдельный класс, расширяющий Thread, чтобы он работал как собственный поток и не блокировал вызывающий код. Однако меня беспокоят дополнительные затраты, необходимые для создания потока для каждого подключения сокета, который я поддерживаю, особенно когда в 99% случаев SocketChannel.write () завершается успешно с первой попытки, то есть нет необходимости в наличии потока , (Другими словами, размещение send () в отдельном потоке действительно необходимо, только если используется цикл while () - только в тех случаях, когда существует проблема с буфером, возможно, в 1% случаев). Если есть проблема с буфером только 1% времени, мне не нужны служебные данные потока для остальных 99% вызовов send ().

Надеюсь, это имеет смысл ... Я мог бы действительно использовать некоторые предложения. Спасибо!

Ответы [ 7 ]

1 голос
/ 18 июля 2009

Вам не нужен сон (), так как запись либо сразу же вернется, либо заблокируется. У вас может быть исполнитель, которому вы передаете запись, если она не пишет в первый раз. Другой вариант - иметь небольшой пул потоков для выполнения записи.

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

1 голос
/ 18 июля 2009

До Java NIO у вас было , чтобы использовать один поток на сокет для получения хорошей производительности. Это проблема для всех приложений на основе сокетов, а не только для Java. Поддержка неблокирующего ввода-вывода была добавлена ​​во все операционные системы, чтобы преодолеть это. Реализация Java NIO основана на Selectors.

См. Полная книга по Java NIO и эта На Java статья для начала. Заметьте, однако, что это сложная тема, и она все еще приносит некоторые проблемы с многопоточностью в ваш код. Google "неблокирующий NIO" для получения дополнительной информации.

0 голосов
/ 24 февраля 2010

Чем больше я читаю о Java NIO, тем больше это дает мне воли. Во всяком случае, я думаю, что эта статья отвечает вашей проблеме ...

http://weblogs.java.net/blog/2006/05/30/tricks-and-tips-nio-part-i-why-you-must-handle-opwrite

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

Также я быстро прихожу к выводу, что использование Java NIO само по себе слишком опасно. Где я могу, я думаю, что я, вероятно, буду использовать Apache MINA, которая обеспечивает хорошую абстракцию над Java NIO и ее маленькие «сюрпризы».

0 голосов
/ 19 июля 2009

Я второй Томс отвечаю о регистрации OP_WRITE на клавише выбора.

Я бы рекомендовал Rox Java NIO Tutorial .

0 голосов
/ 18 июля 2009

У меня сейчас одни и те же проблемы:
- Если у вас небольшое количество соединений, но с большими передачами, я просто создал бы пул потоков и позволил бы блок записи для потоков записи.
- Если у вас много подключений, вы можете использовать полную версию Java NIO и зарегистрировать OP_WRITE на своих сокетах accept () ed, а затем дождаться появления селектора.

В книге Orielly Java NIO есть все это.
Также: http://www.exampledepot.com/egs/java.nio/NbServer.html?l=rel

Некоторые онлайн-исследования привели меня к мысли, что NIO довольно излишне, если у вас нет много входящих подключений. В противном случае, если его всего несколько больших передач - тогда просто используйте поток записи. Это, вероятно, будет иметь более быстрый ответ. У многих людей есть проблемы с NIO, не отвечающие так быстро, как они хотят. Поскольку ваш поток записи сам по себе блокирует, он не причинит вам вреда.

0 голосов
/ 18 июля 2009

Есть несколько вещей, которые вам нужно сделать, предполагая, что у вас уже есть цикл, использующий Selector.select (); определить, какие сокеты готовы к вводу / выводу.

  • Установите канал сокета неблокирующим после его создания, sc.configureBlocking (false);
  • Запишите (возможно, части) в буфер и проверьте, осталось ли что-нибудь. Сам буфер заботится о текущей позиции и о том, сколько осталось.

Что-то вроде

sc.write(bb);
if(sc.remaining() == 0)
   //we're done with this buffer, remove it from the select set if there's nothing else to send.
else
    //do other stuff/return to select loop
  • Избавьтесь от спящего цикла while
0 голосов
/ 18 июля 2009

Для сотен соединений вам, вероятно, не нужно беспокоиться о NIO. Вам подойдут старые добрые блокирующие розетки и нитки.

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

...