我有一个最小的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数据包吗?
答案 0 :(得分:1)
我建议你检查select()方法的返回值。
try {
if(selector.select() == 0) continue;
} catch (final IOException ex) {
break;
}
您是否尝试过调试以查看循环的位置?
编辑:
答案 1 :(得分:1)
升级到Java 1.6.0_22后问题就消失了。
答案 2 :(得分:1)
您可能正在获取IOException并在空catch块中忽略它。 从不那样做。并且在IOException之后继续执行几乎从来都不是正确的操作。我可以想到的那个规则的唯一例外是SocketTimeoutException,并且你处于非阻塞模式,所以你不会得到那些,并且你无论如何也不会在选择器上得到它们。我希望看到处理连接,接受,读取和写入的方法的内容。
答案 3 :(得分:0)
修改您的设计,使每个传入网络连接都有一个线程。
当您使用一个线程处理多个TCP套接字上的传入消息时,应使用选择器。您使用选择器注册每个套接字,然后select()
注册,直到其中一个可用数据为止。然后循环遍历每个键并处理等待的数据。这是我在编写C代码时总是使用的方法,它可以工作,但我不认为这是用Java编写的最佳方法。
Java具有很好的本机线程支持,而C则没有。我认为每个TCP套接字有一个线程而根本不使用选择器更有意义。如果您只是对套接字执行读取操作,则线程将阻塞,直到数据到达或套接字关闭。这实际上只与选择一个注册频道相同。
如果您希望仅使用一个线程,则应仅将选择器用于需要传入连接的TCP套接字。这样,对select()
的调用将返回的唯一时间是在其中一个套接字上有传入数据等待的时候。该线程将在所有其他时间处于睡眠状态,并且没有其他操作会将其唤醒。