NIO-Selector详解

news2024/11/24 12:41:47

NIO-Selector详解

Selector概述

Selector选择器,也可以称为多路复⽤器。它是Java NIO的核⼼组件之⼀,⽤于检查⼀个或多个Channel的状态是否处于可读、可写、可连接、可接收等。通过⼀个Selector选择器管理多个Channel,可以实现⼀个线程管理多个Channel对应的⽹络连接。使⽤单线程管理多个Channel可以避免多线程的线程上下⽂切换带来的额外开销。

SelectableChannel可选择通道

只有SelectableChannel才能被Selector管理,⽐如所有的Socket通道。⽽FileChannel并没有继承SelectableChannel,因此不能被Selector管理。

Channel注册到Selector上

Channel通过注册的⽅式关联Selector。⼀个Channel可以注册到多个Selector上,但在某⼀个Selector上只能注册⼀次。注册时需要告知Selector,Selector需要对通道的哪个操作感兴趣。

public final SelectionKey register(Selector sel, int ops) throws ClosedChannelException{
    return register(sel, ops, null);
}

通道的操作如下:

  • 可读:SelectionKey.OP_READ

  • 可写:SelectionKey.OP_WRITE

  • 可连接:SelectionKey.OP_CONNECT

  • 可接收:SelectionKey.OP_ACCEPT

⽐如channel调⽤register⽅法进⾏注册到Selector,并告知Selector对哪个操作感兴趣:

channel.register(selector, SelectionKey.OP_READ);

也可以同时注册多个操作:

channel.register(selector, SelectionKey.OP_READ | SelectionKey.OP_WRITE);

选择器会查询每个⼀个channel的操作事件,如果是该channel注册的操作已就绪,则进⾏响应。注意,这⾥channel的操作指的是channel完成某个操作的条件,表示该channel对于该操作已处于就绪状态。⽐如ServerSocketChannel已准备好接收新的连接,那么它注册的 SelectionKey.OP_ACCEPT 操作就处于就绪状态。⼜⽐如SocketChannel已准备好去连接Server服务器,那么它注册的SelectionKey.OP_CONNECT 操作就处于就绪状态。于是Selector就可以触发之后的动作。

SelectionKey选择键

SelectionKey封装了Channel和注册的操作。

当Selector调⽤select()⽅法时,会轮询所有注册在它身上的Channel,查看是否有处于某个操作(已注册到selector上的)就绪状态的Channel,把这些Channel放⼊到SelectionKey的集合中。

Selector的使用

  • 创建Selector 通过Selector的open⽅法创建Selector对象。

    // 创建Selector
    Selector selector = Selector.open();
  • Channel注册到Selector上 Channel必须处于非阻塞模式才能注册到Selector上

    package com.my.io.selector;
    ​
    import java.io.IOException;
    import java.net.InetSocketAddress;
    import java.nio.channels.SelectionKey;
    import java.nio.channels.Selector;
    import java.nio.channels.ServerSocketChannel;
    ​
    /**
     * @author zhupanlin
     * @version 1.0
     * @description: selector的使用:注册channel到selector上
     * @date 2024/1/26 11:02
     */
    public class Demo1 {
    ​
        public static void main(String[] args) throws IOException {
            // 1.创建Selector
            Selector selector = Selector.open();
            // 2.获得Channel
            ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
            // 3.设置成非阻塞的模式
            serverSocketChannel.configureBlocking(false);
            // 4.绑定端口
            serverSocketChannel.bind(new InetSocketAddress(9001));
            // 5.注册channel到selector上
            SelectionKey selectionKey = serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
            
        }
        
    }
  • Selector轮询就绪状态的Channel

    Selector通过调⽤select⽅法轮询已就绪的通道操作。select⽅法是阻塞的,直到⾄少有⼀个通道的注册操作已就绪。当完成select⽅法调⽤后,被选中的已就绪的所有channel通过Selector的selectedKeys()⽅法获得,该⽅法获得的是⼀个SelectionKey集合,其中每⼀个SelectionKey都表示⼀个Channel。于是可以根据SelectionKey的注册操作来做具体的业务处理。

    package com.my.io.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;
    ​
    /**
     * @author zhupanlin
     * @version 1.0
     * @description: Selector轮询就绪状态的Channel
     * @date 2024/1/26 11:10
     */
    public class Demo2 {
    ​
        public static void main(String[] args) throws IOException {
            Selector selector = Selector.open();
            // serverSocketChannel
            ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
            serverSocketChannel.configureBlocking(false);
            // 绑定端口
            serverSocketChannel.bind(new InetSocketAddress(9001));
            // 注册
            SelectionKey selectionKey = serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
            // selector轮询
            while (true){
                // 阻塞等待某个操作就绪状态的channel
                selector.select();
                // 获得一个集合,里面包含了这次selector执行select方法获得的发生就绪状态的多个channel
                Set<SelectionKey> selectionKeys = selector.selectedKeys();
                // 遍历
                Iterator<SelectionKey> iterator = selectionKeys.iterator();
                while (iterator.hasNext()){
                    SelectionKey key = iterator.next();
                    if (key.isReadable()){
                        // 处理读状态的业务
                        
                    }else if (key.isAcceptable()){
                        // 处理接收状态的业务
                        
                    }else if (key.isConnectable()){
                        // 处理连接状态的业务
                        
                    }else if (key.isWritable()){
                        // 处理写状态的业务
                        
                    }
                    // 保证下次channel有就绪状态的操作发生时可以被selector轮询到
                    iterator.remove();
                }
            }
        }
        
    }
    ​

