Java I/O(4):AIO和NIO中的Selector
您好,我是湘王,这是我的博客园,欢迎您来,欢迎您再来~
在Java NIO的三大核心中,除了Channel和Buffer,剩下的就是Selector了。有的地方叫它选择器,也有叫多路复用器的(比如Netty)。
之前提过,数据总是从Channel读取到Buffer,或者从Buffer写入到Channel,单个线程可以监听多个Channel——Selector就是这个线程背后的实现机制(所以得名Selector)。

Selector通过控制单个线程处理多个Channel,如果应用打开了多个Channel,但每次传输的流量都很低,使用Selector就会很方便(至于为什么,具体到Netty中再分析)。所以使用Selector的好处就显而易见:用最少的资源实现最多的操作,避免了线程切换带来的开销。
还是以代码为例来演示Selector的作用。新建一个类,在main()方法中输入下面的代码:
/**
* NIO中的Selector
*
* @author xiangwang
*/
public class TestSelector {
public static void main(String args[]) throws IOException {
// 创建ServerSocketChannel
ServerSocketChannel channel1 = ServerSocketChannel.open();
channel1.socket().bind(new InetSocketAddress("127.0.0.1", 8080));
channel1.configureBlocking(false);
ServerSocketChannel channel2 = ServerSocketChannel.open();
channel2.socket().bind(new InetSocketAddress("127.0.0.1", 9090));
channel2.configureBlocking(false); // 创建一个Selector对象
Selector selector = Selector.open();
// 按照字面意思理解,应该是这样的:selector.register(channel, event);
// 但其实是这样的:channel.register(selector, SelectionKey.OP_READ);
// 四种监听事件:
// OP_CONNECT(连接就绪)
// OP_ACCEPT(接收就绪)
// OP_READ(读就绪)
// OP_WRITE(写就绪)
// 注册Channel到Selector,事件一旦被触发,监听随之结束
SelectionKey key1 = channel1.register(selector, SelectionKey.OP_ACCEPT);
SelectionKey key2 = channel2.register(selector, SelectionKey.OP_ACCEPT); // 模板代码:在编写程序时,大多数时间都是在模板代码中添加相应的业务代码
while(true) {
int readyNum = selector.select();
if (readyNum == 0) {
continue;
} Set<SelectionKey> selectedKeys = selector.selectedKeys();
// 轮询
for (SelectionKey key : selectedKeys) {
Channel channel = key.channel();
if (key.isConnectable()) {
if (channel == channel1) {
System.out.println("channel1连接就绪");
} else {
System.out.println("channel2连接就绪");
}
} else if (key.isAcceptable()) {
if (channel == channel1) {
System.out.println("channel1接收就绪");
} else {
System.out.println("channel2接收就绪");
}
}
// 触发后删除,这里不删
// it.remove();
}
}
}
}
代码写好后启动ServerSocketChannel服务,可以看到我这里已经启动成功:

然后在网上下载一个叫做SocketTest.jar的工具(在一些工具网站下载的时候当心中毒,如果不放心,可以私信我,给你地址),双击打开,并按下图方式执行:

点击「Connect」可以看到变化:

然后点击「Disconnect」,再输入「9090」后,再点击「Connect」试试:

可以看到结果显示结果变了:

两次连接,打印了三条信息:说明selector的轮询在起作用(因为Set<SelectionKey>中包含了所有处于监听的SelectionKey)。但是「接收就绪」监听事件仅执行了一次就再不响应。如果感兴趣的话你可以把OP_READ、OP_WRITE这些事件也执行一下试试看。
因为Selector是单线程轮询监听多个Channel,那么如果Selector(线程)之间需要传递数据,怎么办呢?——Pipe登场了。Pipe就是一种用于Selector之间数据传递的「管道」。
先来看个图:

