【问题标题】:NIO Server not able to listen to clientNIO Server 无法监听客户端
【发布时间】:2010-10-28 17:33:36
【问题描述】:

您好,我正在尝试实现一个简单的 Java NIO 服务器;它将 socketChannel 注册到选择器。因此,我希望听取客户的意见并发送一些回复。 socketChannel注册到selector后,即使client(non NIO)发送了一些数据,Server也无法读取;但是生成的密钥仍在迭代中。

详细视图:服务器端:

**First thread**:

公共无效运行(){ 而(真){

    ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
    serverSocketChannel.configureBlocking(true);
    serverSocketChannel.socket().bind(inetAdressOfServer);
    SocketChannel clientChannel = serverSocketChannel.accept();
    new Listener("").addSocketChannel(clientChannel);

}}

**Second Thread**:

    static Selector selector = Selector.open();
    public boolean addSocketChannel(SocketChannel clientChannel) {

        SelectionKey key = clientSocketChannel.register(selector, selector.OP_READ|SelectionKey.OP_WRITE);              
        key.attach(new ChannelCallback(clientSocketChannel));
        return key.isValid();
    }

    public void run() {

        Set keysSet = selector.keys();
        Iterator i = keysSet.iterator();        
        while (i.hasNext()) {
            SelectionKey key = (SelectionKey) i.next();
        }

        if (key.isReadable()) {
            //read and do something
        }
    }



Client Side:

Socket socket = new Socket(serverIP, serverPort);    
DataOutputStream dos = new DataOutputStream(socket.getOutputStream());    
dos.writeBytes(str + "\n");

注意:在单线程中完成时,相同的程序可以工作,但是以上述方式实现时会导致它不听客户端。 请帮我解决这个问题。

【问题讨论】:

  • 我每次都在想,现在人们仍然使用 6 年前的 Java-1.4 编写风格来编写迭代器。旧的 Java 书籍和教程仍然如此普遍吗?这让我很难过。

标签: java tcp nio


【解决方案1】:

从另一个线程读取作品,这是您的代码明显的问题。

public void run() {
    Set keysSet = selector.keys();

这里你从迭代器中获取键集,但是没有代码在选择器上执行 select() 或 selectNow(),所以这个集总是空的。

    Iterator i = keysSet.iterator();        
    while (i.hasNext()) {
        SelectionKey key = (SelectionKey) i.next();
    }
    if (key.isReadable()) {
        //read and do something
    }
}

这甚至无法编译,必须在 while 块内完成对键的“读取”检查。

SelectionKey key = clientSocketChannel.register(selector,
                                                SelectionKey.OP_READ | 
                                                SelectionKey.OP_WRITE);              

两个问题:在完成之前应该将通道设置为非阻塞模式,并且不应该设置 SelectionKey.OP_WRITE,除非您希望每次运行选择时都返回键。

如果你真的打算写,你应该只设置 SelectionKey.OP_WRITE。

最后,这里使用两个线程是非常规的。这样做的推荐方法是使用 OP_ACCEPT 将 ServerSocketChannel 注册到 Selector,并在与读/写相同的线程上运行 ServerSocket 上的接受。

【讨论】:

  • 非常感谢诺基亚,我可以运行代码了。我同意你关于线程的概念是非常规的,但这只是我试图看到性能优化。能否请您告诉我如何在读取数据后将 key.isReadable 标记为 false。
  • 通常你会在读取后使用 iterator.remove() 从集合中删除密钥。我似乎记得,否则每次执行 select() 时它都会出现。在 remove() 之后,您可以保证该键不会成为所选键的一部分,直到有数据可供再次读取。当然,如果你想关闭对密钥的阅读兴趣(即你不想在这个连接上继续阅读),你应该使用 key.interestOps(int ops) 来设置这个密钥的新兴趣(例如 0既不读也不写)。
  • Selector.keys() 返回一组 注册 键,而不是 ready 键,因此您是否曾经调用过select() 没有区别.
  • 是的。那里的基本问题......应该是selectedKeys()
  • 只有在实际完成返回零的写入时才应设置 SelectionKey.OP_WRITE,因此必须将其推迟到套接字发送缓冲区中有空间为止。否则你就写吧。
【解决方案2】:

很难看出你在那里做了什么,但看起来你标记为“第二个线程”的东西被两个线程使用(在实现Runnable/扩展Thread和实际线程时有些混淆? )。特别是,我猜测new Listener 构造并启动了一个线程。然后您在第一个线程中调用addSocketChannel。因此,存在竞争条件。

另外,将selector 设为静态也是一个糟糕的主意。

【讨论】:

  • 嗨,汤姆,抱歉,代码不清晰。第一个线程在不同的类中运行,该类具有接受套接字通道并将其传递给注册的逻辑。请您让我知道应该进行哪些修改。谢谢。
  • 看起来可能可行的最短修复方法是将 addSocketChannel 的调用移至 Listener 构造函数(并在启动线程之前调用它)。但是,您确实需要整理您的线程。
  • 嗨,Tom,请您给我一个学习和改进 NIO 线程的指针。你也可以让我知道是否有任何替代静态选择器的方法,不能让它与 volatile 一起工作。还有比这更好的方法吗?
  • 我经常看到提到的 Rox 教程:rox-xmlrpc.sourceforge.net/niotut 您还可以在 naga.googlecode.com 上研究(完全免费复制)到 Naga(NIO 包装器库)的源代码 -实现处理了许多常见问题,例如部分读写、写后关闭/关闭、异步断开连接、数据包转换等。
猜你喜欢
  • 2012-05-19
  • 2015-09-14
  • 1970-01-01
  • 2017-12-08
  • 2016-10-03
  • 2016-06-25
  • 2017-08-09
  • 1970-01-01
  • 2010-10-21
相关资源
最近更新 更多