【问题标题】:How java nio ServerSocketChannel accept works?java nio ServerSocketChannel 接受如何工作?
【发布时间】:2015-02-10 20:29:17
【问题描述】:

我无法理解 NIO 的工作原理。这是一个示例代码:

// Create the server socket channel
ServerSocketChannel server = ServerSocketChannel.open();
// nonblocking I/O
server.configureBlocking(false);
// host-port 8000
server.socket().bind(new java.net.InetSocketAddress(host,8000));

// Create the selector
Selector selector = Selector.open();
// Recording server to selector (type OP_ACCEPT)
server.register(selector,SelectionKey.OP_ACCEPT);

while (true) {
      selector.select(); // blocking operation
      Iterator it = selector.selectedKeys().iterator();
      while (it.hasNext()) {
        SelectionKey selKey = (SelectionKey) it.next();

        // THE MOST INTRIGUING PART HERE!!!
        if (selKey.isAcceptable()) {
          ServerSocketChannel ssChannel = (ServerSocketChannel) selKey.channel();
          SocketChannel sc = ssChannel.accept();
        }
        it.remove();
     }
}

这里我有几个问题:

  1. selKey.channel() 返回一个 ServerSocketChannel 是否与我们一开始使用 ServerSocketChannel.open() 创建的通道完全相同?如果不是,那是什么?
  2. 更重要的问题:在大多数其他教程中 selKey.channel();步骤被跳过,他们只是使用 SocketChannel client = server.accept();例如这里:http://www.onjava.com/pub/a/onjava/2002/09/04/nio.html?page=2 和这里:http://www.developer.com/java/article.php/10922_3837316_2/Non-Blocking-IO-Made-Possible-in-Java.htm 那么,server.accept() 如何知道我们处理的当前密钥?
  3. 在http://www.developer.com/java/article.php/10922_3837316_2/Non-Blocking-IO-Made-Possible-in-Java.htm 中,他们甚至建议在新线程中接受频道。我猜可能会出现以下情况。

    key1 someClient1 acceptable
    key2 someClient2 not acceptable
    key3 someClient3 acceptable
    
    startThread1
    startThread3
    
    scheduler decides to give time to thread3 instead of thread1
    
    thread3 -> socket.accept() <- actually accepts client1
    thread1 -> socket.accept() <- actually accepts client3
    

那么,您能否解释一下 selector 如何与 ServerSocketChannel 和 accept 方法配对使用?因为我不明白#accept 接受客户的顺序以及该顺序与 selectedKeys 的关系。

  1. 我可以简单地执行以下操作吗:

    int availableClients = 0;
    while (it.hasNext()) {
        SelectionKey selKey = (SelectionKey) it.next();
    
        if (selKey.isAcceptable()) {
            ++availableClients;
        }
        it.remove();
    }
    for (int i = 0; i < availableClients; ++i) {
           SocketChannel sc = server.accept();
           doSomething(sc);
    }
    

