Java NIO Selector

Selector简介

Selector和Channel关系

Selector一般称为选择器,也可以翻译为多路复用器。它是Java NIO核心组件中的一个,用于检查一个或多个NIO Channel(通道)的状态是否处于可读、可写。如此可以实现单线程管理多个channels,也就是可以管理多个网络连接。

在这里插入图片描述
使用Selector的好处在于:使用更少的线程来就可以处理通道了,相比使用多个线程,避免了线程上下文切换带来的开销。

SelectableChannel(可选择通道)

(1)不是所有的Channel都可以被Selector复用的。比方说,FileChannel就不能被选择器复用。判断一个Channel能被Selector复用,有一个前提:判断它是否继承了一个抽象类SelectableChannel。如果继承了SelectableChannel,则可以被复用,否则不能。
(2)SelectableChannel类提供了实现通道的可选择性所需要的公共方法。它是所有支持就绪检查的通道类的父类。所有socket通道,都继承了SelectableChannel类都是可选择的,包括从管道对象中获得的通道。而FileChannel类,没有继承SelectableChannel,因此不是可选通道。
(3)一个通道可以被注册到多个选择器上,但对每个选择器而言只能被注册一次。通道和选择器之间的关系,使用注册的方式完成。SelectableChannel可以被注册到Selector对象上,在注册的时候,需要指定通道的哪些操作,是Selector感兴趣的。

在这里插入图片描述

Channel注册到Selector

(1)使用Channel.register(Selector sel, int ops)方法,将一个通道注册到一个选择器时。第一个参数,指定通道要注册的选择器。第二个参数指定选择器需要查询的通道操作。
(2)可以供选择器查询的通道操作,从类型来分,包括以下四种:

  • 可读:SelectionKey.OP_READ
  • 可写:SelectionKey.OP_WRITE
  • 连接:SelectionKey.OP_CONNECT
  • 接收:SelectionKey.OP_ACCEPT

如果Selector对通道的多操作类型感兴趣,可以用“位或”操作符来实现:
比如:int key = SelectionKey.OP_READ | SelectionKey.OP_WRITE;
(3)选择器查询的不是通道的操作,而是通道的某个操作的一种就绪状态。什么是操作的就绪状态?一旦通道具备完成某个操作的条件,表示该通道的某个操作已经就绪,就可以被Selector查询到,程序可以对通道进行对应操作。比方说,某个SocketChannel通道可以连接到一个服务器,则处于“连接就绪”(OP_CONNECT)。再比方说,一个ServerSocketChannel服务器通道准备好接收新进入的连接,则处于“接受就绪”(OP_ACCEPT)状态。还比方说,一个有数据可读的通道,可以说是“读就绪”(OP_READ)。一个等待写数据的通道可以说是“写就绪”(OP_WRITE)。

SelectionKey(选择键)

(1)Channel注册到后,并且一旦通道处于某种就绪状态,就可以被选择器查询到。这个工作使用选择器Selector的select()方法完成。select方法的作用,对感兴趣的通道操作,进行就绪状态的查询。
(2)Selector可以不断的查询Channel中发生的操作的就绪状态。并且挑选感兴趣的操作就绪状态。一旦通道有操作的就绪状态达成,并且是Selector感兴趣的操作,就会被Selector选中,放入选择键集合中。
(3)一个选择键,首先是包含了注册在Selector通道操作的类型,比方说SelectionKey.OP_READ。也包含了特定的通道与特定的选择器之间的注册关系。

开发应用程序是,选择键是编程的关键。NIO的编程,就是根据对应的选择键,进行不同的业务逻辑处理。

(4)选择键的概念,和事件的概念比较相似。一个选择键类似监听器模式里面的一个事件。由于Selector不是事件触发的模式,而是主动取查询的模式,所以不叫事件Event,而是叫SelectionKey选择键。

Selector的使用方法

Selector的创建

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.util.Iterator;
import java.util.Set;

public class SelectorDemo1 {

    public static void main(String[] args) throws IOException {
        //创建selector
        Selector selector = Selector.open();

        //创建通道
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        //设置非阻塞
        serverSocketChannel.configureBlocking(false);
        //绑定连接
        serverSocketChannel.bind(new InetSocketAddress(8888));

        //注册channel到selector
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

        //轮询查询就绪操作
        Set<SelectionKey> keys = selector.selectedKeys();
        //遍历集合
        Iterator<SelectionKey> iterator = keys.iterator();
        while (iterator.hasNext()) {
            SelectionKey key = iterator.next();
            //判断就绪状态
            if (key.isAcceptable()) {
            } else if (key.isConnectable()) {
            } else {
            }
            iterator.remove();
        }

        //停止选择的方法

    }

}

Selector示例

客户端:

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.util.Date;

public class SelectorDemo2 {

    //client
    public static void main(String[] args) throws IOException {
        // 1. 获取通道,绑定主机和端口号
        SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1",8888));
        // 2. 切换到非阻塞模式
        socketChannel.configureBlocking(false);
        // 3. 创建buffer
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        // 4. 写入buffer
        buffer.put(new Date().toString().getBytes());
        // 5. 模式切换
        buffer.flip();
        // 6. 写入通道
        socketChannel.write(buffer);
        // 7. 关闭
        buffer.clear();
    }

}

服务端:

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
import java.util.Set;

public class SelectorDemo3 {

    //server
    public static void main(String[] args) throws IOException {
        // 1. 获取服务端通道
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        // 2. 切换非阻塞模式
        serverSocketChannel.configureBlocking(false);
        // 3. 创建buffer
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        // 4. 绑定端口号
        serverSocketChannel.bind(new InetSocketAddress(8888));
        // 5. 获取selector
        Selector selector = Selector.open();
        // 6. 通道注册到选择器,进行监听
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
        // 7. 选择器进行轮询,进行后续操作
        while (selector.select() > 0) {
            Set<SelectionKey> selectionKeys = selector.selectedKeys();
            //遍历
            Iterator<SelectionKey> iterator = selectionKeys.iterator();
            while (iterator.hasNext()) {
                //获取就绪操作
                SelectionKey key = iterator.next();
                //判断什么操作
                if (key.isAcceptable()) {
                    //获取连接
                    SocketChannel accept = serverSocketChannel.accept();
                    //切换非阻塞模式
                    accept.configureBlocking(false);
                    //注册
                    accept.register(selector,SelectionKey.OP_READ);
                } else if (key.isReadable()) {
                    SocketChannel channel = (SocketChannel) key.channel();
                    ByteBuffer buffer1 = ByteBuffer.allocate(1024);
                    int len = 0;
                    while ((len = channel.read(buffer1)) > 0) {
                        buffer1.flip();
                        System.out.println(new String(buffer1.array(),0,len));
                        buffer1.clear();
                    }
                }
            }
            iterator.remove();
        }
    }

}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值