Selectior(选择器)

JavaNIOSelector详解:多路复用与非阻塞IO
Selector是JavaNIO中的关键组件,它允许多个Channel在一个线程中进行非阻塞的多路复用,提高程序运行效率。Selector可以通过一个线程处理多个Channel上的事件,如连接、接受、读写等。使用Selector可以减少线程切换的开销,尤其适合高并发、高频度的网络服务。文章还介绍了Selector的获取、注册、常用方法以及NIO2中的AIO(异步非阻塞IO)概念。

讲解

选择器Selector是NIO中的重要技术之⼀。它与SelectableChannel联合使⽤实现了⾮阻塞的多路复⽤。使⽤它可以节省CPU资源,提⾼程序的运⾏效率。
 
"多路"是指:服务器端同时监听多个端⼝的情况。每个端⼝都要监听多个客户端的连接。
 
服务器端的多路复用效果
 
661cf16a2e804794bc17cd749f34dfac.png

 

使⽤了多路复⽤,只需要⼀个线程就可以处理多个通道,降低内存占⽤率,减少CPU切换时间,在⾼并发、⾼频段业务环境下有⾮常重要的优势
 
选择器Selector的获取和注册
概述:Selector被称为:选择器,也被称为:多路复⽤器,可以把多个Channel注册到⼀个Selector选择器上, 那么就可以实现利⽤⼀个线程来处理这多个Channel上发⽣的事件,并且能够根据事件情况决定Channel读写。这样,通过⼀个线程管理多个Channel,就可以处理⼤量⽹络连接了, 减少系统负担,提⾼效率。因为线程之间的切换对操作系统来说代价是很⾼的,并且每个线程也会占⽤⼀定的系统资源。所以,对系统来说使⽤的线程越少越好。
 
作⽤: ⼀个Selector可以监听多个Channel发⽣的事件, 减少系统负担 , 提⾼程序执⾏效率
 
Selector选择器的获取
 
Selector selector = Selector.open()
注册ChannelSelector
 
通过调⽤ channel.register(Selector sel, int ops)⽅法来实现
注册:
channel.configureBlocking(false);// 设置⾮阻塞
SelectionKey key =channel.register(selector,SelectionKey.OP_READ )
register()⽅法的第⼆个参数:是⼀个int值,意思是在通过Selector监听Channel时对什么事件感兴趣。可以监听四种不同类型的事件,⽽且可以使⽤SelectionKey的四个常量表示:
  1. 连接就绪--常量:SelectionKey.OP_CONNECT
  2. 接收就绪--常量:SelectionKey.OP_ACCEPT (ServerSocketChannel在注册时只能使⽤此项)
  3. 读就绪--常量:SelectionKey.OP_READ
  4. 写就绪--常量:SelectionKey.OP_WRITE
注意:对于ServerSocketChannel在注册时,只能使⽤OP_ACCEPT,否则抛出异常。
代码:
public class Test11 {
    public static void main(String[] args) throws IOException {
        //Selector(选择器)
        //案例演示,监听一个通道
        //获取ServerSocketChannel服务器通道对象
        ServerSocketChannel ssc1 = ServerSocketChannel.open();
        //绑定端口号
        ssc1.bind(new InetSocketAddress(7777));
        //设置非阻塞
        ssc1.configureBlocking(false);
        //获取Selector选择器对象
        Selector selector = Selector.open();
        //服务器通道的accept()交给选择器来处理
        //注册Channel到Selector选择器上
        ssc1.register(selector, SelectionKey.OP_ACCEPT);
    }

}
示例:服务器创建3个通道,同时监听3个端⼝,并将3个 通道注册到⼀个选择器中
 
public class Test12 {
    public static void main(String[] args) throws IOException {
        //服务器创建三个通道,同时监听三个端口,并将三个通道注册到一个选择器中

        //获取ServerSocketChannel服务器通道对象
        ServerSocketChannel ssc1 = ServerSocketChannel.open();
        //绑定端口号
        ssc1.bind(new InetSocketAddress(7777));

        //获取ServerSocketChannel服务器通道对象
        ServerSocketChannel ssc2 = ServerSocketChannel.open();
        //绑定端口号
        ssc2.bind(new InetSocketAddress(888));

        //获取ServerSocketChannel服务器通道对象
        ServerSocketChannel ssc3 = ServerSocketChannel.open();
        //绑定端口号
        ssc3.bind(new InetSocketAddress(9999));

        //设置阻塞
        ssc1.configureBlocking(false);
        ssc2.configureBlocking(false);
        ssc3.configureBlocking(false);

        //获取Selector选择器对象
        Selector selector = Selector.open();

        //把服务器通道的accept()交给选择器来处理
        //注册Channel到Selector选择器上
        ssc1.register(selector, SelectionKey.OP_ACCEPT);
        ssc2.register(selector, SelectionKey.OP_ACCEPT);
        ssc3.register(selector, SelectionKey.OP_ACCEPT);


    }
}