【问题讨论】:

    标签: java sockets network-programming nio nonblocking


    【解决方案1】:

    selKey.channel() 返回一个 ServerSocketChannel 是否和我们一开始用 ServerSocketChannel.open() 创建的通道完全相同?

    是的。

    更重要的问题:在大多数其他教程中 selKey.channel();步骤被跳过,他们只是使用 SocketChannel client = server.accept();例如这里:http://www.onjava.com/pub/a/onjava/2002/09/04/nio.html?page=2 和这里:http://www.developer.com/java/article.php/10922_3837316_2/Non-Blocking-IO-Made-Possible-in-Java.htm 那么,server.accept() 如何知道我们处理的当前密钥?

    它没有。他们假设只有一个ServerSocketChannel。你的方式更好:它更通用。

    在http://www.developer.com/java/article.php/10922_3837316_2/Non-Blocking-IO-Made-Possible-in-Java.htm,他们甚至建议在新线程中接受频道。

    我不知道为什么。这是一个非阻塞调用。它会立即返回。建议毫无意义。忽略它。这是六年前的一个质量很差的教程,但十三年前有更好的教程。试试 Oracle 教程。这一篇的作者似乎根本不明白非阻塞模式的意义。为每个事件使用单独线程的建议是完全可笑的。他也不明白如何使用 OP_WRITE。他对cancel() 做出了错误的断言。我可以继续。他是否曾经执行过这段代码值得怀疑:他当然没有以任何方式调查它的行为。如何编写不可扩展的 NIO 服务器。相当的壮举。

    我猜可能会出现以下情况。

    我什至不明白为什么你会同时在两个线程中接受,更不用说它接受哪个客户端对任何一个线程有什么影响。这是在不存在的情况下发明的困难。

    那么,您能否解释一下 selector 如何与 ServerSocketChannel 和 accept 方法配对使用?因为我不明白#accept 接受客户的顺序以及该顺序与 selectedKeys 的关系。

    accept() 方法返回积压队列中的下一个套接字,并且只要积压队列非空,就会触发 OP_ACCEPT。这非常简单,没有什么神秘之处。该顺序根本与“选定的键”无关。选择的键是ServerSocketChannel 的键。

    编辑:看来你有一个重大的误解。考虑:

    1. 您为OP_ACCEPT 创建并注册ServerSocketChannel。
    2. 两个客户端同时连接。
    3. 现在正好存在一个SelectionKey,因此在选定的键集中恰好有一个:ServerSocketChannel 的那个。
    4. 然后您在该密钥上处理您的isAcceptable() 案例;接受一个或两个连接;为OP_READ.注册这些频道
    5. 现在存在三个选择键,并且选择键集中没有,因为您已清除它。
    6. 两个客户端都发送了一些数据。
    7. 现在您已经准备好在选定键集中读取两个选择键。

    好吗?

    我可以简单地执行以下操作吗:

    当然可以,但是为什么呢?把简单的事情变得复杂并没有任何好处。按第一种方式做。

    【讨论】:

    • 哇!谢谢,这几乎是我想听到的,但在我接受它作为答案之前,我想请你更详细地描述以下句子:“accept() 方法返回积压队列中的下一个套接字,并且 OP__ACCEPT 触发每当积压队列非空时。”我最后关于“++availableClients”的声明只是因为我不明白可接受的密钥和accept() 之间的相关性是什么。看起来我们可以处理一个密钥,但接受另一个让我感到困惑的客户端。提前致谢。
    • 正确。您处理 ServerSocketChannel 密钥,并接受连接到新客户端的新创建的 SocketChannel。相关性是当 OP__ACCEPT 触发时,accept() 不会返回 null。
    • 好吧,我的意思是如果 client1 有 key1,client2 有 key2 并且 selectedKeys 返回 [key1,key2] 那么 serverChannel.accept() 可能首先为 client2 创建新频道。然后,当我们处理 key2 时,它将为 client1 创建新通道。所以,我认为顺序实际上是随机的(至少对于非阻塞的东西),我错了吗?
    • 什么键?什么客户?你还没有。在您 (i) 接受连接然后 (ii) 注册新的套接字通道之前,客户端没有 任何 键。客户端按照它们被放入积压队列的顺序被接受,这取决于完成 TCP 握手的顺序。 “客户乱序”的概念没有意义。
    • 天啊。 没有两种类型的键,我没有在任何地方说其他。我说“客户端密钥”指的是不存在的东西。有一种类型的密钥:SelectionKey.有两种类型的通道。 ServerSocketChannel 和 SocketChannel. 我不认为这有任何影响。你在某处有严重误解,但我不能把我的手指在它上面。
    猜你喜欢
    • 1970-01-01
    • 2010-10-28
    • 2011-02-10
    • 2019-06-26
    • 2014-01-12
    • 1970-01-01
    • 2015-04-11
    • 2018-11-07
    • 1970-01-01
    相关资源
    最近更新 更多