Selector.select() 启动无限循环

发布于 2024-09-30 17:17:22 字数 1520 浏览 4 评论 0原文

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

我的问题是:如果我只发送和接收 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 数据包吗?

I have a minimal JMS provider, which sends topic messages over UDP and queue messages over TCP.
I use a single selector to handle UDP and TCP selection keys (registering both SocketChannels and DatagramChannels).

My problem is: if I only send and receive UDP packets, everything goes well, but as soon as I start writing on a TCP socket (using Selector.wakeup() to have the selector do the actual writing), the selector enters an infinite loop, returning an empty selection key set, and eating 100% CPU.

The code of the main loop (somewhat simplified) is:

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();
   }
  }
 }

And for a UDP send (TCP send is similar):

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

So, what's wrong here? Should I use 2 different selectors to handle UDP and TCP packets?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

几味少女 2024-10-07 17:17:23

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

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

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

编辑:

  • 我建议不要在迭代器上调用“remove()”,而是在迭代它们后调用 selectedKeys.clear() 。迭代器的实现可能不会将其从底层集合中删除。
  • 检查您是否未在已连接的通道上注册 OP_CONNECT。

I suggest you check the return value of select() method.

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

Did you try debugging to see where the loop is?

Edit:

  • I recomend that instead of calling "remove()" on the iterator, you call selectedKeys.clear() after you iterate over them. It is possible that the implementation of the iterator, does not remove it from the underlying set.
  • Check that you are not registering OP_CONNECT on a connected channel.
執念 2024-10-07 17:17:23

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

Problem went away after upgrading to Java 1.6.0_22.

高速公鹿 2024-10-07 17:17:23

您可能会收到 IOException 并在空的 catch 块中忽略它。 永远不要这样做。在 IOException 之后继续执行实际上永远不是正确的操作。我能立即想到的该规则的唯一例外是 SocketTimeoutException,并且您处于非阻塞模式,因此您不会获得这些异常,并且无论如何您也不会在选择器上获得它们。我想看看你处理连接、接受、读取和写入的方法的内容。

You are probably getting an IOException and ignoring it in the empty catch block. Never do that. And just continuing after an IOException is practically never the correct action. The only exception to that rule I can think of offhand is a SocketTimeoutException, and you're in non-blocking mode so you won't be getting those, and you don't get them on selectors anyway. I would want to see the content of your methods that handle connect, accept, read, and write.

故人爱我别走 2024-10-07 17:17:23

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

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

Java 具有强大的本机线程支持,而 C 则没有。我认为每个 TCP 套接字有一个线程并且根本不使用选择器更有意义。如果您只是对套接字执行读取操作,则线程将阻塞,直到数据到达或套接字关闭。这实际上与仅选择一个注册频道是一样的。

如果您想让它仅与一个线程一起工作,则应该仅对需要传入连接的 TCP 套接字使用该选择器。这样,只有当某个套接字上有传入数据等待时,才会返回 select() 调用。该线程在所有其他时间都将处于休眠状态,并且没有其他操作会唤醒它。

Modify your design to have one thread per incoming network connection.

The selector should be used when you're using one thread to process incoming messages on multiple TCP sockets. You register each socket with the selector and then select(), which blocks until there is data available on one of them. Then you loop through each key and process the waiting data. This is the method I've always used when writing C code, and it will work, but I don't think it's the best way to do it in Java.

Java has great native thread support, which C does not. I think it makes a lot more sense to have one thread per TCP socket and not to use selectors at all. If you just do a read operation on a socket, the thread will block until data arrives or the socket is closed. This is effectively the same thing as selecting with only one registered channel.

If you want to get this to work with only one thread, you should use the selector only for TCP sockets where you want incoming connections. This way, the only time the call to select() will return is when there is incoming data waiting on one of the sockets. That thread will be asleep at all other times, and no other operation will wake it up.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文