java nio Socket网络编程-Selector

介绍

nio 网络编程,需要用 Selector,相当于 监听器,可以 监听 channel 通道 的 某个事件

提示: 在 nio 中,客户端与服务器端的连接,称之为:channel 通道

作用

多个 channel 可以 注册 到同一个 Selector,让 Selector 监听指定的 事件,当某个通道有事件发生时,可获取事件并做相应处理。

如:当某个通道有新数据时,才会进行读写

这样就可以用 一个 Selector 管理多个 channel,即:处理多个客户端连接

非阻塞通道

上图中的 channel 通道是 必须是 非阻塞的,不会像 bio 那样,一直阻塞,而是有数据时才进行读写,没有数据时,线程可以进行其他任务(因为不会阻塞)。

理解:相当于渣男渣女,当正牌对象在外地时,不会一直等待(没有数据,不会阻塞),而是跟别人处对象(处理其他任务)。一个人可以跟多人处对象,效率高

优点

只需更少线程就可以处理多个 channel(jdk的源码中,每注册 1023 个通道,selector 会创建新的线程)

创建对象

Selector selector = Selector.open()

注册 Channel

为了将 channelSelector 搭配使用,必须将 channel 注册到 selector

serverChannel.register(selector, SelectionKey.OP_ACCEPT);

SelectionKey.OP_ACCEPT 表示事件,封装在 SelectionKey 类中

注意:

channel 必须是非阻塞模式,通过下面代码设置:

channel.configureBlocking(false);

选取通道

一旦向 Selector 注册了一或多通道,就可调用 select() 方法,返回 某事件(如:连接、接受、读或写)已经准备就绪的那些通道

select()

阻塞,直到至少有一个channel在符合注册的事件

selector.select();

返回值:返回值不是已就绪的channel的总数,而是从上一个select() 调用之后,进入就绪态的channel的数量。
之前的调用中就绪的,并在本次调用中仍就绪的channel不会被计入。
在前一次调用中已就绪,但已不再处于就绪态的通道也不会计入。这些channel可能仍然在SelectionKey集合中,但不会被计入返回值中

所以返回值可能是 0

select(long timeout)

select() 一样,只是规定了最长会阻塞 timeout 毫秒(参数)

selectNow()

不会阻塞,立刻返回。若自从上一次选择操作后,没有channel可选择,则此方法直接返回 0

selectedKeys()

一旦调用 select() 系列方法,并且返回值 大于0,表明有一个或更多个通道就绪了,然后可以通过下面代码,获取就绪通道:

Set<SelectionKey> selectionKeys = this.selector.selectedKeys();

通常通过下面方式进行遍历:

Set<SelectionKey> selectionKeys = this.selector.selectedKeys();
Iterator ite = selectionKeys.iterator();
while (ite.hasNext()) {
    SelectionKey key = (SelectionKey) ite.next();'
    // 通过强转成 ServerSocketChannel 通道,可能报错,具体与操作相关
    // ServerSocketChannel server = (ServerSocketChannel) key.channel();
    // 通过强转成 SocketChannel 通道,可能报错,具体与操作相关
    // SocketChannel channel = (SocketChannel) key.channel();
}

关键代码例子

// 轮询访问selector
while (true) {
    //当注册的事件到达时,方法返回;否则,该方法会一直阻塞
    int select = selector.select(2000);
    if(select == 0){
        continue;
    }
    // 获得selector中选中的项的迭代器,选中的项为注册的事件
    Iterator ite = this.selector.selectedKeys().iterator();
    while (ite.hasNext()) {
        SelectionKey key = (SelectionKey) ite.next();
        // 删除已选的key,以防重复处理
        ite.remove();

        if (key.isAcceptable()) { // 接受客户端连接就绪

            ServerSocketChannel server = (ServerSocketChannel) key.channel();
        } else if (key.isConnectable()) { // 连接准备就绪

        } else if (key.isReadable()) { // 读就绪
            SocketChannel channel = (SocketChannel) key.channel();
        } else if (key.isWritable()) { // 写就绪
            SocketChannel channel = (SocketChannel) key.channel();
        }

    }

}

参考:
https://zhuanlan.zhihu.com/p/376074271
https://blog.csdn.net/weixin_42762133/article/details/100040141


原文出处:https://malaoshi.top/show_1IX4tFP1gIOb.html