Selector示例

  • 实现NIO通信的服务端

    package com.my.io.selector;
    ​
    ​
    import java.io.IOException;
    import java.net.InetSocketAddress;
    import java.nio.ByteBuffer;
    import java.nio.channels.*;
    import java.util.Iterator;
    import java.util.Set;
    ​
    /**
     * @author zhupanlin
     * @version 1.0
     * @description: 服务端demo
     * @date 2024/1/26 11:45
     */
    public class ServerDemo {
    ​
        public static void main(String[] args) throws IOException {
            // 获得Channel
            ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
            // 设置成非阻塞
            serverSocketChannel.configureBlocking(false);
            // 绑定端口号
            serverSocketChannel.bind(new InetSocketAddress(9001));
            // 获得Selector
            Selector selector = Selector.open();
            // 把channel注册到selector上面
            serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
            // 让selector轮询监听
            while (true){
                // 阻塞直到有通道就绪
                selector.select();
                Set<SelectionKey> selectionKeys = selector.selectedKeys();
                // 获取有动作的selectionKey == channel
                Iterator<SelectionKey> iterator = selectionKeys.iterator();
                while (iterator.hasNext()){
                    SelectionKey selectionKey = iterator.next();
                    handle(selectionKey);
                    // 删除key,表示处理完成
                    iterator.remove();
                }
            }
        }
    ​
        private static void handle(SelectionKey selectionKey) throws IOException {
            if (selectionKey.isAcceptable()){
                // 当服务端处于接收的就绪状态
                // 获得selectionKey中的channel
                ServerSocketChannel serverSocketChannel = (ServerSocketChannel)selectionKey.channel();
                // 接收客户端连接,获得socketChannel
                SocketChannel socketChannel = serverSocketChannel.accept();
                // 设置成非阻塞状态,否则无法被selector复用
                socketChannel.configureBlocking(false);
                // 把socketChannel注册到selector上,让selector对socketChannel的read操作感兴趣
                socketChannel.register(selectionKey.selector(), SelectionKey.OP_READ);
            }else if (selectionKey.isReadable()){
                // 当socketChannel处于读数据的就绪状态
                SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
                // 读取socketChannel中的数据
                //设置成非阻塞
                socketChannel.configureBlocking(false);
                // 创建Buffer
                ByteBuffer buffer = ByteBuffer.allocate(1024);
                // 读数据
                int len = 0;
                while ((len = socketChannel.read(buffer)) > 0){
                    // 翻转
                    buffer.flip();
                    System.out.println(new String(buffer.array(), 0, len));
                    // 清除buffer中的数据
                    buffer.clear();
                }
                socketChannel.register(selectionKey.selector(), SelectionKey.OP_WRITE);
    ​
            }else if (selectionKey.isWritable()){
                
            }
        }
    ​
    }
  • 客户端

    package com.my.io.selector;
    ​
    import java.io.IOException;
    import java.net.InetSocketAddress;
    import java.nio.ByteBuffer;
    import java.nio.channels.SocketChannel;
    ​
    /**
     * @author zhupanlin
     * @version 1.0
     * @description: 客户端demo
     * @date 2024/1/26 11:28
     */
    public class ClientDemo {
    ​
        public static void main(String[] args) throws IOException {
            // 创建Channel
            SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("localhost", 9001));
            // 设置成非阻塞模式
            socketChannel.configureBlocking(false);
            // 得到buffer
            ByteBuffer buffer = ByteBuffer.allocate(1024);
            // 把数据写入到buffer中
            buffer.put("hello selector".getBytes());
            // 反转buffer
            buffer.flip();
            // 把buffer中的数据写入到channel中
            socketChannel.write(buffer);
            // 关闭
            socketChannel.close();
        }
        
    }

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1415637.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

