关于NIO的一些总结
上一篇博客讲了io流的一些基本操作,这次讲一下NIO,NIO这个实际上在企业开发中很少用到的,大家适当看下就好
简介
传统的 java.io包,提供了我们最熟知的一些 IO 功能,比如 File 抽象、输入输出流等。交互方式是同步、阻塞的方式,也就是说,在读取输入流或者写入输出流时,在读、写动作完成之前,线程会一直阻塞在那里,它们之间的调用是可靠的线性顺序。
java.io包的好处是代码比较简单、直观,缺点则是 IO 效率和扩展性存在局限性,容易成为应用性能的瓶颈。
同步:一条线程执行,其他线程就只能等待
异步:一条线程在执行,其他线程无需等待,照样可以继续执行
NIO和IO的区别:
- IO 面向流,NIO是面向缓冲区
- IO是阻塞的,NIO是非阻塞的
- NIO使用选择器
NIO的优点:非阻塞,可以实现多路复用, nIO异步非阻塞
NIO的三个组成部分
Channel(通道)、Buffer(缓冲区)、Selector(选择器)
Buffer
Buffer主要有如下几种:
-
ByteBuffer
-
CharBuffer
-
DoubleBuffer
-
FloatBuffer
-
IntBuffer
-
LongBuffer
-
ShortBuffer
Buffer的使用:以ByteBuffer举例 -
ByteBuffer对象的创建方式:
- static ByteBuffer allocate(int capacity) 分配一个新的字节缓冲区。
- static ByteBuffer allocateDirect(int capacity) 分配新的直接字节缓冲区。
- static ByteBuffer wrap(byte[] array) 将 byte 数组包装到缓冲区中。
//1.创建间接缓存区--缓存区是在:堆中:创建的快;访问的慢;
ByteBuffer buf1 = ByteBuffer.allocate(10);
//2.创建直接缓存区--系统内存:创建的慢;访问的快;
ByteBuffer buf2 = ByteBuffer.allocateDirect(10);
//3.根据一个已有的byte[]数组创建一个缓存区对象:间接缓存区
byte[] byteArray = {97, 98, 99, 100};
ByteBuffer buf3 = ByteBuffer.wrap(byteArray);
-
缓冲区存取数据的两个核心方法:
- put();存入数据到缓冲区中
- get();获取缓冲区的数据
-
缓冲区中的4个核心属性:
- capacity:代表缓冲区的最大容量,一般新建一个缓冲区的时候,limit的值和capacity的值默认是相等的。
- limit:代表有多少数据可以取出或有多少空间可以写入
- position:跟踪已经写了多少数据或读了多少数据,它指向的是下一个字节来自哪个位置
- mark:表示记录当前position位置,可以通过reset()恢复到mark的位置
- 关系:mark <= position <= limit <= capacity
-
Buffer常用方法:
- allocate() 获取Buffer对象
- put() 存储数据
- flip() 切换读取数据的模式
- get() 读取数据
- rewind() 可以重复读
- clear() 清空整个缓冲区
- mark() 在此缓冲区的位置设置标记。
- reset() 将此缓冲区的位置重置为以前标记的位置
public static void main(String[] args) {
/*
Buffer的几个重要技术点
Buffer常用方法:
- allocate() 获取Buffer对象
- put() 存储数据
- flip() 切换读取数据的模式
- get() 读取数据
- rewind() 可以重复读
- clear() 清空缓冲区
属性:
- capacity:表示缓冲区中最大存储数据的容量
- limit:表示缓冲区中可以操作数据的大小
- position:表示缓冲区中正在操作数据的位置
- mark:表示记录当前position位置,可以通过reset()恢复到mark的位置
- 关系:mark <= position <= limit <= capacity
*/
byte[] bys = {97, 98, 99, 100, 101};
ByteBuffer buffer = ByteBuffer.allocate(10);
System.out.println("============allocate()============");
System.out.println(buffer.position());// 0
System.out.println(buffer.limit());// 10
System.out.println(buffer.capacity());// 10
System.out.println("============put()============");
buffer.put(bys);
System.out.println(buffer.position());// 5
System.out.println(buffer.limit());// 10
System.out.println(buffer.capacity());// 10
System.out.println("============flip()============");
buffer.flip();
System.out.println(buffer.position());// 0
System.out.println(buffer.limit());// 5
System.out.println(buffer.capacity());// 10
System.out.println("============get()============");
byte[] bytes = new byte[buffer.limit()];
buffer.get(bytes);
System.out.println(new String(bytes, 0, buffer.limit()));// abcde
System.out.println(buffer.position());// 5
System.out.println(buffer.limit());// 5
System.out.println(buffer.capacity());// 10
System.out.println("============rewind()============");
buffer.rewind();
System.out.println(buffer.position());// 0
System.out.println(buffer.limit());// 5
System.out.println(buffer.capacity());// 10
buffer.clear();
System.out.println("============clear()============");
System.out.println(buffer.position());// 0
System.out.println(buffer.limit());// 10
System.out.println(buffer.capacity());// 10
}
Channel(通道)
Channel(通道):Channel是一个对象,可以通过它读取和写入数据。可以把它看做是IO中的流,不同的是:
- Channel是双向的,既可以读又可以写,而流是单向的
- Channel可以进行异步的读写
- 对Channel的读写必须通过buffer对象
正如上面提到的,所有数据都通过Buffer对象处理,所以,您永远不会将字节直接写入到Channel中,相反,您是将数据写入到Buffer中;同样,您也不会从Channel中读取字节,而是将数据从Channel读入Buffer,再从Buffer获取这个字节。简而言之:Channel负责传输,buffer负责存储
因为Channel是双向的,所以Channel可以比流更好地反映出底层操作系统的真实情况。特别是在Unix模型中,底层操作系统通常都是双向的。
在Java NIO中的Channel主要有如下几种类型:
- FileChannel:从文件读取数据的
- DatagramChannel:读写UDP网络协议数据
- SocketChannel:读写TCP网络协议数据
- ServerSocketChannel:可以监听TCP连接
FileChannel的使用
我们将通过CopyFile这个实力让大家体会NIO的操作过程。CopyFile执行三个基本的操作:创建一个Buffer,然后从源文件读取数据到缓冲区,然后再将缓冲区写入目标文件。
public static void copyFileUseNIO(String src,String dst) throws IOException{
//声明源文件和目标文件
FileInputStream fi=new FileInputStream(new File(src));
FileOutputStream fo=new FileOutputStream(new File(dst));
//获得传输通道channel
FileChannel inChannel=fi.getChannel();
FileChannel outChannel=fo.getChannel();
//获得容器buffer
ByteBuffer buffer=ByteBuffer.allocate(1024);
while(inChannel.read(buffer) != -1){
//判断是否读完文件
//重设一下buffer的position=0
buffer.flip();
//开始写
outChannel.write(buffer);
//写完要重置buffer,重设position=0,limit=capacity
buffer.clear();
}
inChannel.close();
outChannel.close();
fi.close();
fo.close();
}
SocketChannel的使用
- 客户端
public class Client {
public static void main(String[] args) {
try (SocketChannel socket = SocketChannel.open()) {
socket.connect(new InetSocketAddress("localhost", 8888));
//1.先发一条
ByteBuffer buf = ByteBuffer.allocate(100);
buf.put("你好服务器,我是客户端".getBytes());
buf.flip();//limit设置为position,position设置为0
socket.write(buf);//输出从position到limit之间的数据
//2.再收一条,不确定字数是多少,但最多是100字节。先准备100字节空间
ByteBuffer inBuffer = ByteBuffer.allocate(100);
socket.read(inBuffer);
inBuffer.flip();//limit设置为position,position设置为0
String msg = new String(inBuffer.array(),0,inBuffer.limit());
System.out.println("【客户端】收到信息:" + msg);
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
System.out.println("客户端完毕!");
}
}
- 服务端
public class Server {
public static void main(String[] args) {
try (ServerSocketChannel serverChannel = ServerSocketChannel.open())
{
serverChannel.bind(new InetSocketAddress(8888));
System.out.println("【服务器】等待客户端连接...");
SocketChannel accept = serverChannel.accept();//阻塞的
System.out.println("【服务器】有连接到达...");
//1.先发一条
ByteBuffer outBuffer = ByteBuffer.allocate(100);
outBuffer.put("你好客户端,我是服务器".getBytes());
outBuffer.flip();//limit设置为position,position设置为0
accept.write(outBuffer);//输出从position到limit之间的数据
//2.再收一条,不确定字数是多少,但最多是100字节。先准备100字节空间
ByteBuffer inBuffer = ByteBuffer.allocate(100);
accept.read(inBuffer);
inBuffer.flip();//limit设置为position,position设置为0
String msg = new String(inBuffer.array(),0,inBuffer.limit());
System.out.println("【服务器】收到信息:" + msg);
accept.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
Selector
Channel : 通道 ,负责传输 类似: 铁轨
Buffer: 缓冲区,负责读写数据 类似 : 火车
Selector: 选择器,负责调度通道 类似: 指挥中心
选择器Selector是NIO中的重要技术之一,它与SelectableChannel联合使用了非阻塞的多路复用.使用它可以节省CPU资源,提供程序的运行效率.
多路是指:服务器同时监听多个"端口"的情况,每个端口都要监听多个多个客户端连接
- 服务器端的非多路复用效果
- 服务器端多路复用效果
使用了多路复用,只需要一个县城就可以处理多个通道,降低内存占用率,减少cpu切换时间,在高并发,高频段业务环境下有非常重要的优势
Selector是一个对象,它可以注册到很多个Channel上,监听各个Channel上发生的事件,并且能够根据事件情况决定Channel读写。这样,通过一个线程管理多个Channel,就可以处理大量网络连接了。
有了Selector,我们就可以利用一个线程来处理所有的channels。线程之间的切换对操作系统来说代价是很高的,并且每个线程也会占用一定的系统资源。所以,对系统来说使用的线程越少越好。
1.如何创建一个Selector
Selector 就是您注册对各种 I/O 事件兴趣的地方,而且当那些事件发生时,就是这个对象告诉您所发生的事件。
Selector selector = Selector.open();
2.注册Channel到Selector
为了能让Channel和Selector配合使用,我们需要把Channel注册到Selector上。通过调用 channel.register()方法来实现注册:
channel.configureBlocking(false);// 非阻塞
SelectionKey key =channel.register(selector,SelectionKey.OP_READ);// 注册
注意,注册的Channel 必须设置成异步模式 才可以,否则异步IO就无法工作,这就意味着我们不能把一个FileChannel注册到Selector,因为FileChannel没有异步模式,但是网络编程中的SocketChannel是可以的。
3.关于SelectionKey
请注意对register()的调用的返回值是一个SelectionKey。 SelectionKey 代表这个通道在此 Selector 上注册。当某个 Selector 通知您某个传入事件时,它是通过提供对应于该事件的 SelectionKey 来进行的。SelectionKey 还可以用于取消通道的注册。
SelectionKey中包含如下属性:
- The interest set
- The ready set
- The Channel
- The Selector
- An attached object (optional)
(1) Interest set
就像我们在前面讲到的把Channel注册到Selector来监听感兴趣的事件,interest set就是你要选择的感兴趣的事件的集合。你可以通过SelectionKey对象来读写interest set:
int interestSet = selectionKey.interestOps();
boolean isInterestedInAccept = interestSet & SelectionKey.OP_ACCEPT;
boolean isInterestedInConnect = interestSet & SelectionKey.OP_CONNECT;
boolean isInterestedInRead = interestSet & SelectionKey.OP_READ;
boolean isInterestedInWrite = interestSet & SelectionKey.OP_WRITE;
通过上面例子可以看到,我们可以通过用AND 和SelectionKey 中的常量做运算,从SelectionKey中找到我们感兴趣的事件。
SelectionKey.OP_ACCEPT : 接收连接就绪事件,表示服务器监听到了客户连接,服务器可以接收这个连接了
SelectionKey.OP_CONNECT:连接就绪事件,表示客户端和服务器的连接已经建立成功
SelectionKey.OP_READ: 读就绪事件,表示通道中有了可读的数据,可以执行读操作了
SelectionKey.OP_WRITE: 写就绪事件,表示已经可以向通道中写数据了(通道目前可以用于写操作)
(2) Ready Set
ready set 是通道已经准备就绪的操作的集合。在一次选Selection之后,你应该会首先访问这个ready set。Selection将在下一小节进行解释。可以这样访问ready集合:
int readySet = selectionKey.readyOps();
可以用像检测interest集合那样的方法,来检测Channel中什么事件或操作已经就绪。但是,也可以使用以下四个方法,它们都会返回一个布尔类型:
selectionKey.isAcceptable();
selectionKey.isConnectable();
selectionKey.isReadable();
selectionKey.isWritable();
(3) Channel 和 Selector
我们可以通过SelectionKey获得Selector和注册的Channel:
Channel channel = selectionKey.channel();
Selector selector = selectionKey.selector();
(4) Attach一个对象
可以将一个对象或者更多信息attach 到SelectionKey上,这样就能方便的识别某个给定的通道。例如,可以附加 与通道一起使用的Buffer,或是包含聚集数据的某个对象。使用方法如下:
selectionKey.attach(theObject);
Object attachedObj = selectionKey.attachment();
还可以在用register()方法向Selector注册Channel的时候附加对象。如:
SelectionKey key = channel.register(selector, SelectionKey.OP_READ, theObject);
4.关于SelectedKeys()
生产系统中一般会额外进行就绪状态检查
一旦调用了select()方法,它就会返回一个数值,表示一个或多个通道已经就绪,然后你就可以通过调用selector.selectedKeys()方法返回的SelectionKey集合来获得就绪的Channel。请看演示方法:
Set<SelectionKey> selectedKeys = selector.selectedKeys();
当你通过Selector注册一个Channel时,channel.register()方法会返回一个SelectionKey对象,这个对象就代表了你注册的Channel。这些对象可以通过selectedKeys()方法获得。你可以通过迭代这些selected key来获得就绪的Channel,下面是演示代码:
Set<SelectionKey> selectedKeys = selector.selectedKeys();
Iterator<SelectionKey> keyIterator = selectedKeys.iterator();
while(keyIterator.hasNext()) {
SelectionKey key = keyIterator.next();
if(key.isAcceptable()) {
// a connection was accepted by a ServerSocketChannel.
} else if (key.isConnectable()) {
// a connection was established with a remote server.
} else if (key.isReadable()) {
// a channel is ready for reading
} else if (key.isWritable()) {
// a channel is ready for writing
}
keyIterator.remove();
}
这个循环遍历selected key的集合中的每个key,并对每个key做测试来判断哪个Channel已经就绪。
请注意循环中最后的keyIterator.remove()方法。Selector对象并不会从自己的selected key集合中自动移除SelectionKey实例。我们需要在处理完一个Channel的时候自己去移除。当下一次Channel就绪的时候,Selector会再次把它添加到selected key集合中。
SelectionKey.channel()方法返回的Channel需要转换成你具体要处理的类型,比如是ServerSocketChannel或者SocketChannel等等。
IO 都是同步阻塞模式,所以需要多线程以实现多任务处理。而 NIO 则是利用了单线程轮询事件的机制,通过高效地定位就绪的 Channel,来决定做什么,仅仅 select 阶段是阻塞的,可以有效避免大量客户端连接时,频繁线程切换带来的问题,应用的扩展能力有了非常大的提高
多路复用案例 不发送数据:
- Server
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.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;
public class Server {
public static void main(String[] args) throws IOException,
InterruptedException {
//1.同时监听三个端口:7777,8888,9999
ServerSocketChannel serverChannel1 = ServerSocketChannel.open();
serverChannel1.bind(new InetSocketAddress(7777));
serverChannel1.configureBlocking(false);// 设置异步
ServerSocketChannel serverChannel2 = ServerSocketChannel.open();
serverChannel2.bind(new InetSocketAddress(8888));
serverChannel2.configureBlocking(false);
ServerSocketChannel serverChannel3 = ServerSocketChannel.open();
serverChannel3.bind(new InetSocketAddress(9999));
serverChannel3.configureBlocking(false);
//2.获取一个选择器
Selector selector = Selector.open();
//3.注册三个通道 注册服务端和客户端通道的 接收连接就绪事件
SelectionKey key1 = serverChannel1.register(selector, SelectionKey.OP_ACCEPT);
SelectionKey key2 = serverChannel2.register(selector, SelectionKey.OP_ACCEPT);
SelectionKey key3 = serverChannel3.register(selector, SelectionKey.OP_ACCEPT);
//4.循环监听三个通道
boolean isRun = true;
while (isRun) {
System.out.println();
System.out.println("等待客户端连接...");
// 这个方法可能会阻塞,直到至少有一个已注册的事件发生,或者当一个或者更多的事件发生时
int keyCount = selector.select();//阻塞的
System.out.println("有一个客户端连接成功...");
System.out.println("keyCount = " + keyCount);
System.out.println("注册通道的数量 = " + selector.keys().size());
System.out.println("已连接的数量 = " +
selector.selectedKeys().size());
System.out.println();
//遍历已连接的每个通道
Set<SelectionKey> set2 = selector.selectedKeys();
Iterator<SelectionKey> it = set2.iterator();
while (it.hasNext()) {
SelectionKey key = it.next();
System.out.println("获取通道...");
//获得服务端的channel
ServerSocketChannel channel =
(ServerSocketChannel) key.channel();
System.out.println("等待【" + channel.getLocalAddress() + "】通道数据...");
// 获得服务端和客户端连接的通道
SocketChannel socketChannel = channel.accept();
//接收数据(略)
it.remove();
}
System.out.println("休息1秒,等待下一个连接...");
Thread.sleep(1000);
}
}
}
- Client2
nel.close();
} catch (IOException e) {
e.printStackTrace();
}
}).start();
new Thread(()->{
SocketChannel socketChannel = null;
try {
socketChannel = SocketChannel.open();
socketChannel.connect(new
InetSocketAddress("localhost",9999));
socketChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}).start();
}
}
多路复用,发送数据
- Server
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 Server {
public static void main(String[] args) throws IOException,
InterruptedException {
//1.同时监听三个端口:7777,8888,9999
ServerSocketChannel serverChannel1 = ServerSocketChannel.open();
serverChannel1.bind(new InetSocketAddress(7777));
serverChannel1.configureBlocking(false);// 设置非阻塞
ServerSocketChannel serverChannel2 = ServerSocketChannel.open();
serverChannel2.bind(new InetSocketAddress(8888));
serverChannel2.configureBlocking(false);
ServerSocketChannel serverChannel3 = ServerSocketChannel.open();
serverChannel3.bind(new InetSocketAddress(9999));
serverChannel3.configureBlocking(false);
//2.获取一个选择器
Selector selector = Selector.open();
//3.注册三个通道 注册服务端和客户端通道的 接收就绪事件
// serverChannel1.register(selector, SelectionKey.OP_ACCEPT|SelectionKey.OP_CONNECT|SelectionKey.OP_READ|SelectionKey.OP_WRITE);
// serverChannel2.register(selector, SelectionKey.OP_ACCEPT|SelectionKey.OP_CONNECT|SelectionKey.OP_READ|SelectionKey.OP_WRITE);
// serverChannel3.register(selector, SelectionKey.OP_ACCEPT|SelectionKey.OP_CONNECT|SelectionKey.OP_READ|SelectionKey.OP_WRITE);
serverChannel1.register(selector, SelectionKey.OP_ACCEPT);
serverChannel2.register(selector, SelectionKey.OP_ACCEPT);
serverChannel3.register(selector, SelectionKey.OP_ACCEPT);
//4.循环监听三个通道
boolean isRun = true;
while (isRun) {
System.out.println();
System.out.println("等待客户端连接...");
// 这个方法可能会阻塞,直到至少有一个已注册的事件发生,或者当一个或者更多的事件发生时
int keyCount = selector.select();//阻塞的
System.out.println("有一个客户端连接成功...");
System.out.println("keyCount = " + keyCount);
System.out.println("注册通道的数量 = " + selector.keys().size());
System.out.println("已连接的数量 = " +
selector.selectedKeys().size());
System.out.println();
//遍历已连接的每个通道
Set<SelectionKey> set2 = selector.selectedKeys();
Iterator<SelectionKey> it = set2.iterator();
/*ServerSocketChannel channel = null;
SocketChannel socketChannel = null;*/
while (it.hasNext()) {
SelectionKey key = it.next();
/*try {
System.out.println("获取通道...");
channel =
(ServerSocketChannel) key.channel();
System.out.println("等待【" + channel.getLocalAddress() + "】通道数据...");
// 获得服务端和客户端连接的通道
socketChannel = channel.accept();
ByteBuffer buf = ByteBuffer.allocate(1000);
socketChannel.read(buf);
buf.flip();
System.out.println("接收到的数据是:" + new String(buf.array(), 0, buf.limit()));
} catch (IOException e) {
e.printStackTrace();
}
it.remove();*/
//接收数据(略)
it.remove();
//服务端的channel
ServerSocketChannel serverChannel = null;
//获得和客户端连接的通道
SocketChannel socketChannel = null;
//客户端请求连接事件
if (key.isAcceptable()) {
serverChannel = (ServerSocketChannel) key.channel();
//获得服务端和客户端连接的通道
socketChannel = serverChannel.accept();
//将服务端和客户端连接的设置非阻塞
socketChannel.configureBlocking(false);
//注册服务端和客户端通道的读事件
socketChannel.register(selector, SelectionKey.OP_READ);
}else if (key.isReadable()){
ByteBuffer buf = ByteBuffer.allocate(1024);
//获取通道对象,方便后面将通道内的数据读入缓冲区
socketChannel = (SocketChannel) key.channel();
socketChannel.read(buf);
buf.flip();
System.out.println(new String(buf.array(),0,buf.limit()));
Thread.sleep(5000);
}
System.out.println("休息1秒,等待下一个连接...");
Thread.sleep(1000);
}
}
}
}
- 客户端
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
public class Client2 {
public static void main(String[] args) {
new Thread(() -> {
SocketChannel socketChannel = null;
try {
socketChannel = SocketChannel.open();
socketChannel.connect(new
InetSocketAddress("localhost", 8888));
ByteBuffer buf = ByteBuffer.allocate(1024);
buf.put("服务器你好,我是客户端8888...".getBytes());
buf.flip();
socketChannel.write(buf);
socketChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}).start();
new Thread(() -> {
SocketChannel socketChannel = null;
try {
socketChannel = SocketChannel.open();
socketChannel.connect(new
InetSocketAddress("localhost", 9999));
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//1.先发一条
ByteBuffer buf = ByteBuffer.allocate(100);
buf.put("你好服务器,我是客户端9999....".getBytes());
buf.flip();//limit设置为position,position设置为0
socketChannel.write(buf);//输出从position到limit之间的数据
socketChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}).start();
}
}
NIO2
在 Java 7 中,NIO 有了进一步的改进,也就是 NIO 2,引入了异步非阻塞 IO 方式,也有很多人叫它 AIO(Asynchronous IO)。异步 IO 操作基于事件和回调机制,可以简单理解为,应用操作直接返回,而不会阻塞在那里,当后台处理完成,操作系统会通知相应线程进行后续工作。
AIO是异步IO的缩写,虽然NIO在网络操作中,提供了非阻塞的方法,但是NIO的IO行为还是同步的。对于NIO来说,我们的业务线程是在IO操作准备好时,得到通知,接着就由这个线程自行进行IO操作,IO操作本身是同步的。但是对AIO来说,则更加进了一步,它不是在IO准备好时再通知线程,而是在IO操作已经完成后,再给线程发出通知。因此AIO是不会阻塞的,此时我们的业务逻辑将变成一个回调函数,等待IO操作完成后,由系统自动触发。与NIO不同,当进行读写操作时,只须直接调用API的read或write方法即可。这两种方法均为异步的,对于读操作而言,当有流可读取时,操作系统会将可读的流传入read方法的缓冲区,并通知应用程序;对于写操作而言,当操作系统将write方法传递的流写入完毕时,操作系统主动通知应用程序。 即可以理解为,read/write方法都是异步的,完成后会主动调用回调函数。 在JDK1.7中,这部分内容被称作NIO.2,主要在Java.nio.channels包下增加了下面四个异步通道:
- AsynchronousSocketChannel
- AsynchronousServerSocketChannel
- AsynchronousFileChannel
- AsynchronousDatagramChannel
在AIO socket编程中,服务端通道是AsynchronousServerSocketChannel,这个类提供了一个open()静态工厂,一个bind()方法用于绑定服务端IP地址(还有端口号),另外还提供了accept()用于接收用户连接请求。在客户端使用的通道是AsynchronousSocketChannel,这个通道处理提供open静态工厂方法外,还提供了read和write方法。在AIO编程中,发出一个事件(accept read write等)之后要指定事件处理类(回调函数),AIO中的事件处理类是CompletionHandler<V,A>,这个接口定义了如下两个方法,分别在异步操作成功和失败时被回调。
void completed(V result, A attachment);
void failed(Throwable exc, A attachment);