AIO模型

AIO:异步非阻塞

AIO需要操作系统的支持,在Linux内核2.6版本之后增加了对真正异步IO的实现。java从JDK1.7之后支持AIO,JDK1.7新增一些与文件/网络IO相关的一些API,称之为NIO2.0或者称之为AIO(Asynchronous IO)。AIO最大的特征提供了异步功能,对于socket网络通信和文件IO都是起作用的。

与NIO不同,当进行读写操作时,只需直接调用API提供的读写方法,这些方法均是异步操作。

对于读操作而言:当有流可读取时,操作系统会将可读的流传入read方法的缓冲区,并通知应用程序。

对于写操作而言:当操作系统将write方法传递的流写入完成,操作系统会通知应用程序

读写操作都是异步的,完成之后会主动调用回调函数

在JDK1.7中,在java.nio.channels包下增加了四个异步通道

AsynchronousSocketChannel

AsynchronousServerSocketChannel

AsynchronousFileChannel

AsynchronousDatagramChannel

AsynchronousServerSocketChannel在AIO中服务端socket

以accept为例:

AsynchronousServerSocketChannel创建成功之后,需要调用accept方法来接收客户端的连接

由于异步IO实际上是把IO操作交给操作系统来做的,用户程序只负责通知操作系统进行IO和接收操作系统IO完成的通知,所以accept方法调用后,当前的用户线程是不会被阻塞

AIO为accept提供了两个版本的处理

future方法:

Future<AsynchronousSocketChannel> accept();

当开始接收客户端的连接时,如果当前线程需要进行网络IO,则调用该方法的Future对象的get方法,此时get方法会阻塞当前线程,所以这种方式是阻塞式的异步IO

提交一个IO操作请求,会返回一个future,然后可以对future进行检查(get),确定他是否完成,后者阻塞IO指导操作正常完成或者超时异常。使用future方式简单,需要注意的是,future.get方法是同步的,如果使用future很容易使编程进入同步编程模式,使得异步编程成为摆设

callback方法:

<A> void accept(A attachment, CompletionHandler<AsynchronousSocketChannel,? super A> handler);

接收客户端的请求,连接成功或者失败都会触发CompletionHandler对象的相应方法,其中AsynchronousServerSocketChannel就代表该CompletionHandler处理器在处理连接成功时的result

CompletionHandler接口定义了两个方法,

void completed(V result, A attachment);当IO操作成功之后触发的方法,该方法的第一个参数代表IO操作返回的对象,第二个参数代表发起IO操作时传入的附加参数
void failed(Throwable exc, A attachment);当IO操作失败后触发的方法,第一个参数代表IO操作失败引发的一个异常或错误,第二个参数代表发起IO操作时传入的附加参数

当提交一个IO操作请求,并且指定一个CompletionHandler,当异步IO操作完成时,便发送一个通知,此时这个CompletionHandler对象的completed或者是failed方法会被调用