STM32标准库——(5)EXTI外部中断

1.中断系统 中断&#xff1a;在主程序运行过程中&#xff0c;出现了特定的中断触发条件&#xff08;中断源&#xff09;&#xff0c;使得CPU暂停当前正在运行的程序&#xff0c;转而去处理中断程序&#xff0c;处理完成后又返回原来被暂停的位置继续运行 中断优先级&#xff…

Ribbon 体系架构解析

前面已经介绍了服务治理相关组件&#xff0c;接下来趁热打铁&#xff0c;快速通关Ribbon&#xff01;前面我们了解了负载均衡的含义&#xff0c;以及客户端和服务端负载均衡模型&#xff0c;接下来我们就来看下SpringCloud 下的客户端负载均衡组件Ribbon 的特点以及工作模型。 …

day04 两两交换链表中的节点、删除链表倒数第N个节点、链表相交、环形链表II

题目链接&#xff1a;leetcode24-两两交换链表中的节点, leetcode19-删除链表倒数第N个节点, leetcode160-链表相交, leetcode142-环形链表II 两两交换链表中的节点 基础题没有什么技巧 解题思路见代码注释 时间复杂度: O(n) 空间复杂度: O(1) Go func swapPairs(head *Li…

Android Handler完全解读

一&#xff0c;概述 Handler在Android中比较基础&#xff0c;本文笔者将对此机制做一个完全解读。读者可简单参考上述类图与时序图&#xff0c;便于后续理解。 二&#xff0c;源码解读 1&#xff0c;主线程伊始 众所周知&#xff0c;通过Zygote的fork方式&#xff0c;新创建…

腾讯云轻量应用Ubuntu服务器如何一键部署幻兽帕鲁Palworld私服?

幻兽帕鲁/Palworld是一款2024年Pocketpair开发的开放世界生存制作游戏&#xff0c;在帕鲁的世界&#xff0c;玩家可以选择与神奇的生物“帕鲁”一同享受悠闲的生活&#xff0c;也可以投身于与偷猎者进行生死搏斗的冒险。而帕鲁可以进行战斗、繁殖、协助玩家做农活&#xff0c;也…

网页转文件下载工具

为了更快捷copy博客 做了个 网页转文件下载工具 1.0.1 更新如下&#xff1a; javaphpjava提供页面转换文件的微服务APIphp调用接口&#xff0c;输出文件下载支持网页转md 1.0.2 更新如下&#xff1a; 样式表切换&#xff0c;白天or黑夜&#xff0c;cookie七天保质期 未…

全国首条智慧高速开通,“车牌付” 会取代传统 ETC 收费吗?

2024年1月19日&#xff0c;全国首条智慧高速--杭绍甬高速杭绍段正式建成通车&#xff01;项目全长约52.8公里&#xff0c;设计速度120公里/小时&#xff0c;是长三角智慧交通示范项目。 01 杭绍甬 “慧眼”感知系统 全国首条智慧高速公路--杭绍甬高速在视频AI算法、IoT物联网、…

SpringBoot系列之MybatisPlus实现分组查询

SpringBoot系列之MybatisPlus实现分组查询 我之前博主曾记写过一篇介绍SpringBoot2.0项目怎么集成MybatisPlus的教程&#xff0c;不过之前的博客只是介绍了怎么集成&#xff0c;并没有做详细的描述各种业务场景&#xff0c;本篇博客是对之前博客的补充&#xff0c;介绍在mybat…

mac裁剪图片

今天第一次用mac裁剪图片&#xff0c;记录一下过程&#xff0c;差点我还以为我要下载photoshop了&#xff0c; 首先准备好图片 裁剪的目的是把图片的标题给去掉&#xff0c;但是不能降低分辨率&#xff0c;否则直接截图就可以了 解决办法 打开原始图片(不要使用预览&#xf…

【机器学习笔记】0 基础知识之python基础