Selector的常用方法

  • Selector的select()方法:
  • 作用:服务器等待客户端连接的方法
  • 阻塞问题:
    • 在连接到第⼀个客户端之前,会⼀直阻塞
    • 当连接到客户端后,如果客户端没有被处理,该⽅法会计⼊不阻塞状态
    • 当连接到客户端后,如果客户端有被处理,该⽅法⼜会进⼊阻塞状态
        
public class Test13 {
    public static void main(String[] args) throws IOException {
        //Selector的常用方法
        //获取ServerSocketChannel服务器通道对象
        ServerSocketChannel ssc1 = ServerSocketChannel.open();
        //绑定端口号
        ssc1.bind(new InetSocketAddress(1234));

        //获取ServerSocketChannel服务器通道对象
        ServerSocketChannel ssc2 = ServerSocketChannel.open();
        //绑定端口号
        ssc2.bind(new InetSocketAddress(456));

        //获取ServerSocketChannel服务器通道对象
        ServerSocketChannel ssc3 = ServerSocketChannel.open();
        //绑定端口号
        ssc3.bind(new InetSocketAddress(789));

//        设置非阻塞
        ssc1.configureBlocking(false);
        ssc2.configureBlocking(false);
        ssc3.configureBlocking(false);

        //获取Selector选择器对象
        Selector selector = Selector.open();
        //把服务器通道的accept()交给选择器来处理
        //注册Channel到Selector选择器上
        ssc1.register(selector, SelectionKey.OP_ACCEPT);
        ssc2.register(selector, SelectionKey.OP_ACCEPT);
        ssc3.register(selector, SelectionKey.OP_ACCEPT);

        //死循环一直接受客户端的连接请求
        while (true){
            System.out.println(1);
            //服务器等待客户端的连接
            selector.select();//阻塞
            System.out.println(2);

            //处理客户端请求的代码--》暂时看不懂,先放着

            Set<SelectionKey> keySet = selector.selectedKeys();//存储所有的连接的服务器Channel对象
            for (SelectionKey key : keySet) {
                ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
                SocketChannel sc = ssc.accept();
                System.out.println("...开始处理,接受数据,代码省略。。。");
            }


        }

    }
}

Selector的selectedKeys()方法

public class Test14 {
    public static void main(String[] args) throws IOException {
        //Selector的selectedKeys()方法
        //获取已连接的所有的通道集合

        //获取ServerSocketChannel服务器通道对象
        ServerSocketChannel ssc1 = ServerSocketChannel.open();
        //绑定端口号
        ssc1.bind(new InetSocketAddress(7777));

        //获取ServerSocketChannel服务器通道对象
        ServerSocketChannel ssc2 = ServerSocketChannel.open();
        //绑定端口号
        ssc2.bind(new InetSocketAddress(7777));

        //获取ServerSocketChannel服务器通道对象
        ServerSocketChannel ssc3 = ServerSocketChannel.open();
        //绑定端口号
        ssc3.bind(new InetSocketAddress(7777));

        //设置非阻塞
        ssc1.configureBlocking(false);
        ssc2.configureBlocking(false);
        ssc3.configureBlocking(false);

        //获取Selector选择器对象
        Selector selector = Selector.open();

        //把服务器通道的accept()交给选择器来处理
        //注册Channel到Selector选择器上
        ssc1.register(selector, SelectionKey.OP_ACCEPT);
        ssc2.register(selector, SelectionKey.OP_ACCEPT);
        ssc3.register(selector, SelectionKey.OP_ACCEPT);

        // 获取所有被连接的服务器Channel对象的Set集合
        // 该Set集合中的元素类型是SelectionKey,该SelectionKey类其实就是对Channel的⼀个封装
        Set<SelectionKey> keySet = selector.selectedKeys();
        System.out.println("被连接的服务器对象有多少个:"+keySet.size() + "");//0
        //死循环一直接受客户端的连接请求
        while (true){
            System.out.println(1);
            //服务器等待客户端的连接
            selector.select();//阻塞
            System.out.println(2);
            System.out.println("被连接的服务器对象个数:"+keySet.size());//有多少个客户端连接服务器成功,就打印几


        }


    }
}

