Netty框架之Selector使用详解

谈到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还会被重复执行.

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值