4. Selector 基本应用

4. Selector 基本应用

  • 4.1. Selector 概述


Selector 是 Java NIO 中实现多路复用技术的关键,多路复用技术又是提高通讯性能的一个重要因素。项目中可以基于 selector 对象实现了一个线程管理多个 channel 对象,多个网络链接的目的。例如:在一个单线程中使用一个Selector 处理 3 个 Channel,如图所示:

为什么使用 Selector?

仅用单个线程来处理多个 Channel 的好处是:只用一个线程处理所有的通道,可以有效避免线程之间上下文切换带来的开销,而且每个线程都要占用系统的一些资源(如内存)。因此,使用的线程越少越好。

  • 4.2. Selector 基本应用


  • Selector 的创建

通过调用 Selector.open()方法创建一个 Selector,如下:

Selector selector = Selector.open();
  • 向 Selector 注册通道

为了将Channel 和 Selector 配合使用,必须将 channel 注册到selector 上。通过 SelectableChannel.register()方法来实现,如下:

channel.configureBlocking(false);
SelectionKey key = channel.register(selector,Selectionkey.OP_READ);

channel 与 Selector 一起使用时,Channel 必须处于非阻塞模式下。这意味着不能将 FileChannel 与 Selector 一起使用,因为 FileChannel 不能切换到非阻塞模式。而套接字通道都可以。
注意 register()方法的第二个参数。这是一个“interest 集合”,意思是在通过 Selector 监听 Channel 时对什么事件感兴趣。可以监听四种不同类型的事件:

  • 1) Connect
  • 2) Accept
  • 3) Read
  • 4) Write

通道触发了一个事件意思是该事件已经就绪。所以,某个 channel 成功连接到另一个服务器称为“连接就绪”。一个 server socket channel 准备好接收新进入的连接称为“接收就绪”。一个有数据可读的通道可以说是“读就绪”。等待写数据的通道可以说是“写就绪”。

这四种事件用 SelectionKey 的四个常量来表示:

  • 1) SelectionKey.OP_CONNECT
  • 2) SelectionKey.OP_ACCEPT
  • 3) SelectionKey.OP_READ
  • 4) SelectionKey.OP_WRITE

代码示例:

服务端实现:

    @Test
    public void testServerSelector() throws Exception {
        //1. 获取通道
        ServerSocketChannel ssChannel = ServerSocketChannel.open();
        //2. 切换非阻塞模式
        ssChannel.configureBlocking(false);
        //3. 绑定连接
        ssChannel.bind(new InetSocketAddress(9898));
        //4. 获取选择器
        Selector selector = Selector.open();
        //5. 将通道注册到选择器上, 并且指定“监听接收事件”
        ssChannel.register(selector, SelectionKey.OP_ACCEPT);
        //6. 轮询式的获取选择器上已经“准备就绪”的事件
        while (selector.select() > 0) {
            //7. 获取当前选择器中所有注册的“选择键(已就绪的监听事件)”
            Iterator<SelectionKey> it = selector.selectedKeys().iterator();
            while (it.hasNext()) {
                //8. 获取准备“就绪”的是事件
                SelectionKey sk = it.next();
                //9. 判断具体是什么事件准备就绪
                if (sk.isAcceptable()) {
                    //10. 若“接收就绪”,获取客户端连接
                    SocketChannel sChannel = ssChannel.accept();
                    //11. 切换非阻塞模式
                    sChannel.configureBlocking(false);
                    //12. 将该通道注册到选择器上
                    sChannel.register(selector, SelectionKey.OP_READ);
                } else if (sk.isReadable()) {
                    //13. 获取当前选择器上“读就绪”状态的通道
                    SocketChannel sChannel = (SocketChannel) sk.channel();
                    //14. 读取数据
                    ByteBuffer buf = ByteBuffer.allocate(1024);
                    int len = 0;
                    while ((len = sChannel.read(buf)) > 0) {
                        buf.flip();
                        System.out.println(new String(buf.array(), 0, len));
                        buf.clear();
                    }

                }
            }
            //15. 取消选择键 SelectionKey
            it.remove();
        }
    }

客户端实现:(这里我一开始也是用的@Test来使方法作为服务端,但是idea貌似不支持Scanner的控制台输入,所以就改为main方法就可以了)

public static void main(String[] args) throws Exception {
        //1. 获取通道
        SocketChannel sChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 9898));
        //2. 切换非阻塞模式
        sChannel.configureBlocking(false);
        //3. 分配指定大小的缓冲区
        ByteBuffer buf = ByteBuffer.allocate(1024);
        //4. 发送数据给服务端
        Scanner scan = new Scanner(System.in);
        while (scan.hasNext()) {
            String str = scan.next();
            buf.put((new Date().toString() + "\n" + str).getBytes());
            buf.flip();
            sChannel.write(buf);
            buf.clear();
        }
        //5. 关闭通道
        sChannel.close();
    }

程序运行图示:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值