Selectorkeys()⽅法

public class Test15 {
    public static void main(String[] args) throws IOException {
      //Selector的keys()方法

        //获取已注册的所有通道集合

        //获取ServerSocketChannel服务器通道对象
        ServerSocketChannel ssc1 = ServerSocketChannel.open();
        //绑定端口号
        ssc1.bind(new InetSocketAddress(7777));
        //获取ServerSocketChannel服务器通道对象
        ServerSocketChannel ssc2 = ServerSocketChannel.open();
        //绑定端口号
        ssc2.bind(new InetSocketAddress(8888));

        //获取ServerSocketChannel服务器通道对象
        ServerSocketChannel ssc3 = ServerSocketChannel.open();
        //绑定端口号
        ssc3.bind(new InetSocketAddress(9999));

//        设置非阻塞
        ssc1.configureBlocking(false);
        ssc2.configureBlocking(false);
        ssc3.configureBlocking(false);

        //获取Selector选择器对象
        Selector selector = Selector.open();

        //把服务器通道的accept()交给选择器来处理
        //这侧Channel到Selector选择器上
        ssc1.register(selector, SelectionKey.OP_ACCEPT);
        ssc2.register(selector, SelectionKey.OP_ACCEPT);
        ssc3.register(selector, SelectionKey.OP_ACCEPT);

        //获取所有被连接的服务器Channel对象Set集合
        //该Set集合的元素类型是SelectionKey,该SelectionKey类其实就是对Channel的封装
        Set<SelectionKey> keySet = selector.selectedKeys();
        System.out.println("被连接的服务器对象有多少个:"+keySet.size() + "");//0
        //获取所有被注册的服务器Channel对象Set集合
        //该Set集合中的元素类型是SelectionKey,该SelectionKey类其实就是对Channel的一个封装
        Set<SelectionKey> keys  =selector.keys();
        System.out.println("被注册的服务器对象有多少个:"+keySet.size());//3

        //死循环一直接受客户端的连接请求
        while (true){
            System.out.println(1);
            //服务器等待客户的连接
            selector.select();//阻塞
            System.out.println(2);
            System.out.println("被连接的服务器对象个数:"+keySet.size());//有多少客户端链接服务器成功就打印几
            System.out.println("被注册的服务器对象个数:"+keys.size());//选择器上注册了多少个服务器Channel,就打印几




        }


    }
}
Selector多路复用
public class Test16 {
    public static void main(String[] args) throws IOException {
        //Selector多路复用
        //创建3个服务器Channel对象,并绑定端口号
        ServerSocketChannel scc1 = ServerSocketChannel.open();
        scc1.bind(new InetSocketAddress(7777));

        ServerSocketChannel scc2 = ServerSocketChannel.open();
        scc1.bind(new InetSocketAddress(8888));

        ServerSocketChannel scc3 = ServerSocketChannel.open();
        scc1.bind(new InetSocketAddress(9999));

        //把三个服务器Channel对象设置成非阻塞
        scc1.configureBlocking(false);
        scc2.configureBlocking(false);
        scc3.configureBlocking(false);

        //3.获得Selector选择器
        Selector selector = Selector.open();

        //4.把3个服务器Channel对象对象注册到同一个Selector选择器上,指定监听事件
        scc1.register(selector, SelectionKey.OP_ACCEPT);
        scc2.register(selector, SelectionKey.OP_ACCEPT);
        scc3.register(selector, SelectionKey.OP_ACCEPT);
        //死循环去等待客户端的连接
        while (true){
            //服务器等待客户端连接
            System.out.println(1);
            selector.select();

            //获取所有被连接的服务器Channel对象Set集合
            Set<SelectionKey> keySet = selector.selectedKeys();//2
            // 7.循环遍历所有被连接的服务器Channel对象,获取每⼀个被连接的服务器Channel对象
            for (SelectionKey key : keySet){
                ServerSocketChannel ssc = (ServerSocketChannel)key.channel();
                // 9.处理客户端的请求
                // 9.1 获取连接的客户端对象
                SocketChannel sc = ssc.accept();
                //创建ByteBuffer缓冲数组
                ByteBuffer b = ByteBuffer.allocate(1024);
                //读取数据
                int len = sc.read(b);
                //打印输出
                System.out.println(new String(b.array(),0,len));
                //释放资源
                sc.close();


            }


        }





    }
}

NIO2-Aio(异步、非阻塞)

AIO概述
同步,异步,阻塞,⾮阻塞概念回顾
 
