0
点赞
收藏
分享

微信扫一扫

NIO 基础组件之 Selector



生活的道路一旦选定,就要勇敢地走到底,决不回头 —— 左拉


NIO 基础组件之 Selector

Selector 释义

    Selector 是Java NIO中能够检测一到多个NIO的通道,并能够知道通道是否有为例如读写事件做好准备的组件。这样,一个单独的线程可以管理多个通道,从而管理多个网络连接
NIO 基础组件之 Selector_非阻塞
    如果没有selector 这个组件的话,那么就是客户端建立一个请求,那么服务器端就会建立一个相对应的线程进行处理,那么就会非常的耗费性能,而且还是阻塞的那种方式,还会涉及到线程的切换等等,所以有了Selector之后就不用大量的线程进行处理了,直接用一个Selector进行轮训多个请求,会大大的减少资源,如果一个Selector处理不过来那么就用多个进行处理

Selector 跟 Channel 的关系

    Selector 和 Channel 是一对多的关系,一个 Selector 可以为多个 Channel 服务,监听它们准备好的事件,上面的图中可以看到一个selector对应多个通道
这种场景其实还是很常见的在生活中,比如厨师、服务员、客户人员这三个角色,厨师就相当于服务端,服员其实就是Selector选择器,客户其实就是客户端:

  1. 如果没有服务员的话,那么厨师需要和客户进行直接对接,问要什么菜,之后厨师去做,做好厨师端上来,此时整个过程非常的耗时,如果只有一个厨师那么其他的客户都得等着那么这饭店不用开了,效率太低了
  2. 如果有服务员这个角色之后,那么服务员进行客户服务,点好菜给到厨师,厨师去做,服务员进行另一个客户服务,这样一来就不需要一直等着,等菜好了服务员去把菜给客户。后续客户有什么需求直接对接服务员就可以了,如果一个服务员服务不过来的情况下可以多雇佣几个服务员就可以了

Selector 使用步骤

1. 创建 Selector

  1. 第一种创建方式
Selector selector = Selector.open(); // 创建一个Selector
  1. 第二种创建方式
SelectorProvider.openSelector(); // SelectorProvider 是自定义的。不常用

2. 注册事件到 Selector

    注册事件到 Selector 上,当然了,我们是非阻塞式的,所以,注册之前还要将channel通道先设置为非阻塞式的,channel 注册到 Selector 上之后,返回了一个叫作 SelectionKey 的对象,,这个SelectionKey 其实就是客户点的菜单一样,通过这个菜单去取餐,所以这个 SelectionKey 相当于 Selector 和 channel 的绑定映射,并保存着你感兴趣的事件

channel.configureBlocking(false);// 设置非阻塞的 
SelectionKey key = channel.register(selector, SelectionKey.OP_READ);// 注册事件

什么是事件

    通道触发了一个事件意思是该事件已经就绪。

  1. 所当某个 channel 成功连接到另一个服务器称为连接就绪也就是链接事件
  2. 一个 server socket channel 准备好接收新进入的连接称为接收就绪也就是接受连接事件
  3. 一个有数据可读的通道可以说是读就绪也就是读事件
  4. 等待写数据的通道可以说是写就绪也就是写事件

用常量表示就是下面的这四种方式

  1. Connect

    连接事件:SelectionKey.OP_CONNECT

  1. Accept

    接受连接事件:SelectionKey.OP_ACCEPT

  1. Read

    读事件:SelectionKey.OP_READ

  1. Write

    写事件:SelectionKey.OP_WRITE
    如果你对不止一种事件感兴趣,那么可以用“位或”操作符将常量连接起来,如下:

int interestSet = SelectionKey.OP_READ | SelectionKey.OP_WRITE;

3. 轮询事件

    select()只有询问的意思,加上循环才是轮询的意思,所以在写代码的时候我们一般都是用的 while 死循环

while(true) {
selector.select(); // 一直阻塞直到有感兴趣的事件
}

    这里的select()方法会一直阻塞在这块,所以select的衍生方法还有其他的比如selectNow()、select(时间限制)这种

  1. select() 阻塞到至少有一个通道在你注册的事件上就绪才返回【某个线程调用select()方法后阻塞了,即使没有通道已经就绪,也有办法让其从select()方法返回。只要让其它线程在第一个线程调用select()方法的那个对象上调用Selector.wakeup()方法即可。阻塞在select()方法上的线程会立马返回】
  2. select(long timeout)与select()一样,除了最长会阻塞timeout毫秒,有一个事件的限制
  3. selectNow() 不会阻塞,不管什么通道就绪都立刻返回

4. 返回就绪事件

    通过 selector.selectedKeys() 返回就绪的事件,然后遍历这些事件就可以拿到想要的结果,返回的结果是一个可迭代的集合,之后通过这个集合进行相应的事件类型判断进行后续的处理

Set selectedKeys = selector.selectedKeys();// 返回就绪的事件
Iterator keyIterator = selectedKeys.iterator(); // 迭代遍历感兴趣事件
while(keyIterator.hasNext()) {
SelectionKey key = keyIterator.next();
if(key.isAcceptable()) { // 接受连接事件已就绪
....
} else if (key.isConnectable()) { // 连接事件已就绪
....
} else if (key.isReadable()) { // 读事件已就绪
....
} else if (key.isWritable()) { // 写事件已就绪
....
}
keyIterator.remove();// 防止多次进行处理,需要将迭代过的事件进行移除
}

总结

    其实 Selector 组件的存在主要的作用就是减少了线程的开销,降低了资源的消耗,一个线程轮询多个客户端的请求,这样减少了线程开销并提高了并发量和处理量,之后就是讲解了Selector 与channel通道的关系,是一对多的关系,一个selector 对应多个channel通道,之后又讲解了使用方式,就是先创建一个选择器selector,之后将channel设置成非阻塞的,再将这个channel注册到selector上面,之后进行select()轮询,如果有时间就绪就进行对应的事件处理。后续我们会进行一个NIO的小案例讲解

举报

相关推荐

0 条评论