谈到Selector的具体使用,那么就要结合BIO,NIO的知识讲解,Selector使用在非阻塞模式NIO场景下,学习NIO之前先要了解BIO原理,下面我们一步步讲解
一.BIO(Blocking-IO)-阻塞IO模式
先来看一下BIO下服务端的代码
public static void main(String[] args) throws Exception {
//创建服务端通道
ServerSocketChannel ssc = ServerSocketChannel.open();
//监听8080端口
ssc.bind(new InetSocketAddress(8080));
ssc.configureBlocking(true);//设置成阻塞模式,默认也是true
List<SocketChannel>list = new ArrayList<>();
//创建大小为16字节的Bytebuffer缓冲区用于读取channel中的数据
ByteBuffer buffer = ByteBuffer.allocate(16);
while(true){
//如果有连接,就接收,返回一个SocketChannel
SocketChannel socketChannel = ssc.accept();
list.add(socketChannel);
for (SocketChannel sc : list) {
sc.read(buffer);
buffer.flip();//切换到读模式
//读取缓冲区中的数据
System.out.println(Charset.defaultCharset().decode(buffer));
}
}
}
阻塞的体现在于
1.当没有连接请求的时候,线程会阻塞在accept()方法等待,直到有连接过来
SocketChannel socketChannel = ssc.accept();
2.当没有SocketChannel 通道没有消息的时候,线程会阻塞在read()方法,直到通道中有消息,线程开始读取
sc.read(buffer);
也即所有通道(ServerSocketChannel和SocketChannel)的核心方法都是阻塞模式的
二.NIO(Non Blocking-IO)-非阻塞IO模式
我们可以通过设置参数修改通道为非阻塞模式,通过configureBlocking(false),把参数设置为false,false对应非阻塞,true对应阻塞,默认为true
1.对于服务端的ServerSocketChannel
ServerSocketChannel ssc = ServerSocketChannel.open();
ssc.configureBlocking(false);
2.对于客户端的SocketChannel
SocketChannel socketChannel = ssc.accept();
socketChannel.configureBlocking(false);
非阻塞的体现:
1.当服务端没有收到连接请求,线程不会一直阻塞在accpet()方法等待,而是继续向下执行代码流程
1.同理当通道没有消息处理时,线程也不会阻塞在read()方法等待,而是继续向下执行代码流程
三.Selector基于NIO的使用
Selector作用就是把所有的channel通道注册进去统一管理,然后调用Selector.select()监听通道是否有事件发生,如果没有就阻塞,有就通过selector.selectedKeys()获取所有事件的集合,遍历集合去处理这些通道的事件,请你尝试理解下面的服务端代码,注释很详细
public static void main(String[] args) throws Exception{
//创建服务器通道
ServerSocketChannel ssc = ServerSocketChannel.open();
//通道设置为非阻塞模式
ssc.configureBlocking(false);
ssc.bind(new InetSocketAddress(8080));
//轮询器
Selector selector = Selector.open();
//返回一个SelectionKey,可以通过它直到是哪一个channel有事件
SelectionKey ssck = ssc.register(selector, 0, null);
//设置为只关注连接事件OP_ACCEPT
ssck.interestOps(SelectionKey.OP_ACCEPT);
//创建一个字节缓冲区,大小为16字节
ByteBuffer buf = ByteBuffer.allocate(16);
while(true){
//作用:判断有无事件发生,如果没有事件发生,线程阻塞,直到有事件发生为止,当然这里可以设置阻塞时长,当阻塞超过timeout,解除阻塞状态,继续向下执行代码
selector.select();//selector.select(long timeout);
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
while(iterator.hasNext()){
//获取事件
SelectionKey key = iterator.next();
//处理完一个事件就移除一个,不然下次还在selectedKeys的集合中,需要手动移除
iterator.remove();
//如果是连接事件
try {
if (key.isAcceptable()) {
//获取连接通道
SocketChannel sc = (SocketChannel) key.channel();
//设置为非阻塞模式
sc.configureBlocking(false);
//加入到轮询器
SelectionKey sck = sc.register(selector, 0, null);
//设置为只监听读事件
sck.interestOps(SelectionKey.OP_READ);
} else if (key.isReadable()) {
SocketChannel sc = (SocketChannel) key.channel();
sc.read(buf);
buf.flip();//转换为读模式
System.out.println(Charset.defaultCharset().decode(buf));
buf.clear();
}
}catch (Exception e){
e.printStackTrace();
key.cancel();//事件要么被处理要么被取消,不然会一直在selector.select()的监听中
}
}
}
}
注意:
1.SelectionKey 事件要么被执行,要么被取消(调用key.cancel()方法),不然下一次调用Selector.select()旧的事件SelectionKey 会被认为没有处理,认为有事件要处理,就算没有新事件也不会阻塞
2.每次执行完一个事件SelectionKey,需要从selectedKeys()集合中删除这个key,因为集合不会自动移除这个key,需要手动调用remove()方法,不然下次湖北进行SelectionKey集合的遍历时,旧的key还会被重复执行.