Java NIO:选择器
最近打算把Java网络编程相关的知识深入一下(IO、NIO、Socket编程、Netty)
Java NIO主要需要理解缓冲区、通道、选择器三个核心概念,作为对Java I/O的补充, 以提升大批量数据传输的效率。
学习NIO之前最好能有基础的网络编程知识
传统监控多个Socket的Java解决方案是为每一个Socket创建一个线程并使线程阻塞在read调用处, 直到数据可读。这种方式在系统并发不高时可以正常运行,如果是并发很高的系统就需要创建很多的线程(每个连接需要一个线程)。过多的线程会导致频繁的上下文切换、且线程是系统资源,可创建最大线程数是有限制的且远小于可以建立的网络连接数。
NIO的选择器就是为了解决这个问题, 选择器提供了同时询问多个通道是否准备好执行I/O的能力,比如SocketChannel对象是否还有更多的字节待读取, ServerSocketChannel是否有已经到达的客户端连接。
通过使用选择器,我们可以在一个线程里监听多个通道的就绪状态!
核心概念
选择器 :管理可选择通道集合&更新可选择通道的就绪状态
可选择通道:所有继承了SelectableChannel的通道, Socket都是可选择的,而文件通道不是,只有可选择通道可以注册到选择器上。
选择键:可选择通道注册到选择器后返回选择键, 所以选择键其实是通道与选择器注册关系的一个封装
三者之间的关系:可选择通道注册到选择器上,返回选择键
选择器使用
使用选择器的步骤一般是:
构造选择器
可选择通道注册到选择器
选择器选择(选择出就绪通道)
对就绪通道进行读写操作
重复 2 ~ 4
下面从这几步进行讲解
构造选择器
使用静态工厂方法构造(底层使用SelectorProvider创建Selector实例, SelectorProvider支持java spi扩展)
Selector selector = Selector.open();
可选择通道注册到选择器
只有运行在非阻塞模式下的通道可以注册到选择器上
注册的方法定义在SelectableChannel类中, 注册时需带上可选择的操作(四种可选择操作,定义在SelectionKey中),也可以带上附件
注册成功返回选择键,具体API如下:
//参数 选择器 + 可选择操作
public final SelectionKey register(Selector sel, int ops)
//带附件的版本
public abstract SelectionKey register(Selector sel, int ops, Object att)
选择器选择
select方法选择出就绪的通道,把该就绪通道关联的SelectionKey放到选择器的selectedKeys集合中(通道就绪指底层Socket已经就绪,执行连接或者读写操作时不会阻塞)
对就绪通道进行读写操作
对就绪通道的读写操作见下面Demo
Demo
写了一个demo把上面几步整合起来,代码主要两部分:可选择通道注册&选择器选择 和 对就绪通道进行读写操作
可选择通道注册&选择器选择
//构造选择器
Selector selector = Selector.open();
//初始化ServerSocketChannel绑定本地端口并设置为非阻塞模式
ServerSocketChannel ch = ServerSocketChannel.open();
ch.bind(new InetSocketAddress("127.0.0.1", 7001));
ch.configureBlocking(false);
//通道注册到选择器,并且关心ACCEPT操作(因为是Server)
ch.register(selector, SelectionKey.OP_ACCEPT);
//一直循环, 进行通道就绪状态的选择
while (true) {
int n = selector.select();
if (n == 0) {
continue;
}
Iterator<SelectionKey> it = selector.selectedKeys().iterator();
//遍历所有就绪的通道
while (it.hasNext()) {
SelectionKey key = it.next();
//如果就绪操作是ACCEPT, 接受客户端连接并注册到选择器。
if (key.isAcceptable()) {
try {
SocketChannel cch = ch.accept();
cch.configureBlocking(false);
//客户端通道注册到选择选择器,并关心READ操作
cch.register(selector, SelectionKey.OP_READ, ByteBuffer.allocate(1024));
} catch (IOException e) {
e.printStackTrace();
}
} else {
//如果是其他就绪操作, 提交到线程池处理
pool.submit(() -> handle(key));
}
//移除该选择键
it.remove();
}
}
对就绪通道进行读写操作
public static void handle(SelectionKey key) {
//如果通道是读就绪的
if (key.isReadable()) {
key.interestOps(key.interestOps() & (~SelectionKey.OP_READ));
SocketChannel ch = (SocketChannel) key.channel();
ByteBuffer bf = (ByteBuffer) key.attachment();
try {
int n = ch.read(bf);
//读取数据(ascii),并简单输出
if (n > 0) {
bf.flip();
StringBuilder builder = new StringBuilder();
while (bf.hasRemaining()) {
builder.append((char) bf.get());
}
System.out.print(builder.toString());
bf.clear();
key.interestOps(key.interestOps() | SelectionKey.OP_READ);
key.selector().wakeup();
} else if (n < -1) { //关闭连接
ch.close();
}
} catch (IOException e) {
//
}
}
}
选择器深入
可选择的操作
一共有四种可选择的操作(OP_READ、OP_WRITE、OP_ACCEPT、OP_ACCEPT),下为Socket通道对这四种可选择操作的支持情况
| OP_READ | OP_WRITE | OP_ACCEPT | OP_CONNECT | |
|---|---|---|---|---|
| SocketChannel | 支持 | 支持 | 不支持 | 支持 |
| SeverScoketChannel | 支持 | 支持 | 支持 | 不支持 |
| DatagramChannel | 支持 | 支持 | 不支持 | 不支持 |
概括来说:客户端Socket通道不关心ACCEPT操作, 服务端Socket通道不关心CONNECT操作,数据报Socket通道只关心READ和WRITE操作
选择键(SelectionKey)
可选择的通道注册到选择器,然后返回一个选择键对象,即选择键代表通道和选择器的一个关联关系。主要需要了解他的几个属性
interestOps:感兴趣的可选择操作集合,可选择通道注册到选取器的时候初始化,可以修改
readyOps: 就绪的可选择操作集合, 选择器选择的时候会对该集合更新, 客户端不能修改。
attachment:选择键可以关联一个对象,叫做附件(比如关联一个缓冲区对象)
选择器选择过程
在了解具体选择过程之前,我们先了解选择器中三个键集合的含义
已注册的键的集合,通过keys方法返回。通道注册的时候会把对应的选择键加入该集合
已选择的键的集合,选择器选择的时候会把就绪的通道对应的选择键放到该集合中
已取消的键的集合,选择键的cancel方法被调用后该选择键会加入该集合
具体选择过程:
- 如果已取消的键的集合非空, 将每个已取消的键从其他两个集合中移除,并将相关的通道注销,最后将已取消键的集合清空。
- 询问已注册键的集合中通道的就绪状态(系统调用),更新已选择键的集合以及键的readyOps集合。(如果一个键在该次选择操作之前就已在已选择键的集合中,则更新该键的readyOps集合;否则把键加入到已选择的集合中,并重置该键的readyOps集合)
最佳实践
我们通常使用一个选择器管理所有的可选择通道,并将就绪通道的服务委托给其他线程,只需要一个线程监控通道的就绪状态并使用一个协调好的工作线程池来读写数据
在这种方式下,进行相关操作前需要先将操作位从interestOps集合中移除,避免下次selector选择时再将选择键放到已选择集合中(造成一次就绪多次处理的问题),相关操作结束后再把操作加入到interestOps集合中并且唤醒selector进行下一次选择
下面是读操作简单伪码
//读就绪
if (key.isReadable()) {
//把READ从interestOps中移除
key.interestOps(key.interestOps() & (~SelectionKey.OP_READ));
//......数据读取处理
//把READ加入到interestOps中
key.interestOps(key.interestOps() | SelectionKey.OP_READ);
//唤醒selector,重新选择(因为键的interestOps变化了)
key.selector().wakeup();
}
总结
- 可选择通道注册到选择器上,返回选择键
- 选择器核心思想:使用一个(或者少数个)选择器管理所有的可选择通道,每个选择器使用一个线程监控就绪状态,使用一个工作线程池处理就绪通道的数据读写
Java NIO:选择器的更多相关文章
- Java NIO 选择器(Selector)的内部实现(poll epoll)
http://blog.csdn.net/hsuxu/article/details/9876983 之前强调这么多关于linux内核的poll及epoll,无非是想让大家先有个认识: Java NI ...
- Java NIO 选择器(Selector)的内部实现(poll epoll)(转)
转自:http://blog.csdn.net/hsuxu/article/details/9876983 之前强调这么多关于linux内核的poll及epoll,无非是想让大家先有个认识: Java ...
- JAVA NIO 选择器
为什么要使用选择器 通道处于就绪状态后,就可以在缓冲区之间传送数据.可以采用非阻塞模式来检查通道是否就绪,但非阻塞模式还会做别的任务,当有多个通道同时存在时,很难将检查通道是否就绪与其他任务剥离开来, ...
- Java NIO学习笔记 NIO选择器
Java NIO选择器 A Selector是一个Java NIO组件,可以检查一个或多个NIO通道,并确定哪些通道已准备就绪,例如读取或写入.这样一个线程可以管理多个通道,从而管理多个网络连接. 为 ...
- Java NIO学习笔记四 NIO选择器
Java NIO选择器 A Selector是一个Java NIO组件,可以检查一个或多个NIO通道,并确定哪些通道已准备就绪,例如读取或写入.这样一个线程可以管理多个通道,从而管理多个网络连接. 为 ...
- Java NIO学习笔记七 Non-blocking Server
Java NIO:Non-blocking Server 即使你了解了Java NIO非阻塞功能的工作(怎么样Selector,Channel, Buffer等等),设计一个无阻塞服务器仍然很难.非阻 ...
- 【NIO】Java NIO之选择器
一.前言 前面已经学习了缓冲和通道,接着学习选择器. 二.选择器 2.1 选择器基础 选择器管理一个被注册的通道集合的信息和它们的就绪状态,通道和选择器一起被注册,并且选择器可更新通道的就绪状态,也可 ...
- Java NIO之选择器
1.简介 前面的文章说了缓冲区,说了通道,本文就来说说 NIO 中另一个重要的实现,即选择器 Selector.在更早的文章中,我简述了几种 IO 模型.如果大家看过之前的文章,并动手写过代码的话.再 ...
- Java NIO之Selector(选择器)
历史回顾: Java NIO 概览 Java NIO 之 Buffer(缓冲区) Java NIO 之 Channel(通道) 其他高赞文章: 面试中关于Redis的问题看这篇就够了 一文轻松搞懂re ...
- Java NIO(六)选择器
前言 Selector选择器是Java NIO中能够检测一到多个NIO通道,并能够知晓通道是否为诸如读写事件做好准备的组件.这样使得一个单独的线程可以管理多个Channel,从而管理多个网络连接.选择 ...
随机推荐
- -webkit-line-clamp下多行文字溢出点点点...
限制在一个块元素显示的文本的行数. -webkit-line-clamp 是一个 不规范的属性(unsupported WebKit property),它没有出现在 CSS 规范草案中. 为了实现该 ...
- PHP的七个数组指针函数
1. PHP的七个数组指针函数 函数 描述 reset() 将一个数组的内部指针重置到首位,并返回第一个元素的值 end() 将一个数组的内部指针移动到数组的最后一个元素所在的位置,并返回最后一个元素 ...
- composer 国内镜像
本文列举一些最常用的国内镜像,配置国内镜像后可以提高composer包的下载速度.使用阿里云镜像的开发者较多,我也一直在使用这个镜像. 1. composer 中文网提供的中国全量镜像 https:/ ...
- java中双亲委派机制(+总结)
类加载器 加载类的开放性 类加载器(ClassLoader)是Java语言的一项创新,也是Java流行的一个重要原因.在类加载的第一阶段"加载"过程中,需要通过一个类的全限定名来获 ...
- 2020年 .NET ORM 完整比较、助力选择
.NET ORM 前言 为什么要写这篇文章? 希望针对 SEO 优化搜索引擎,让更多中国人知道并且使用.目前百度搜索 .NET ORM 全是 sqlsugar,我个人是无语的,每每一个人进群第一件事就 ...
- PHP实现Restful风格的API(转)
Restful是一种设计风格而不是标准,比如一个接口原本是这样的: http://www1.qixoo.com/user/view/id/1表示获取id为1的用户信息,如果使用Restful风格,可以 ...
- Vue等待父组件异步请求回数据'后'传值子组件
问题: 让子组件在父组件有哪个数据的时候再渲染, 解决方案: 可以在父组件上加一个判断条件, 举例说明: <a-component :opt="opt" v-if=" ...
- 3.GoolgeProtoBuffer序列化反序列化
- 针对于Java的35 个代码性能优化总结
针对于Java的35 个代码性能优化总结前言代码优化,一个很重要的课题.可能有些人觉得没用,一些细小的地方有什么好修改的,改与不改对于代码的运行效率有什么影响呢?这个问题我是这么考虑的,就像大海里面的 ...
- Java知识系统回顾整理01基础01第一个程序02命令行格式编译和执行Java程序
一.先看运行效果 在控制台下运行第一个Java程序,可以看到输出了字符串 hello world 二.准备项目目录 通常都会在e: 创建一个project目录 在这个例子里,我们用的是e:/proje ...