65d4a91d8c7f4a5ca819945826cb6b8e.png

 

AIO相关类和⽅法介绍
 
AIO是异步IO的缩写,虽然NIO在⽹络操作中,提供了⾮阻塞的⽅法,但是NIO的IO⾏为还是同步的。对于NIO来说,我们的业务线程是在IO操作准备好时,得到通知,接着就由这个线程⾃⾏进⾏IO操作,IO操作本身是同步的。
 
JDK1.7中,这部分内容被称作NIO.2---->AIO,主要在Java.nio.channels包下增加了下⾯四个异步通道:
 
  • AsynchronousSocketChannel
  • AsynchronousServerSocketChannel
  • AsynchronousFileChannel
  • AsynchronousDatagramChannel
在AIO编程中,发出⼀个事件(accept read write.connect等)之后要指定事件处理类(回调函数),AIO中的事件处理类是CompletionHandler<V,A>,这个接⼝定义了如下两个⽅
法,分别在异步操作成功和失败时被回调。
void completed(V result, A attachment); 成功
 
void failed(Throwable exc, A attachment);
 
AIO 异步⾮阻塞连接
分析
  • 获取AsynchronousServerSocketChannel对象,绑定端⼝
  • 异步接收客户端请求
  • void accept(A attachment,CompletionHandler<AsynchronousSocketChannel,?super A> handler)
  • 第⼀个参数: 附件,没啥⽤,传⼊null即可
  • 第⼆个参数: CompletionHandler抽象类 ,AIO中的事件处理类
  • void completed(V result, A attachment);异步连接 成功,就会⾃动调⽤这个⽅法
  • void failed(Throwable exc, A attachment);异步连接失败,就会⾃动调⽤这个⽅法
服务器端:
 
public static void main(String[] args) throws
IOException {
 //服务器异步的连接⽅法
 //创建对象
 AsynchronousServerSocketChannel assc =AsynchronousServerSocketChannel.open();
 //绑定端⼝
 assc.bind(new InetSocketAddress(8000));
 //【异步⾮阻塞】⽅式!!!!!连接客户端
 System.out.println("11111111111");
 assc.accept(null, newCompletionHandler<AsynchronousSocketChannel,
Object>() {
 @Override
 //回调函数,当成功连接了客户端之后,会⾃动回来调⽤这个⽅法
public voidcompleted(AsynchronousSocketChannel result,Object attachment) {
 //completed是完成,如果连接成功会⾃动调⽤这个⽅法
 
System.out.println("completed");
 }
 @Override
 public void failed(Throwable exc,Object attachment) {
 //failed是失败,如果连接失败会⾃动调⽤这个⽅法
 }
 });
 System.out.println("22222222222");
 //写⼀个死循环让程序别结束(因为程序结束了⽆法演示效果)
 while(true){
 }
 }
-AIO 异步⾮阻塞连接和异步
分析
 
  • 获取AsynchronousServerSocketChannel对象,绑定端⼝
  • 异步接收客户端请求
  • 在CompletionHandler的completed⽅法中异步读数据
//异步⾮阻塞连接和读取
 public static void main(String[] args)throws IOException {
 //创建对象
 AsynchronousServerSocketChannel assc =AsynchronousServerSocketChannel.open();
 //绑定端⼝
assc.bind(new InetSocketAddress(8000));
 //异步⾮阻塞连接!!!!
 //第⼀个参数是⼀个附件
 System.out.println(1);
 assc.accept(null, newCompletionHandler<AsynchronousSocketChannel,Object>() {
 @Override
 public void completed(AsynchronousSocketChannel s, Objectattachment) {
 //如果连接客户端成功,应该获取客户端发来的数据
 //completed()的第⼀个参数表示的是Socket对象.
 System.out.println(5);
 //创建数组
 ByteBuffer buffer =ByteBuffer.allocate(1024);
 //异步⾮阻塞读!!!!!
 System.out.println(3);
 s.read(buffer, null, new CompletionHandler<Integer, Object>() {
 @Override
 public void completed(Integer len, Object attachment) {
 //读取成功会⾃动调⽤这个⽅法
 //completed()⽅法的第⼀个参数是read()读取到的实际个数

//打印数据
 System.out.println(6);
 System.out.println(new String(buffer.array(),0,len));
 }
 @Override
 public voidfailed(Throwable exc, Object attachment) {
 }
 });
 System.out.println(4);
 }
 @Override
 public void failed(Throwable exc,Object attachment) {
 }
 });
 System.out.println(2);
 //让程序别结束写⼀个死循环
 while(true){
 }
 }

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值