2010-11-02 6 views
4

У меня есть минимальный поставщик JMS, который отправляет сообщения темы через UDP и сообщения о очереди через TCP. Я использую один селектор для обработки ключей выбора UDP и TCP (регистрации как SocketChannels, так и DatagramChannels).Selector.select() запускает бесконечный цикл

Моя проблема: если я только отправляю и получаю UDP-пакеты, все идет хорошо, но как только я начинаю писать в сокет TCP (используя Selector.wakeup(), чтобы селектор выполнял собственно запись), селектор вводит бесконечный цикл, возвращая пустой набор ключей выбора и потребляя 100% процессор.

Код основного цикла (несколько упрощенно) является:

public void run() { 
    while (!isInterrupted()) { 
    try { 
    selector.select(); 
    } catch (final IOException ex) { 
    break; 
    } 

    final Iterator<SelectionKey> selKeys = selector.selectedKeys().iterator(); 
    while (selKeys.hasNext()) { 
    final SelectionKey key = selKeys.next(); 
    selKeys.remove(); 
    if (key.isValid()) { 
    if (key.isReadable()) { 
     this.read(key); 
    } 
    if (key.isConnectable()) { 
     this.connect(key); 
    } 
    if (key.isAcceptable()) { 
     this.accept(key); 
    } 
    if (key.isWritable()) { 
     this.write(key); 
     key.cancel(); 
    } 
    } 
    } 
    synchronized(waitingToWrite) { 
    for (final SelectableChannel channel: waitingToWrite) { 
    try { 
     channel.register(selector, SelectionKey.OP_WRITE); 
    } catch (ClosedChannelException ex) { 
     // TODO: reopen 
    } 
    } 
    waitingToWrite.clear(); 
    } 
    } 
} 

И для UDP отправить (TCP отправить аналогично):

public void udpSend(final String xmlString) throws IOException { 
    synchronized(outbox) { 
    outbox.add(xmlString); 
    } 
    synchronized(waitingToWrite) { 
    waitingToWrite.add(dataOutChannel); 
    } 
    selector.wakeup(); 
} 

Итак, что здесь не так? Должен ли я использовать 2 разных селектора для обработки пакетов UDP и TCP?

+0

Я нашел кого-то, у кого есть аналогичная проблема: http://web.archiveorange.com/archive/v/xyOAds3Uh2NVesezD5VH. Но решения пока нет ... –

ответ

1

Проблема исчезла после перехода на Java 1.6.0_22.

0

Измените свой проект, чтобы иметь один поток для входящего сетевого подключения.

Селектор следует использовать, если вы используете один поток для обработки входящих сообщений на нескольких сокетах TCP. Вы регистрируете каждый сокет с помощью селектора, а затем select(), который блокируется до тех пор, пока на одном из них не будет данных. Затем вы просматриваете каждую клавишу и обрабатываете ожидающие данные. Это метод, который я всегда использовал при написании кода C, и он будет работать, но я не думаю, что это лучший способ сделать это на Java.

Java имеет отличную поддержку встроенного потока, который C не поддерживает. Я думаю, что имеет смысл иметь один поток для TCP-сокета, а не использовать селектор вообще. Если вы просто выполняете операцию чтения в сокете, поток будет блокироваться до тех пор, пока данные не поступят или сокет не будет закрыт. Это фактически то же самое, что и выбор только с одним зарегистрированным каналом.

Если вы хотите, чтобы это работало только с одним потоком, вы должны использовать селектор только для сокетов TCP, где вы хотите получать входящие соединения. Таким образом, единственный раз, когда будет возвращен вызов select(), при поступлении входящих данных на один из сокетов. Эта нить будет спать в любое другое время, и никакая другая операция не разбудит ее.

+0

Я не понимаю, почему он не должен пытаться работать над однопоточной парадигмой. Это, безусловно, правильная работа. – krico

+0

Я хотел бы иметь все вместе в одном потоке, так как этот класс является одновременно клиентом и сервером. –

+0

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

1

Предлагаю вам проверить возвращаемое значение метода select().

try { 
if(selector.select() == 0) continue; 
} catch (final IOException ex) { 
break; 
} 

Вы пробовали отлаживать, чтобы увидеть, где находится цикл?

Edit:

  • Я рекомендую, что вместо вызова "удалить()" на итератора, вызовите selectedKeys.clear() после того, как вы итерацию над ними. Возможно, что реализация итератора не удаляет его из базового набора.
  • Убедитесь, что вы не регистрируете OP_CONNECT на подключенном канале.
+0

Уже пытался это сделать, цикл находится на вызове select(), а возвращаемое значение всегда равно 0. –

+0

, когда вы делаете selector.wakeup(), select() будет возвращаться с 0 при следующем вызове. – krico

+0

Я удалил все вызовы wakeup() из моего кода, все еще не работает. Однако одно пробуждение не должно запускать бесконечный цикл. –

1

Возможно, вы получаете исключение IOException и игнорируете его в пустом блоке catch. Никогда сделать это.И только продолжение после исключения IOException практически никогда не является правильным действием. Единственное исключение из этого правила, о котором я могу думать, это исключение SocketTimeoutException, и вы находитесь в режиме без блокировки, поэтому вы не получите их, и вы все равно не получите их на селекторах. Я хотел бы видеть содержимое ваших методов, которые обрабатывают соединение, прием, чтение и запись.

+0

Блок catch не пуст, он должен выйти из цикла while в исключениях ввода-вывода. Однако проблема решена путем обновления до последней версии 1.6 JDK. –

+0

Почему downvote? Некоторая ошибка в вышеперечисленном? Если это так, то нам всем любезно отметить это. – EJP

+0

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

Смежные вопросы