IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> Java Nio中Selector是什么?Selector怎么使用呢? -> 正文阅读

[Java知识库]Java Nio中Selector是什么?Selector怎么使用呢?

????????以前大家都是用阻塞式IO来对网络IO进行数据请求,对于不同的IO都要分配一个线程来处理,如果没有数据就会进行等待,从而造成了阻塞,这种方式极大地浪费了资源(如图1)。于是,有人就提出了一个想法,使用一个线程去监控多个IO请求,如果哪一个IO数据准备完毕后就通知相应的线程来处理,这就是selector模型(如图2)。而Java中的selector就是对selector模型的一种实现,用于询问选择已就绪的IO处理任务。

Selector的几个核心的概念

  1. Channel(通道):用于进行网络传输的通道,网络传输的数据都放在通道中,可以进行写入,也可以进行读取。Channel主要有两种:ServerSocketChannel和SocketChannel,其中ServerSocketChannel是用于服务端开发的,而SocketChannel是用于客户端开发的。
  2. Selector(选择器):用于进行监控多个通道数据状态。
  3. SelectableChannel(可选择通道):可以被选择器选择的通道,继承了抽象类SelectableChannel的Channel,而FileChannel没有继承此类,所以不可以被选择器选择。
  4. SelectionKey(选择键):用于表示通道可以被选择的某种就绪事件状态。选择键的事件主要有以下几种:
    OP_READ:可读事件。 ??OP_WRITE:可写事件。 ??OP_CONNECT:客户端连接服务端的事件,一般为创建SocketChannel客户端channel。 ??OP_ACCEPT:服务端接收客户端连接的事件,一般为创建ServerSocketChannel服务端channel。

Selector的使用

1.创建选择器

Selector selector = Selector.open();

2.获取通道

ServerSocketChannel channel = ServerSocketChannel.open(); // 创建通道
channel.bind(new InetSocketAddress(8080));  // 绑定端口
channel.configureBlocking(false); // 设置为非阻塞,注册到selector上的通道一定设置为非阻塞,否则会报IllegalBlockingModeException错误

3.将通道注册到选择器上

channel.register(selector, SelectionKey.OP_ACCEPT); // 将通道注册到选择器上,监听可接收事件,对于监听多个事件可以用“按位或”来操作

4.轮询已就绪的事件,并对不同的事件进行处理

while (true) {
    int count = selector.select();  // 获取已就绪事件的数量
    if (count == 0) {
        continue;
    }
    Set<SelectionKey> selectionKeys = selector.selectedKeys(); // 获取已就绪键集
    Iterator<SelectionKey> iterator = selectionKeys.iterator();
    while (iterator.hasNext()) {
        SelectionKey key = iterator.next();           
        if(key.isAcceptable()) {
            // 接收处理
        } else if (key.isConnectable()) {
            // 连接处理
        } else if (key.isReadable()) {
            // 读取处理
        } else if (key.isWritable()) {
            // 写入处理
        }
        key.remove(); // 移除键,防止下次重复处理
    }
}             
// 注意:key.isWritable只要建立连接,就会触发,所以在设置可写入事件时,在写入之后要改回可监听事件,否则就会死循环

其他关于Selector的知识点

Selector内部总共维护了三组键集:

keys:当前Channel注册在Selector上面的所有的key,可以调用keys()获取。

selectedKeys:当前Channel所有已就绪的事件,可以调用selectedKeys()获取。

cancelledKeys:当前Channel所有已取消的事件,主动调用cancel()方法的事件会放在该集合。

其他一些常用的方法:

  • Selector#isOpen():判断selector是否是open状态,如果调用了close()方法则会返回false
  • SelectionKey#isValid():判断选择键是否有效。
  • Selector#selectNow():获取是否有就绪的事件,该方法立即返回结果,不会阻塞。
  • Selector#select(long timeout):在超时时间内,有就绪事件时才会返回,其次超过时间也会返回。
  • Selector#select():阻塞直到有事件就绪时才会返回
  • Selector#wakeup():调用该方法会时,阻塞在select()处的线程会立即返回。即使当前不存在线程阻塞在select()处,那么下一个select()方法也会立即返回。
  • Selector#close():用完Selector后调用其close()方法会关闭该Selector,且使注册到该Selector上的所有SelectionKey实例无效。

一个Selector的简单示例

/**
    仅服务端,客户端可以使用telnet命令
**/
@Test
public void testSelector() throws IOException, InterruptedException {
    ServerSocketChannel channel = ServerSocketChannel.open(); // 创建通道
    channel.bind(new InetSocketAddress(8080)); // 绑定端口号
    channel.configureBlocking(false);   // 设置为非阻塞
    Selector selector = Selector.open(); // 创建选择器
    channel.register(selector, SelectionKey.OP_ACCEPT); // 注册到选择器上
   
    Thread thread = new Thread(() -> {
        try {
            while (true) {
                int count = selector.select(); // 获取已就绪事件数量
                if (count == 0) {
                    continue;
                }
                Set<SelectionKey> selectionKeys = selector.selectedKeys(); // 获取已就绪键集
                Iterator<SelectionKey> iterator = selectionKeys.iterator();
                while (iterator.hasNext()) {
                    SelectionKey key = iterator.next();
                    if (!key.isValid()) {  // 判断选择键是否有效
                        continue;
                    } else if (key.isAcceptable()) {  // 处理接收事件
                        SocketChannel socket = channel.accept();
                        socket.configureBlocking(false);
                        socket.register(selector, SelectionKey.OP_READ);
                        System.out.println("已注册" + socket);
                    } else if (key.isReadable()) {  // 处理读取事件 
                        SocketChannel socketChannel = (SocketChannel) key.channel();
                        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);  // 声明buffer
                        socketChannel.read(byteBuffer);  // 将通道中的数据读取到buffer
                        byteBuffer.flip();  
                        byte[] bytes = new byte[byteBuffer.remaining()];
                        byteBuffer.get(bytes);
                        String str = new String(bytes);
                        str = "\r\nreceive: " + str;
                        System.out.println(str);
                        socketChannel.write(ByteBuffer.wrap(str.getBytes())); // 将数据写回通道中
                    }
                    iterator.remove();
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    });
    thread.start();
    thread.join();
}
  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2021-10-26 12:05:05  更:2021-10-26 12:07:00 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 0:36:38-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码