AIO与NIO和BIO
AIO与BIO和NIO
1.相关名词概念
在高性能的IO体系设计中,有几个名词概念常常会使我们感到迷惑不解。具体如下:
*[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FCJ4NV1m-1594734567205)(C:\Users\Lenovo\Desktop\QQ图片20200429222056_爱奇艺.jpg)]
1.同步,异步,阻塞,非阻塞
同步:指用户进程触发IO操作并等待或者去查看IO操作是否就绪
举例:自己上街买衣服,自己亲自干这件事,别的事情做不了
异步:异步是指用户进程触发IO操作以后开始做自己的事情,而当IO操作完成时会得到IO完成的通知异步的特点就是通知
举例:告诉朋友自己合适的衣服,尺寸,大小.颜色.让朋友去买,自己可以去干别的事.(使用异步IO事,java会将IO读写委托给OS处理,需要将数据缓冲区的地址和大小传给OS)
阻塞:所谓阻塞方式,试图对改文件描述进行读写时,如果当时没有东西刻度或暂时不可写,程序进入等待状态,直到有东西可读或者有东西可以写为止
举例:去公交站充值,发现这个时候,工作人员不在,然而我们要在这里等待,一直等到工作人员回来
非阻塞:非阻塞状态下,如果没有东西可读或者不可写,读写函数会马上返回,而不会等待
2.组合概念
同步阻塞(BIO):同步并阻塞,服务器实现一个连接一个的线程,即客户端有连接请求时,服务端就需要启动一个线程来处理,如果这个线程不进行任何处理那么会造成不必要的线程开销,当然可以通过线程池机制改善.
同步非阻塞(NIO):服务器实现一个请求一个的线程,即客户端发送的连接请求都会注册到复用器上,多路复用器轮训到有连接的IO请求时启动一个线程进行处理.用户进程也需要是不是的询问IO操作是否就绪,这就要求进程不停地去询问.
异步阻塞(NIO): 此种方式下是指应用发起一个IO操作以后,不等待内核IO操作的完成,等内核完成IO操作以后会通知应用程序,这其实就是同步和异步最关键的区别,同步必须等待或者主动的去询问IO是否完成,那么为什么说是阻塞的呢?因为此时是通过select系统调用来完成的,而select函数本身的实现方式是阻塞的,而采用select函数有个好处就是它可以同时监听多个文件句柄(如果从UNP的角度看,select属于同步操作。因为select之后,进程还需要读写数据),从而提高系统的并发性!
异步非阻塞(AIO(NIO2)):在此种模式下,用户进程只需要发起一个IO操作然后立即返回,等IO操作真正的完成以后,应用程序会得到IO操作完成的通知,此时用户进程只需要对数据进行处理就好了,不需要进行实际的IO读写操作,因为真正的IO读取或者写入操作已经由内核完成了。
2.BIO,NIO,AIO的使用场景
BIO方式适用于连接数目比较小且固定的架构,这种方式对服务器资源要求比较高,并发局限于应用中,JDK1.4以前的唯一选择,但程序直观简单易理解。
NIO方式适用于连接数目多且连接比较短(轻操作)的架构,比如聊天服务器,并发局限于应用中,编程比较复杂,JDK1.4开始支持。
AIO方式使用于连接数目多且连接比较长(重操作)的架构,比如相册服务器,充分调用OS参与并发操作,编程比较复杂,JDK7开始支持。
搞清楚了以上概念以后,我们再回过头来看看,Reactor模式和Proactor模式。
(其实阻塞与非阻塞都可以理解为同步范畴下才有的概念,对于异步,就不会再去分阻塞非阻塞。对于用户进程,接到异步通知后,就直接操作进程用户态空间里的数据好了。)
首先来看看Reactor模式,Reactor模式应用于同步I/O的场景。我们分别以读操作和写操作为例来看看Reactor中的具体步骤:
读取操作:
1. 应用程序注册读就绪事件和相关联的事件处理器2. 事件分离器等待事件的发生
3. 当发生读就绪事件的时候,事件分离器调用第一步注册的事件处理器
4. 事件处理器首先执行实际的读取操作,然后根据读取到的内容进行进一步的处理
写入操作类似于读取操作,只不过第一步注册的是写就绪事件。
下面我们来看看Proactor模式中读取操作和写入操作的过程:
读取操作:
1. 应用程序初始化一个异步读取操作,然后注册相应的事件处理器,此时事件处理器不关注读取就绪事件,而是关注读取完成事件,这是区别于Reactor的关键。2. 事件分离器等待读取操作完成事件
3. 在事件分离器等待读取操作完成的时候,操作系统调用内核线程完成读取操作(异步IO都是操作系统负责将数据读写到应用传递进来的缓冲区供应用程序操作,操作系统扮演了重要角色),并将读取的内容放入用户传递过来的缓存区中。这也是区别于Reactor的一点,Proactor中,应用程序需要传递缓存区。
4. 事件分离器捕获到读取完成事件后,**应用程序注册的事件处理器,事件处理器直接从缓存区读取数据,而不需要进行实际的读取操作。
Proactor中写入操作和读取操作,只不过感兴趣的事件是写入完成事件。
从上面可以看出,Reactor和Proactor模式的主要区别就是真正的读取和写入操作是有谁来完成的,Reactor中需要应用程序自己读取或者写入数据,而Proactor模式中,应用程序不需要进行实际的读写过程,它只需要从缓存区读取或者写入即可,操作系统会读取缓存区或者写入缓存区到真正的IO设备.
综上所述,同步和异步是相对于应用和内核的交互方式而言的,同步 需要主动去询问,而异步的时候内核在IO事件发生的时候通知应用程序,而阻塞和非阻塞仅仅是系统在调用系统调用的时候函数的实现方式而已
**如果你想吃一份宫保鸡丁盖饭:
同步阻塞:你到饭馆点餐,然后在那等着,还要一边喊:好了没啊!
同步非阻塞:在饭馆点完餐,就去遛狗了。不过溜一会儿,就回饭馆喊一声:好了没啊!
异步阻塞:遛狗的时候,接到饭馆电话,说饭做好了,让您亲自去拿。
异步非阻塞:饭馆打电话说,我们知道您的位置,一会给你送过来,安心遛狗就可以了。**
**“一个IO操作其实分成了两个步骤:发起IO请求和实际的IO操作。
同步IO和异步IO的区别就在于第二个步骤是否阻塞,如果实际的IO读写阻塞请求进程,那么就是同步IO。
阻塞IO和非阻塞IO的区别在于第一步,发起IO请求是否会被阻塞,如果阻塞直到完成那么就是传统的阻塞IO,如果不阻塞,那么就是非阻塞IO。同步和异步是针对应用程序和内核的交互而言的,同步指的是用户进程触发IO操作并等待或者轮询的去查看IO操作是否就绪,而异步是指用户进程触发IO操作以后便开始做自己的事情,而当IO操作已经完成的时候会得到IO完成的通知。而阻塞和非阻塞是针对于进程在访问数据的时候,根据IO操作的就绪状态来采取的不同方式,说白了是一种读取或者写入操作函数的实现方式,阻塞方式下读取或者写入函数将一直等待,而非阻塞方式下,读取或者写入函数会立即返回一个状态值。
所以,IO操作可以分为3类:同步阻塞(即早期的IO操作)、同步非阻塞(NIO)、异步(AIO)。
同步阻塞:
在此种方式下,用户进程在发起一个IO操作以后,必须等待IO操作的完成,只有当真正完成了IO操作以后,用户进程才能运行。JAVA传统的IO模型属于此种方式。同步非阻塞:
在此种方式下,用户进程发起一个IO操作以后边可返回做其它事情,但是用户进程需要时不时的询问IO操作是否就绪,这就要求用户进程不停的去询问,从而引入不必要的CPU资源浪费。其中目前JAVA的NIO就属于同步非阻塞IO。
异步:
此种方式下是指应用发起一个IO操作以后,不等待内核IO操作的完成,等内核完成IO操作以后会通知应用程序。”这段话比较清楚**
3.BIO,NIO,AIO的代码实现
1.BIO代码实现
服务器端
public static void main(String[] args) throws IOException {
//创建对象
//ServerSocket ss = new ServerSocket(8888);
//创建
ServerSocketChannel ssc = ServerSocketChannel.open();
//服务器绑定端口
ssc.bind(new InetSocketAddress(8888));
//连接上客户端
System.out.println(1);
SocketChannel sc = ssc.accept();
System.out.println(2);
//服务器端接受数据
//创建数组
ByteBuffer buffer = ByteBuffer.allocate(1024);
//接收数据
int len = sc.read(buffer);
//打印结构
System.out.println(new String(buffer.array(),0,len));
//关闭资源
sc.close();
}
客户端
//创建对象
//Socket s = new Socket("127.0.0.1",8888);
//创建对象
SocketChannel sc = SocketChannel.open();
//连接服务器
sc.connect(new InetSocketAddress("127.0.0.1",8888));
//客户端发数据
//创建数组
ByteBuffer buffer = ByteBuffer.allocate(1024);
//数组中添加数据
buffer.put("你好啊~".getBytes());
//切换
buffer.flip();
//发出数据
sc.write(buffer);
//关流
sc.close();
2.NIO代码实现
服务器端
public static void main(String[] args) throws IOException, InterruptedException {
//创建对象
ServerSocketChannel ssc = ServerSocketChannel.open();
//绑定端口
ssc.bind(new InetSocketAddress(8000));
//设置非阻塞连接
//写成false叫非阻塞,非阻塞值得是不管有没有客户端他都会往下执行不会停留
ssc.configureBlocking(false);
while(true) {
//获取客户端连接
SocketChannel sc = ssc.accept();
if(sc != null){
//不等于null说明连接上了客户端
System.out.println("连接上了。。");
//读取数据操作
}else{
//没连接上客户端
System.out.println("打会儿游戏~");
Thread.sleep(2000);
}
}
客户端
public static void main(String[] args) throws IOException {
//创建对象
SocketChannel sc = SocketChannel.open();
//连接服务器指定服务器的ip和端口
sc.connect(new InetSocketAddress("127.0.0.1",8000));
}
3.AIO代码实现
服务器端
//异步非阻塞连接和读取
public static void main(String[] args) throws IOException {
//创建对象
AsynchronousServerSocketChannel assc = AsynchronousServerSocketChannel.open();
//绑定端口
assc.bind(new InetSocketAddress(8000));
//异步非阻塞连接!!!!
//第一个参数是一个附件
System.out.println(1);
assc.accept(null, new CompletionHandler<AsynchronousSocketChannel, Object>() {
@Override
public void completed(AsynchronousSocketChannel s, Object attachment) {
//如果连接客户端成功,应该获取客户端发来的数据
//completed()的第一个参数表示的是Socket对象.
System.out.println(5);
//创建数组
ByteBuffer buffer = ByteBuffer.allocate(1024);
//异步非阻塞读!!!!!
s.read(buffer, null, new CompletionHandler<Integer, Object>() {
@Override
public void completed(Integer len, Object attachment) {
//读取成功会自动调用这个方法
//completed()方法的第一个参数是read()读取到的实际个数
//打印数据
System.out.println(6);
System.out.println(new String(buffer.array(),0,len));
}
@Override
public void failed(Throwable exc, Object attachment) {
}
});
System.out.println(4);
}
@Override
public void failed(Throwable exc, Object attachment) {
}
});
System.out.println(2);
//让程序别结束写一个死循环
while(true){
}
}
客户端
public static void main(String[] args) throws IOException {
//客户端也有AIO对象
AsynchronousSocketChannel asc = AsynchronousSocketChannel.open();
System.out.println(1);
//指定要连接的服务器的ip和端口
asc.connect(new InetSocketAddress("123.23.44.3",8000), null, new CompletionHandler<Void, Object>() {
@Override
public void completed(Void result, Object attachment) {
//成功就执行这个方法
System.out.println(3);
}
@Override
public void failed(Throwable exc, Object attachment) {
//失败就执行这个方法
System.out.println(4);
}
});
System.out.println(2);
//写一个循环让main方法别结束
while(true){
}
}
4.NIO选择器
1.选择器的使用
服务器端
public static void main(String[] args) throws IOException {
//1.创建服务器对象
ServerSocketChannel ssc = ServerSocketChannel.open();
//2.绑定端口号
ssc.bind(new InetSocketAddress(8888));
//3.设置非阻塞
ssc.configureBlocking(false);
//要把服务器的事件交给Selector去做
//4.创建Selector对象
Selector selector = Selector.open();
//5.把Selector注册到Channel上
//把服务器的accept()方法交给Selector去管理
ssc.register(selector, SelectionKey.OP_ACCEPT);
//获取存在被连接的服务器对象的集合
Set<SelectionKey> set = selector.selectedKeys();
System.out.println("集合中的对象个数" + set.size());
System.out.println(1);
selector.select(); //Selector他现在在管理着服务器,他在这里等待客户端的连接
System.out.println(2);
System.out.println("集合中的对象个数" + set.size());
}
客户端
public static void main(String[] args) throws IOException {
//创建对象
SocketChannel sc = SocketChannel.open();
//连接服务器
sc.connect(new InetSocketAddress("127.0.0.1",8888));
//客户端发数据
//创建数组
ByteBuffer buffer = ByteBuffer.allocate(1024);
//数组中添加数据
buffer.put("你好啊~".getBytes());
//切换
buffer.flip();
//发出数据
sc.write(buffer);
//关流
sc.close();
}
2.选择器用法
服务器
public static void main(String[] args) throws Exception{
//创建服务器对象
ServerSocketChannel ssc = ServerSocketChannel.open();
//绑定端口
ssc.bind(new InetSocketAddress(8888));
//创建第二个服务器对象
ServerSocketChannel ssc2 = ServerSocketChannel.open();
//绑定端口
ssc2.bind(new InetSocketAddress(9999));
//创建第二个服务器对象
ServerSocketChannel ssc3 = ServerSocketChannel.open();
//绑定端口
ssc3.bind(new InetSocketAddress(7777));
//设置成非阻塞
ssc.configureBlocking(false);
ssc2.configureBlocking(false);
ssc3.configureBlocking(false);
//创建Selector
Selector selector = Selector.open();
//注册
//把服务器的accept()交给选择器去管理
ssc.register(selector, SelectionKey.OP_ACCEPT);
ssc2.register(selector, SelectionKey.OP_ACCEPT);
ssc3.register(selector, SelectionKey.OP_ACCEPT);
while(true) {
//获取存放所有服务器对象的Set集合
Set<SelectionKey> set2 = selector.keys();
System.out.println("set2集合的个数" + set2.size());
//把被连接到的服务器对象放到Set集合中
Set<SelectionKey> set = selector.selectedKeys();
System.out.println("集合的个数" + set.size()); //9999服务器已经处理完了事情,但是这个对象依旧存在于集合中
//让Selector去接受客户端
selector.select();
System.out.println("set2集合的个数" + set2.size());
System.out.println("集合的个数" + set.size()); //2
//8888有任务 9999已经没任务 在循环遍历集合时,里面遍历到了已经没任务的对象,去操作时就会出现异常
//解决办法:
// 使用完一个对象,就应该把这个对象从集合中删除
//遍历集合
//获取迭代器
Iterator<SelectionKey> it = set.iterator();
//迭代器遍历
while (it.hasNext()) {
SelectionKey key = it.next();
//获取channel对象
SelectableChannel channel = key.channel();
//向下转型
ServerSocketChannel ss = (ServerSocketChannel) channel;
//获取到客户端
SocketChannel s = ss.accept();
//创建数组
ByteBuffer buffer = ByteBuffer.allocate(1024);
//读取数据
int len = s.read(buffer);
//buffer.flip();
//打印
System.out.println(new String(buffer.array(), 0, len));
//使用完了这个对象就从集合中删除
it.remove();
}
}
}
tChannel s = ss.accept();
//创建数组
ByteBuffer buffer = ByteBuffer.allocate(1024);
//读取数据
int len = s.read(buffer);
//buffer.flip();
//打印
System.out.println(new String(buffer.array(), 0, len));
//使用完了这个对象就从集合中删除
it.remove();
}
}
}