可以清楚地看到它的工作方式。
还是用代码来解释。
/**
* NIO中的Pipe
*
* @author xiangwang
*/
public class TestPipe {
public static void main(String args[]) throws IOException {
// 打开管道
Pipe pipe = Pipe.open(); // 将Buffer数据写入到管道
Pipe.SinkChannel sinkChannel = pipe.sink();
ByteBuffer buffer = ByteBuffer.allocate(32);
buffer.put("ByteBuffer".getBytes());
// 切换到写模式
buffer.flip();
sinkChannel.write(buffer); // 从管道读取数据
Pipe.SourceChannel sourceChannel = pipe.source();
buffer = ByteBuffer.allocate(32);
sourceChannel.read(buffer);
System.out.println(new String(buffer.array())); // 关闭管道
sinkChannel.close();
sourceChannel.close();
}
}
之前说过,同步指的按顺序一次完成一个任务,直到前一个任务完成并有了结果以后,才能再执行后面的任务。而异步指的是前一个任务结束后,并不等待任务结果,而是继续执行后一个任务,在所有任务都「执行」完后,通过任务的回调函数去获得结果。所以异步使得应用性能有了极大的提高。为了更加生动地说明什么是异步,可以来做个实验:

通过调用CompletableFuture.supplyAsync()方法可以很明显地观察到,处于位置2的「这一步先执行」会最先显示,然后才执行位置1的代码。而这就是异步的具体实现。
NIO为了支持异步,升级到了NIO2,也就是AIO。而AIO引入了新的异步Channel的概念,并提供了异步FileChannel和异步SocketChannel的实现。AIO的异步SocketChannel是真正的异步非阻塞I/O。通过代码可以更好地说明:
/**
* AIO客户端
*
* @author xiangwang
*/
public class AioClient {
public void start() throws IOException, InterruptedException {
AsynchronousSocketChannel channel = AsynchronousSocketChannel.open();
if (channel.isOpen()) {
// socket接收缓冲区recbuf大小
channel.setOption(StandardSocketOptions.SO_RCVBUF, 128 * 1024);
// socket发送缓冲区recbuf大小
channel.setOption(StandardSocketOptions.SO_SNDBUF, 128 * 1024);
// 保持长连接状态
channel.setOption(StandardSocketOptions.SO_KEEPALIVE, true);
// 连接到服务端
channel.connect(new InetSocketAddress(8080), null,
new AioClientHandler(channel));
// 阻塞主进程
for(;;) {
TimeUnit.SECONDS.sleep(1);
}
} else {
throw new RuntimeException("Channel not opened!");
}
} public static void main(String[] args) throws IOException, InterruptedException {
new AioClient().start();
}
}
/**
* AIO客户端CompletionHandler
*
* @author xiangwang
*/
public class AioClientHandler implements CompletionHandler<Void, AioClient> {
private final AsynchronousSocketChannel channel;
private final CharsetDecoder decoder = Charset.defaultCharset().newDecoder();
private final BufferedReader input = new BufferedReader(new InputStreamReader(System.in)); public AioClientHandler(AsynchronousSocketChannel channel) {
this.channel = channel;
}
@Override
public void failed(Throwable exc, AioClient attachment) {
throw new RuntimeException("channel not opened!");
}
@Override
public void completed(Void result, AioClient attachment) {
System.out.println("send message to server: ");
try {
// 将输入内容写到buffer
String line = input.readLine();
channel.write(ByteBuffer.wrap(line.getBytes()));
// 在操作系统中的Java本地方法native已经把数据写到了buffer中
// 这里只需要一个缓冲区能接收就行了
ByteBuffer buffer = ByteBuffer.allocate(1024);
while (channel.read(buffer).get() != -1) {
buffer.flip();
System.out.println("from server: " + decoder.decode(buffer).toString());
if (buffer.hasRemaining()) {
buffer.compact();
} else {
buffer.clear();
}
// 将输入内容写到buffer
line = input.readLine();
channel.write(ByteBuffer.wrap(line.getBytes()));
}
} catch (IOException | InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
}
/**
* AIO服务端
*
* @author xiangwang
*/
public class AioServer {
public void start() throws InterruptedException, IOException {
AsynchronousServerSocketChannel channel = AsynchronousServerSocketChannel.open();
if (channel.isOpen()) {
// socket接受缓冲区recbuf大小
channel.setOption(StandardSocketOptions.SO_RCVBUF, 4 * 1024);
// 端口重用,防止进程意外终止,未释放端口,重启时失败
// 因为直接杀进程,没有显式关闭套接字来释放端口,会等待一段时间后才可以重新use这个关口
// 解决办法就是用SO_REUSEADDR
channel.setOption(StandardSocketOptions.SO_REUSEADDR, true);
channel.bind(new InetSocketAddress(8080));
} else {
throw new RuntimeException("channel not opened!");
}
// 处理client连接
channel.accept(null, new AioServerHandler(channel));
System.out.println("server started");
// 阻塞主进程
for(;;) {
TimeUnit.SECONDS.sleep(1);
}
} public static void main(String[] args) throws IOException, InterruptedException {
AioServer server = new AioServer();
server.start();
}
}
/**
* AIO服务端CompletionHandler
*
* @author xiangwang
*/
public class AioServerHandler implements CompletionHandler<AsynchronousSocketChannel, Void> {
private final AsynchronousServerSocketChannel serverChannel;
private final CharsetDecoder decoder = Charset.defaultCharset().newDecoder();
private final BufferedReader input = new BufferedReader(new InputStreamReader(System.in)); public AioServerHandler(AsynchronousServerSocketChannel serverChannel) {
this.serverChannel = serverChannel;
}
@Override
public void failed(Throwable exc, Void attachment) {
// 处理下一次的client连接
serverChannel.accept(null, this);
}
@Override
public void completed(AsynchronousSocketChannel result, Void attachment) {
// 处理下一次的client连接,类似链式调用
serverChannel.accept(null, this);
try {
// 将输入内容写到buffer
String line = input.readLine();
result.write(ByteBuffer.wrap(line.getBytes()));
// 在操作系统中的Java本地方法native已经把数据写到了buffer中
// 这里只需要一个缓冲区能接收就行了
ByteBuffer buffer = ByteBuffer.allocate(1024);
while (result.read(buffer).get() != -1) {
buffer.flip();
System.out.println("from client: " + decoder.decode(buffer).toString());
if (buffer.hasRemaining()) {
buffer.compact();
} else {
buffer.clear();
}
// 将输入内容写到buffer
line = input.readLine();
result.write(ByteBuffer.wrap(line.getBytes()));
}
} catch (InterruptedException | ExecutionException | IOException e) {
e.printStackTrace();
}
}
}
执行测试后显示,不管是在客户端还是在服务端,读写完全是异步的。
感谢您的大驾光临!咨询技术、产品、运营和管理相关问题,请关注后留言。欢迎骚扰,不胜荣幸~
Java I/O(4):AIO和NIO中的Selector的更多相关文章
- epoll浅析以及nio中的Selector
出处: https://my.oschina.net/hosee/blog/730598 首先介绍下epoll的基本原理,网上有很多版本,这里选择一个个人觉得相对清晰的讲解(详情见reference) ...
- epoll 浅析以及 nio 中的 Selector
首先介绍下epoll的基本原理,网上有很多版本,这里选择一个个人觉得相对清晰的讲解(详情见reference): 首先我们来定义流的概念,一个流可以是文件,socket,pipe等等可以进行I/O操作 ...
- Java面试总结之AIO与NIO
1.Java NIO 是一种同步非阻塞的I/O模型 将多个IO的阻塞复用到同一个select的阻塞上,从而使得系统在单线程的情况下处理多个客户端请求. NIO三个核心对象:通道(Channel).缓冲 ...
- 3、nio中的selector使用
通过编写一个客户端和服务器端的例子来熟悉selector的使用 服务端逻辑: 1. 绑定一个端口号2. channel注册到selector中3. 用死循环来监听如果有时间发生,遍历selection ...
- Java网络通信方面,BIO、NIO、AIO、Netty
码云项目源码地址:https://gitee.com/ZhangShunHai/echo 教学视频地址:链接: https://pan.baidu.com/s/1knVlW7O8hZc8XgXm1dC ...
- JAVA - IO - IO的类型(AIO, BIO, NIO)
IO的方式通常分为几种,同步阻塞的BIO.同步非阻塞的NIO.异步非阻塞的AIO. 一.BIO 在JDK1.4出来之前,我们建立网络连接的时候采用BIO模式,需要先在服务端启动一个ServerSock ...
- 温故知新-java的I/O模型-BIO&NIO&AIO
文章目录 摘要 传统的BIO编程 伪异步I/O编程 NIO编程 AIO编程 几种IO模型的对比 netty 参考 你的鼓励也是我创作的动力 Posted by 微博@Yangsc_o 原创文章,版权声 ...
- Java IO模型:BIO、NIO、AIO
Java IO模型:BIO.NIO.AIO 本来是打算直接学习网络框架Netty的,但是先补充了一下自己对Java 几种IO模型的学习和理解.分别是 BIO.NIO.AIO三种IO模型. IO模型的基 ...
- JAVA NIO中的Channels和Buffers
前言 Channels和Buffers是JAVA NIO里面比较重要的两个概念,NIO正是基于Channels和Buffers进行数据操作,且数据总是从Channels读取到Buffers,或者从Bu ...
随机推荐
- Auto.js 调用系统短信、电话
本文所有教程及源码.软件仅为技术研究.不涉及计算机信息系统功能的删除.修改.增加.干扰,更不会影响计算机信息系统的正常运行.不得将代码用于非法用途,如侵立删! Auto.js 调用系统短信.电话 操作 ...
- vue自定义switch开关,使用less支持换肤
实际项目用到了,记录一下,也方便以后使用,这样也可以避免为了使用一个switch,引入整个外部web框架: 也可以方便更好的理解是和使用less. 基础代码使用的是网上的,然后自己添加了less换肤, ...
- ansible 的安装及常见模块使用
ansible 基础keys的ssh协议配置的 特性:幂等性:一个任务执行1遍和执行n遍效果一样. ansible是个管理软件不是服务,不需要长期运行 一.通过epel源安装ansible, 1.下 ...
- Python自学教程5-字符串有哪些常用操作
任何编程语言,不管是Python.Java 还是 Golang, 字符串都是最重要的一种数据类型. 但是字符串的操作又很多,初学者经常毫无头绪,不知道从哪儿学起,也不知道哪些操作用得多,今天九柄就和你 ...
- Miller Rabbin 算法—费马定理+二次探测+随机数 (讲解+例题:FZU1649 Prime number or not)
0.引入 那年,机房里来了个新教练, 口胡鼻祖lhy 第一节课,带我们体验了暴力的神奇, 第二节课,带我们体验了随机数的玄妙, -- 那节课,便是我第一次接触到Miller Rabbin算法, 直到现 ...
- 快速搭建 SpringCloud Alibaba Nacos 配置中心!
Spring Cloud Alibaba 是阿里巴巴提供的一站式微服务开发解决方案,目前已被 Spring Cloud 官方收录.而 Nacos 作为 Spring Cloud Alibaba 的核心 ...
- ansible 003 常用模块
常用模块 file 模块 管理被控端文件 回显为绿色则,未变更,符合要求 黄色则改变 红色则报错 因为默认值为file,那么文件不存在,报错 改为touch则创建 将state改为directory变 ...
- Linux之NFS服务搭建及autofs服务搭建
NFS 网络文件系统,英文Network File System(NFS),是由SUN公司研制的UNIX表示层协议(presentation layer protocol),能使使用者访问网络上别处的 ...
- 线程池:ThreadPoolExecutor源码解读
目录 1 带着问题去阅读 1.1 线程池的线程复用原理 1.2 线程池如何管理线程 1.3 线程池配置的重要参数 1.4 shutdown()和shutdownNow()区别 1.5 线程池中的两个锁 ...
- Linux系统编程001--系统IO
1. 文件系统:用来存储.组织.管理文件的一套方式.协议 2. 文件 文件的属性:i-node唯一表示一个文件的存在与否 文件的内容 3. Linux系统如何实现文件的操作? 点击查看代码 硬件层: ...