public class Server {
    public static void main(String[] args) {
        try {
            //创建异步通信channel
            AsynchronousServerSocketChannel asynchronousServerSocketChannel = AsynchronousServerSocketChannel.open();

            //绑定端口
            asynchronousServerSocketChannel.bind(new InetSocketAddress(9999));
            System.out.println("server端启动啦");

            //异步调用
            asynchronousServerSocketChannel.accept(null,new AcceptHandler(asynchronousServerSocketChannel));

            //该操作是异步操作,为了方式当前线程直接执行结束,人为设置阻塞
            while (true) {
                Thread.sleep(1000);
                System.out.println("循环中...");
            }
        } catch (IOException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}


public class AcceptHandler implements CompletionHandler<AsynchronousSocketChannel,Object> {
    private AsynchronousServerSocketChannel asynchronousServerSocketChannel;

    public AcceptHandler(AsynchronousServerSocketChannel asynchronousServerSocketChannel) {
        this.asynchronousServerSocketChannel = asynchronousServerSocketChannel;
    }

    @Override
    public void completed(AsynchronousSocketChannel channel, Object server) {
        //IO成功之后,返回是accept接收的AsynchronousSocketChannel

        try {
            System.out.println("有新客户端连接:"+channel.getRemoteAddress());
        } catch (IOException e) {
            e.printStackTrace();
        }
        //读取数据
        ByteBuffer buffer = ByteBuffer.allocateDirect(1024);
        //异步读
        channel.read(buffer,buffer,new ReadHandler(channel));

        //继续接受客户端端的连接
        asynchronousServerSocketChannel.accept(null,new AcceptHandler(asynchronousServerSocketChannel));

    }

    @Override
    public void failed(Throwable exc, Object attachment) {
        exc.printStackTrace();
    }
}

public class ReadHandler implements CompletionHandler<Integer,ByteBuffer> {
    private AsynchronousSocketChannel channel;
    public ReadHandler(AsynchronousSocketChannel channel) {
        this.channel = channel;
    }


    @Override
    public void completed(Integer result, ByteBuffer buffer) {
        //读取到用户的数据
        buffer.flip();
        byte[] bytes = new byte[buffer.remaining()];
        buffer.get(bytes);
        String msg = new String(bytes);
        System.out.println("读取到数据:"+msg);

        //继续接受数据
        buffer.clear();
        channel.read(buffer,buffer,new ReadHandler(channel));


    }

    @Override
    public void failed(Throwable exc, ByteBuffer attachment) {
        exc.printStackTrace();
    }
}

BIO\NIO\AIO比较:

BIO

同步阻塞式IO,在JDK1.4出来之前,我们建立网络连接的时候采用BIO模式,

需要先在服务端启动一个ServerSocket,然后在客户端启动Socket来对服务端进行通信,服务端会调用accept方法等待接收客户端的连接请求,一旦接收到一个连接请求,就可以建立通信套接字在这个通信套接字上进行读写操作,此时不能再接收其他客户端连接请求,只能等待同当前连接的客户端的操作执行完成。

如果BIO要能够同时处理多个客户端请求,就必须使用多线程,即每次accept阻塞等待来自客户端请求,一旦受到连接请求就建立通信套接字同时开启一个新的线程来处理这个套接字的数据读写请求,然后立刻又继续accept等待其他客户端连接请求,即为每一个客户端连接请求都创建一个线程来单独处理,大概原理图就像这样:

虽然此时服务器具备了高并发能力,即能够同时处理多个客户端请求了,但是却带来了一个问题,随着开启的线程数目增多,将会消耗过多的内存资源,导致服务器变慢甚至崩溃,NIO可以一定程度解决这个问题。

NIO

同步非阻塞式IO,关键是采用了事件驱动的思想来实现了一个多路转换器,NIO本身是基于事件驱动思想来完成的,其主要想解决的是BIO的高并发问题:

在使用同步I/O的网络应用中,如果要同时处理多个客户端请求,或是在客户端要同时和多个服务器进行通讯,就必须使用多线程来处理。也就是说,将每一个客户端请求分配给一个线程来单独处理。这样做虽然可以达到我们的要求,但同时又会带来另外一个问题。由于每创建一个线程,就要为这个线程分配一定的内存空间(也叫工作存储器),而且操作系统本身也对线程的总数有一定的限制。如果客户端的请求过多,服务端程序可能会因为不堪重负而拒绝客户端的请求,甚至服务器可能会因此而瘫痪。NIO基于Reactor,当socket有流可读或可写入socket时,操作系统会相应的通知引用程序进行处理,应用再将流读取到缓冲区或写入操作系统。  也就是说,这个时候,已经不是一个连接就要对应一个处理线程了,而是有效的请求,对应一个线程,当连接没有数据时,是没有工作线程来处理的。

AIO

与NIO不同,当进行读写操作时,只须直接调用API的read或write方法即可。这两种方法均为异步的,对于读操作而言,当有流可读取时,操作系统会将可读的流传入read方法的缓冲区,并通知应用程序;对于写操作而言,当操作系统将write方法传递的流写入完毕时,操作系统主动通知应用程序。  即可以理解为,read/write方法都是异步的,完成后会主动调用回调函数。 

类比生活实例

BIO、NIO、AIO适用场景

  • BIO方式适用于连接数目比较小且固定的架构,这种方式对服务器资源要求比较高,并发局限于应用中,JDK1.4以前的唯一选择,但程序直观简单易理解。
  • NIO方式适用于连接数目多且连接比较短(轻操作)的架构,比如聊天服务器,并发局限于应用中,编程比较复杂,JDK1.4开始支持。
  • AIO方式使用于连接数目多且连接比较长(重操作)的架构,比如相册服务器,充分调用OS参与并发操作,编程比较复杂,JDK1.7开始支持。另外,I/O属于底层操作,需要操作系统支持,并发也需要操作系统的支持,所以性能方面不同操作系统差异会比较明显。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值