注&#xff1a;本文内容仅为个人学习笔记&#xff0c;教程为黄海广老师主讲的机器学习入门系列&#xff0c; 课程链接&#xff08;中国大学慕课&#xff0c;有习题和证书&#xff09; 课程资源&#xff08;pdf版本课件和代码&#xff09;公布在Github链接 课程视频也可以在b站观…

python10-Python的字符串之拼接字符串

如果直接将两个字符串紧挨着写在一起&#xff0c;Python就会自动拼接它们&#xff0c;例如如下代码。 s1 "软件测试划水老师傅&#xff0c;"软件测试老痞print(s1) 上面代码将会输出: 软件测试划水老师傅&#xff0c;软件测试老痞 上面这种写法只是书写字符串的一…

数学知识第三期 欧拉函数

前言 相信大家在高中的时候接触过欧拉函数&#xff0c;希望大家通过本篇文章能够进一步理解欧拉函数&#xff01;&#xff01;&#xff01; 一、什么是欧拉函数&#xff1f; 欧拉函数是一个在数论中用于描述特定正整数的互质数的概念。具体来说&#xff0c;对于一个正整数n&…

初识人工智能,一文读懂机器学习之逻辑回归知识文集(7)

&#x1f3c6;作者简介&#xff0c;普修罗双战士&#xff0c;一直追求不断学习和成长&#xff0c;在技术的道路上持续探索和实践。 &#x1f3c6;多年互联网行业从业经验&#xff0c;历任核心研发工程师&#xff0c;项目技术负责人。 &#x1f389;欢迎 &#x1f44d;点赞✍评论…

电商API接口|爬虫案例|采集某东商品评论信息

前言&#xff1a; 平常大家都有网上购物的习惯&#xff0c;在商品下面卖的好的产品基本都会有评论&#xff0c;当然也不排除有刷评论的情况&#xff0c;因为评论会影响我们的购物决策。今天主要分享用pythonre正则表达式获取京东商品评论。API接口获取京东平台商品详情SKU数据…

API网关-Apinto压缩包方式自动化安装配置教程

文章目录 前言一、Apinto安装教程1. 复制脚本2. 增加执行权限3. 执行脚本4. Apinto命令4.1 启动Apinto4.2 停止Apinto4.3 重启Apinto4.4 查看Apinto版本信息4.5 加入Apinto集群4.6 离开Apinto集群4.7 查看Apinto节点信息 5. 卸载Apinto 二、Apserver(Apinto Dashboard V3)安装教…

powermock: 一个支持 gRPC 的 Mock Server

文章目录 背景选型架构安装配置使用教程快速开始接口定义配置启动 Mock 规则redis 插件HTTP Mock高级配置前置准备场景一 特定 ID 返回特定用户信息场景二 通过脚本返回用户数据 总结参考资料 本文介绍的是如何基于 bilibili 的开源方案 powermock 搭建一套通用的适用于自己公司…

11. 双目视觉之立体视觉基础

目录 1. 深度恢复1.1 单目相机缺少深度信息1.2 如何恢复场景深度&#xff1f;1.3 深度恢复的思路 2. 对极几何约束2.1 直观感受2.2 数学上的描述 1. 深度恢复 1.1 单目相机缺少深度信息 之前学习过相机模型&#xff0c;最经典的就是小孔成像模型。我们知道相机通过小孔成像模…

uniapp scroll-view用法[下拉刷新,触底事件等等...](4)

前言:可滚动视图区域。用于区域滚动 话不多说 直接上官网属性 官网示例 讲一下常用的几个 scroll 滚动时触发 scrolltoupper 滚动到顶部或左边&#xff0c;会触发 scrolltoupper 事件 scrolltolower 滚动到底部或右边&#xff0c;会触发 scrolltolower 事件 1.纵向滚动…

【揭秘】RecursiveAction全面解析

内容概要 RecursiveAction是Java中一个强大的工具&#xff0c;它允许将复杂任务分解为更小的子任务&#xff0c;这些子任务可以并行执行&#xff0c;从而提高整体性能&#xff0c;其主要优点在于能够有效地利用多核处理器&#xff0c;减少任务执行时间&#xff0c;并简化并行编…

SQL注入:盲注

SQL注入系列文章&#xff1a; 初识SQL注入-CSDN博客 SQL注入&#xff1a;联合查询的三个绕过技巧-CSDN博客 SQL注入&#xff1a;报错注入-CSDN博客 目录 什么是盲注&#xff1f; 布尔盲注 手工注入 使用python脚本 使用sqlmap 时间盲注 手工注入 使用python脚本 使…