介绍
nio 网络编程,需要用 Selector
,相当于 监听器,可以 监听 channel
通道 的 某个事件
提示: 在 nio 中,客户端与服务器端的连接,称之为:channel
通道
作用
多个 channel
可以 注册 到同一个 Selector
,让 Selector
监听指定的 事件,当某个通道有事件发生时,可获取事件并做相应处理。
如:当某个通道有新数据时,才会进行读写
这样就可以用 一个 Selector
,管理多个 channel
,即:处理多个客户端连接
非阻塞通道
上图中的 channel
通道是 必须是 非阻塞的,不会像 bio 那样,一直阻塞,而是有数据时才进行读写,没有数据时,线程可以进行其他任务(因为不会阻塞)。
理解:相当于渣男渣女,当正牌对象在外地时,不会一直等待(没有数据,不会阻塞),而是跟别人处对象(处理其他任务)。一个人可以跟多人处对象,效率高
优点
只需更少线程就可以处理多个 channel
(jdk的源码中,每注册 1023
个通道,selector
会创建新的线程)
创建对象
Selector selector = Selector.open()
注册 Channel
为了将 channel
和 Selector
搭配使用,必须将 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