我有一个Java客户端,该客户端使用使用Java NIO的TCP套接字连接到C++服务器。这在Linux,AIX和HP/UX上有效,但是在Solaris上,OP_CONNECT
事件从不触发。
更多详细信息:
Selector.select()
返回0,并且“所选键集”为空。 这是一些演示此问题的测试代码:
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
public class NioTest3
{
public static void main(String[] args)
{
int i, tcount = 1, open = 0;
String[] addr = args[0].split(":");
int port = Integer.parseInt(addr[1]);
if (args.length == 2)
tcount = Integer.parseInt(args[1]);
InetSocketAddress inetaddr = new InetSocketAddress(addr[0], port);
try
{
Selector selector = Selector.open();
SocketChannel channel;
for (i = 0; i < tcount; i++)
{
channel = SocketChannel.open();
channel.configureBlocking(false);
channel.register(selector, SelectionKey.OP_CONNECT);
channel.connect(inetaddr);
}
open = tcount;
while (open > 0)
{
int selected = selector.select();
System.out.println("Selected=" + selected);
Iterator<SelectionKey> it = selector.selectedKeys().iterator();
while (it.hasNext())
{
SelectionKey key = it.next();
it.remove();
channel = (SocketChannel)key.channel();
if (key.isConnectable())
{
System.out.println("isConnectable");
if (channel.finishConnect())
{
System.out.println(formatAddr(channel) + " connected");
key.interestOps(SelectionKey.OP_WRITE);
}
}
else if (key.isWritable())
{
System.out.println(formatAddr(channel) + " isWritable");
String message = formatAddr(channel) + " the quick brown fox jumps over the lazy dog";
ByteBuffer buffer = ByteBuffer.wrap(message.getBytes());
channel.write(buffer);
key.interestOps(SelectionKey.OP_READ);
}
else if (key.isReadable())
{
System.out.println(formatAddr(channel) + " isReadable");
ByteBuffer buffer = ByteBuffer.allocate(1024);
channel.read(buffer);
buffer.flip();
byte[] bytes = new byte[buffer.remaining()];
buffer.get(bytes);
String message = new String(bytes);
System.out.println(formatAddr(channel) + " read: '" + message + "'");
channel.close();
open--;
}
}
}
}
catch (IOException e)
{
e.printStackTrace();
}
}
static String formatAddr(SocketChannel channel)
{
return Integer.toString(channel.socket().getLocalPort());
}
}
您可以使用命令行运行此命令:
java -cp . NioTest3 <ipaddr>:<port> <num-connections>
如果您要使用真正的回显服务,则port应该为7; IE。:
java -cp . NioTest3 127.0.0.1:7 5
如果您无法运行真正的回显服务,则来源为here。使用以下命令在Solaris下编译回显服务器:
$ cc -o echoserver echoserver.c -lsocket -lnsl
并像这样运行它:
$ ./echoserver 8007 > out 2>&1 &
据报道,这是Sun的bug。
最佳答案
您的错误报告已关闭为“不是错误”,并附有解释。您将忽略connect()
的结果,如果为true,则表示OP_CONNECT
将永远不会触发,因为该 channel 已连接。如果返回的结果为false,则只需要整个OP_CONNECT/finishConnect()
megillah。因此,除非OP_CONNECT
返回false,否则您甚至不应该注册connect()
,更不用说在甚至调用connect().
之前注册它了。
进一步说明:
在后台,OP_CONNECT
和OP_WRITE
是同一件事,这解释了其中的一部分。
由于您只有一个线程,解决方法是在阻塞模式下进行连接,然后将I/O切换为非阻塞。
在使用Selector
注册 channel 之后,您是否正在执行select()?
处理非阻塞连接的正确方法如下:
channel.configureBlocking(false);
if (!channel.connect(...))
{
channel.register(sel, SelectionKey.OP_CONNECT, ...); // ... is the attachment, or absent
}
// else channel is connected, maybe register for OP_READ ...
// select() loop runs ...
// Process the ready keys ...
if (key.isConnectable())
{
if (channel.finishConnect())
{
key.interestOps(0); // or SelectionKey.OP_READ or OP_WRITE, whatever is appropriate
}
}
在查看您的扩展代码后,一些非详尽的注释:removeInterest()
方法。TYPE_DEREGISTER_OBJECT
也关闭 channel 。不确定这是否是您真正想要的。我以为它应该只是取消键,并且应该有一个单独的操作来关闭 channel 。addInterest()
和removeInterest()
是很好的例子。他们捕获了异常,将其记录下来,然后进行设置或清除了一点,就好像未发生异常一样进行处理:一行代码。而且最重要的是,它们同时具有静态和非静态版本。所有调用key.cancel()
,channel.close()
等的小方法也是如此。这没有任何意义,只是增加了代码行数。它只会增加模糊性,并使您的代码更难以理解。只需内联进行所需的操作,并在select循环的底部具有单个捕获器即可。finishConnect()
返回false,则不是连接失败,它尚未完成。如果抛出Exception
,则表明连接失败。OP_CONNECT
和OP_READ
。这没有道理,可能会引起问题。在OP_CONNECT
触发之前,没有任何内容可供阅读。只需先注册OP_CONNECT
。ByteBuffer
。这非常浪费。在连接的整个生命周期中,请使用相同的连接器。read()
的结果。可以为零。它可以是-1,表示EOS,您必须在该 channel 上关闭 channel 。您还假设一次读取即可获得完整的应用程序消息。你不能以为。这就是为什么在连接的整个生命周期内都应使用单个ByteBuffer
的另一个原因。write()
的结果。调用时它可能小于buffer.remaining(
)。可以为零。NetSelectable
设置为键附件,可以大大简化此过程。然后,您可以删除一些内容,例如 channel 映射和断言,因为键的 channel 必须始终等于键的附件的 channel 。finishConnect()
代码移动到NetSelector
,并让connectEvent()
只是成功/失败通知。您不想散布这种东西。对readEvent()
进行相同的操作,即使用NetSelector
提供的缓冲区在NetSelectable
中进行读取,然后将读取结果通知给NetSelectable
:count或-1或异常(exception)。写入时同上:如果 channel 可写,请从
NetSelectable
中写入一些内容,将其写入NetSelector中,然后通知结果。您可以让通知回调返回一些内容以指示下一步要执行的操作,例如关闭 channel 。但这实际上是所需复杂度的五倍,事实证明您确实有此错误。简化你的头。
关于java - Java Solaris NIO OP_CONNECT问题,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/6540346/