java - Java Solaris NIO OP_CONNECT问题

标签 java sockets solaris nio

我有一个Java客户端,该客户端使用使用Java NIO的TCP套接字连接到C++服务器。这在Linux,AIX和HP/UX上有效,但是在Solaris上,OP_CONNECT事件从不触发。

更多详细信息:

  • Selector.select()返回0,并且“所选键集”为空。
  • 该问题仅在连接到本地计算机(通过环回或以太网接口(interface))时发生,但在连接到远程计算机时起作用。
  • 我已经在两台不同的Solaris 10计算机上确认了该问题;使用JDK版本1.6.0_21和_26的物理SPARC和虚拟x64(VMWare)。

  • 这是一些演示此问题的测试代码:
    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_CONNECTOP_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
      }
    }
    
    在查看您的扩展代码后,一些非详尽的注释:
  • 关闭 channel 会取消 key 。您不需要两者都做。
  • 错误地实现了非静态removeInterest()方法。
  • TYPE_DEREGISTER_OBJECT也关闭 channel 。不确定这是否是您真正想要的。我以为它应该只是取消键,并且应该有一个单独的操作来关闭 channel 。
  • 在小型方法和异常处理方面,您已经过头了。 addInterest()removeInterest()是很好的例子。他们捕获了异常,将其记录下来,然后进行设置或清除了一点,就好像未发生异常一样进行处理:一行代码。而且最重要的是,它们同时具有静态和非静态版本。所有调用key.cancel()channel.close()等的小方法也是如此。这没有任何意义,只是增加了代码行数。它只会增加模糊性,并使您的代码更难以理解。只需内联进行所需的操作,并在select循环的底部具有单个捕获器即可。
  • 如果finishConnect()返回false,则不是连接失败,它尚未完成。如果抛出Exception,则表明连接失败。
  • 您正在同时注册OP_CONNECTOP_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/

    相关文章:

    c - 查询完整 DNS 记录

    c - 在客户端服务器程序中 fork 客户端

    node.js - socket.request.user 在 Passport.js、Socket.io 和 Express 中未定义

    linux - 使用 Bash Shell 脚本制作一个简单的饼图

    java - 在具有相同引用的真实条件下在线程内创建一个对象

    java - java.util.LinkedList addBefore 方法中的 NPE

    java - 使用 JavaCompiler 编译 Minecraft

    node.js - Solaris 11 : Node. JS:无法自动检测 OpenSSL 支持

    c++ - 为什么我在 solaris 上会出现这个段错误?

    Java 使二叉搜索树通用(它有效,但我知道我做得不对)