Selector.select () запускает бесконечный цикл - PullRequest
4 голосов
/ 02 ноября 2010

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

Моя проблема: если я только отправляю и получаю 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?

Ответы [ 4 ]

1 голос
/ 03 ноября 2010

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

1 голос
/ 02 ноября 2010

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

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

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

Редактировать:

  • Я рекомендую, чтобы вместо вызова «remove ()» для итератора вы вызывали selectedKeys.clear () после итерации по ним. Возможно, что реализация итератора не удалит его из базового набора.
  • Убедитесь, что вы не регистрируете OP_CONNECT на подключенном канале.
1 голос
/ 02 ноября 2010

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

0 голосов
/ 02 ноября 2010

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

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

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

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

...