一,Selector选择器工作模型。
之前用管道(chennal)实现了一个阻塞式模型。那个模型中。服务端建立连接后里面就会分配一个线程等待消息的到达。由于 不知道什么时候消息能到达客户端,所以一直阻塞等待。是否可以等消息到达再分配线程处理?这就要说道selector的作用了。只要将管道设置为非阻塞模式。如何注册至selector。当消息到达就会得到通知。
![](https://i-blog.csdnimg.cn/blog_migrate/460e0d67df07313fd484c5b3e18fba4c.png)
左边的管道没有注册至Selector,需要阻塞等待消息,右边已注册至Selector当消息 到达时才分配线程。避免了线程阻塞。
Selector是借助他的核心组件来实现这个功能的。
二,选择器核心组件.
Selector的核心组件主要有3个:管道(SelectableChannel)、选择器(Selector)、选择键(SelectorKey)。
Selector会不断地轮询注册在其上的Channel,如果某个Channel上面发生读或者写事件,这个Channel就处于就绪状态,会被Selector轮询出来,然后通过SelectionKey可以获取就绪Channel的集合,进行后续的IO操作。但是不是所有的channel都支持向选择器注册,只有SelectableChannel子类才可以。当管道注册到Selector后就会返回一个Key,通过它就可以获取到关联的管道。
![](https://i-blog.csdnimg.cn/blog_migrate/882e05dd36ba6072bb8a709904a31016.png)
这三个组件的作用分别是:
1.SelectableChannel
核心功能有两个,configureBlocking 设置阻塞模式,默认为true,向选择器注册前必须置为false。第二就是是调用register方法注册到指定管道,并且指定要监听的事件。可选的事件有:CONNECT(建立连接)、ACCEPT(接受连接)、READ(可读)、WRITE(可写) 。但并非所有管道都支持这四事件,可通过validOps()来查看当前管道支持哪些事件。
可以监听的事件类型(可使用SelectionKey 的四个常量表示):
- 读: SelectionKey.OP_READ (int-1)
- 写: SelectionKey.OP_WRITE (int-4)
- 连接: SelectionKey.OP_CONNECT(int-8)
- 接收: SelectionKey.OP_ACCEPT (int-16)
(不同的channel支持的事件不同)
![](https://i-blog.csdnimg.cn/blog_migrate/0d0cd61686e7a4314387e37fdbcea505.png)
![](https://i-blog.csdnimg.cn/blog_migrate/b429c686d038e81ed54419e1a18930c0.png)
Selector 可以同时监控多个SelectableChannel 的IO 状况。也就是说,利用Selector 可使一个单独的线程管理多个Channel。
管道注册至Selector之后 会生成一个键(SelectorKey) 该键维护在Selector的keys中。并通过调用select 方法进行刷新,如果返回数大于0表示有指定数量的键状态发生了变更。
select():有键更新,立马返回。否则会一直阻塞直到有键更新为止。
select(long ):有键更新,立马返回。否则会阻塞参数指定毫秒时。
selectNow():无论有没有键更新都会立马返回,如果有键更新,接下来就可以调用selectedKeys 获取更新的键集了。
SelectionKey
表示SelectableChannel 和Selector 之间的注册关系。每次向选择器注册通道时就会选择一个事件(选择键)。选择键包含两个表示为整数值的操作集。操作集的每一位都表示该键的通道所支持的一类可选择操作。SelectionKey键用于关联管道与选择器,并监听维护管道1至多个事件,监听事件可在注册时指定,也可后续通过调用SelectionKey.interestOps 来改变。
// 监听读取事件
key=socketChannel.register(selector, SelectionKey.OP_READ);
// 同时监听读写事件
key.interestOps(SelectionKey.OP_READ|OP_WRITE);
同时SelectionKey 也有4个常量表示4个事件。但并非所有管道都支持这四个事件如:
ServerSocketChannel 仅支持OP_ACCEPT事件,
SocketChannel 支持 OP_CONNECT、OP_READ、OP_WRITE 三个事件查看管道支持哪些事件
可通过 validOps() 反回的值 然后进行‘&’运算 判断如
//表示该管道支持 OP_CONNECT 事件监听
socketChannel.validOps()&SelectionKey.OP_CONNECT != 0
此外Key还有如下主要功能:
1.channel() 获取管道
2.判断状态
a.isAcceptable() 管道是否处于Accept状态
b.isConnectable 管道是否处于连接就绪状态
c.isReadable 管道是否处于读取就绪状态
d.isWritable 管道是否处于写就续状态
3.isValid() 判断该键是否有效,管道关闭、选择器关闭、键调用cancel()方法都会导致该键无效。
4.cancel()取消管道注册(不会直接关闭管道)
![](https://i-blog.csdnimg.cn/blog_migrate/e26d8d767a4fde118c30eba1408ae4c0.png)
处理该键,从键中获取管道并读取消息
![](https://i-blog.csdnimg.cn/blog_migrate/1bf10268e1a7abda098ee0720045705b.png)
重点关注一下第 4、5步,如没有执行第4步(即读取管道中的消息),那么该管道目前还是处于读就续状态。当调用选择器select() 方法时会立马返回,造成死循环。如果执行了第四步但没有执行第5步,会导致其留存在选择集中,从而重复进行处理。