Java NIO中的ServerSocketChannel accept如何工作?

5
我不太理解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() return a ServerSocketChannel is it exactly the same channel we created in the beggining with ServerSocketChannel.open()? If not, then what is it?
  2. More important question: in most other tutorials selKey.channel(); step is skipped and they simply use SocketChannel client = server.accept(); For example here: http://www.onjava.com/pub/a/onjava/2002/09/04/nio.html?page=2 and here: http://www.developer.com/java/article.php/10922_3837316_2/Non-Blocking-IO-Made-Possible-in-Java.htm So, how does server.accept() knows about current key we process?
  3. In http://www.developer.com/java/article.php/10922_3837316_2/Non-Blocking-IO-Made-Possible-in-Java.htm they even suggest to accept channel in new thread. I guess it is possible that the following situation may occur.

    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
    
那么,您能否解释一下选择器与ServerSocketChannel和accept方法配合工作的原理呢?因为我不明白#accept接受客户端的顺序以及这个顺序与selectedKeys有什么关系。
  1. Can I simply do the following:

    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);
    }
    
1个回答

8

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()如何知道我们正在处理的当前键(key)?

它不知道。他们假设只有一个ServerSocketChannel。你的方法更好:更通用。

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

我不知道为什么。这是一个非阻塞调用。它会立即返回。这个建议是毫无意义的。忽略它。这是一个六年前非常低质量的教程,但十三年前已经有更好的教程了。试试Oracle教程。这个作者似乎完全不理解非阻塞模式的重点。对于每个事件使用单独的线程的建议完全荒谬。他也不明白如何使用OP_WRITE。他对cancel()做出了错误的断言。我可以继续说下去。很难想象他曾经执行过这段代码:他肯定没有以任何方式调查它的行为。如何编写一个不可扩展的NIO服务器。相当了不起。

我想可能会发生以下情况。

我甚至不明白为什么你会同时在两个线程中接受,更别说它对任何一个线程有什么影响了。这是一个不存在的困难。
那么,请您解释一下选择器如何与ServerSocketChannel和accept方法配对工作呢?因为我不明白#accept接受客户端的顺序以及这种顺序与selectedKeys之间的关系。
accept()方法返回队列中的下一个套接字,而OP_ACCEPT事件在队列非空时触发。这非常简单,没有神秘之处。顺序根本与selected keys无关。所选键是ServerSocketChannel的键。
编辑:看起来你有一个严重的误解。考虑以下内容:
  1. 创建并注册一个ServerSocketChannel用于OP_ACCEPT
  2. 两个客户端同时连接。
  3. 现在存在一个SelectionKey,因此选择键集中恰好有一个ServerSocketChannel的选择键。
  4. 然后,在该键上处理isAcceptable()情况;接受一个或两个连接;为这些通道注册OP_READ
  5. 现在存在三个选择键,并且没有选择键集中的键,因为您已经清除了它。
  6. 两个客户端都发送了一些数据。
  7. 现在您在选择键集中有两个准备好读取的选择键。

可以吗?

我可以简单地这样做吗:

当然可以,但为什么呢?将简单的事情复杂化是没有任何优势的。请按照第一种方式操作。


哇!谢谢,这几乎就是我想听到的,但在我接受它作为答案之前,我想要问你更详细地描述以下句子:“accept() 方法返回队列中的下一个套接字,并且只要队列非空,OP__ACCEPT 就会触发。” 我关于 "++ availableClients" 的最后一句话只是因为我不明白可接受键和 accept() 之间的联系。看起来我们可以处理一个键,但接受另一个客户端,这让我感到困惑。提前致谢。 - Vadim Kirilchuk
1
正确。您处理ServerSocketChannel键,并接受一个新创建的SocketChannel,该通道连接到新客户端。其相关性在于当OP__ACCEPT触发时,accept()不会返回null - user207421
1
您IP地址为143.198.54.68,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。 - user207421
好的,我的意思是 SelectionKey selKey = (SelectionKey) it.next(); if (selKey.isAcceptable()) { server.accept() };所以,我们在这里有一些键并进行接受。你说客户端按照握手完成的顺序按照后备队列中放置的顺序被接受。这是否与selectedKeys().iterator()(仅限OP_ACCEPT)提供的顺序相同?还是可能不同? - Vadim Kirilchuk
2
天哪。并不会有两种类型的密钥,我也没在任何地方说过。我说的是“客户端密钥”,但其实并不存在这样的东西。只有一种类型的密钥:SelectionKey。通道有两种类型:ServerSocketChannelSocketChannel。我认为你在某个地方存在严重的误解,但我无法找出问题所在。 - user207421
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接