4

我有一个最小的 JMS 提供程序,它通过 UDP 发送主题消息并通过 TCP 将消息排队。我使用单个选择器来处理 UDP 和 TCP 选择键(同时注册 SocketChannels 和 DatagramChannels)。

我的问题是:如果我只发送和接收 UDP 数据包,一切都很顺利,但是一旦我开始在 TCP 套接字上写入(使用 Selector.wakeup() 让选择器进行实际写入),选择器就会进入无限循环,返回一个空的选择键集,并占用 100% 的 CPU。

主循环的代码(有些简化)是:

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

4 回答 4

1

升级到 Java 1.6.0_22 后问题消失了。

于 2010-11-02T16:37:41.947 回答
1

您可能会收到一个 IOException 并在空的 catch 块中忽略它。永远不要那样做。在 IOException 之后继续执行实际上绝不是正确的操作。我能想到的唯一例外是 SocketTimeoutException,你处于非阻塞模式,所以你不会得到这些,而且你也不会在选择器上得到它们。我想看看您处理连接、接受、读取和写入的方法的内容。

于 2010-11-02T23:38:25.457 回答
1

我建议你检查 select() 方法的返回值。

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

您是否尝试调试以查看循环在哪里?

编辑:

  • 我建议不要在迭代器上调用“remove()”,而是在迭代它们之后调用 selectedKeys.clear()。迭代器的实现可能不会将其从底层集合中删除。
  • 检查您没有在连接的通道上注册 OP_CONNECT。
于 2010-11-02T12:01:28.390 回答
0

修改您的设计,使每个传入网络连接有一个线程。

当您使用一个线程处理多个 TCP 套接字上的传入消息时,应使用选择器。您使用选择器注册每个套接字,然后select(),它会阻塞,直到其中一个上有可用数据。然后循环遍历每个键并处理等待的数据。这是我在编写 C 代码时一直使用的方法,它会起作用,但我认为这不是在 Java 中最好的方法。

Java 有很好的本地线程支持,而 C 没有。我认为每个 TCP 套接字有一个线程而不使用选择器更有意义。如果你只是对一个套接字进行读操作,线程将阻塞直到数据到达或套接字关闭。这实际上与仅选择一个注册频道相同。

如果你想让它只使用一个线程,你应该只对你想要传入连接的 TCP 套接字使用选择器。这样,调用select()返回的唯一时间是当有传入数据在其中一个套接字上等待时。该线程将在所有其他时间处于睡眠状态,并且没有其他操作将其唤醒。

于 2010-11-02T11:59:15.503 回答