nio 服务端接收连接请求时,为什么 select 方法会返回 0?

问题描述

使用Java NIO 编写一个Server。端口是9999,使用 select方法阻塞返回,超时时间为2秒。

    Selector selector = Selector.open();
    ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
    serverSocketChannel.bind(new InetSocketAddress(9999)).configureBlocking(false);
    serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

    for (; ; ) {
        int select = selector.select(2_000);
        if (select == 0) {
            System.out.println("no event");
            continue;
        }
        System.out.println("select = " + select);
   }

在没有连接请求时,每隔2秒钟正常打印:

no event
no event
no event

使用 telnet 尝试发起请求后,控制台快速(select 方法立即返回)打印:

no event
no event
no event

没有走到下面一行代码:

System.out.println("select = " + select);

但是在 for 循环里加上了下面几行代码后,代码变成下面的样子,于是情况发生了变化:

        for (; ; ) {
            int select = selector.select();
            if (select == 0) {
                System.out.println("no event");
                continue;
            }
            System.out.println("select = " + select);
            Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
            System.out.println("iterator = " + iterator.hasNext());
            while (iterator.hasNext()) {
                SelectionKey key = iterator.next();
                iterator.remove();
            }
        }

此时返回的的 select 值已经不是0了,控制台开始打印:

    select = 1
    iterator = true
    select = 1
    iterator = true
    select = 1
    iterator = true
    select = 1
    iterator = true

问题:

select 返回值为什么会受到后面几行遍历 key 事件代码的影响?而且我测试过,如果不在此 while 循环中 remove 事件,则 select 返回值依旧是0,但是 remove 后,返回值则为 1.

        while (iterator.hasNext()) {
            SelectionKey key = iterator.next();
            iterator.remove();
        }
        
     


阅读 2.8k
2 个回答
新手上路,请多包涵

还有一个问题就是select 开始是超时阻塞返回,但是如果通过 telnet 发起连接后,则快速返回,且返回值为 0,why

新手上路,请多包涵

如果你用过epoll,对于这个问题应该就容易理解,我没有仔细阅读过java源码,但是根据测试,我猜测selector的运行机制是这样的:
在selector中维护一个Set<SelectionKey> keys,当selector中有socket可读或者可写(看你注册的是什么事件了),select函数会立马返回,否则由你设置的超时参数决定。并且返回值是keys的变化数量。所以可以解释你的几个问题:

1.在没有连接请求时,每隔2秒钟正常打印
    因为没有新的连接请求,所以selector中的套接字没有任何一个是符合OP_ACCEPT这个interest点,所以select函数会在2000毫秒后返回。
2.使用 telnet 尝试发起请求后,控制台快速(select 方法立即返回),没有执行System.out.println("select = " + select);
    可能是控制台刷新得太快了,你没观察到,我觉得在telnet连接到服务器的时候这行代码会执行一次,因为selector中的套接字有一个已经可以进行ACCEPT操作,所以select函数会立马返回,此时keys为一个空集合,于是selector将serverSocketChannel的selectionKey添加到keys中,因为这次添加了一个selectionKey,所以会返回1,于是执行了System.out.println("select = " + select);然后进入下轮循环,在执行select的时候,发现serverSocketChannel可以执行accept操作,但是keys中已经有这个key了,所以立马返回0,于是就疯狂刷新no event
3.和上面类似,因为发现serverSocketChannel可以执行accept操作,但是keys中没有这个key(被你移除了),所以添加到keys中,并返回1。

对于上面的问题2和问题3疯狂在控制台打印字符的原因就是:

serverSocketChannel可以进行accept操作,但是你没有执行accept,所以每次调用select都会立马返回,如果想解决这个问题,接受这个连接即可。
撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题