AIO与NIO的实际区别是什么

技术AIO与NIO的实际区别是什么本篇内容主要讲解“AIO与NIO的实际区别是什么”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“AIO与NIO的实际区别是什么”吧!1. NI

本文主要解释“AIO和NIO的实际区别是什么”。感兴趣的朋友不妨看看。本文介绍的方法简单、快速、实用。让边肖学会“AIO和NIO的实际区别是什么”!

1. NIO2.0——AIO

1. 从某种程度上来说,NIO依然是同步阻塞的

虽然NIO中的Channel(网络通道)和Buffer可以实现无阻塞读/写操作,而Selector提供了复用功能,使得多个IO通道可以在一个线程中管理和使用,避免了传统IO的问题。但是在NIO中,当Selector调用select()方法进行通道选择时,仍然是同步阻塞的,而且由于Selector上注册了多个通道,所以这种方法会同时阻塞多个IO请求。虽然select()方法可以设置超时返回,但它仍然是不利的。

换句话说,虽然NIO在网络操作中提供了无阻塞读/写方法,但是NIO的IO行为是同步的。对于NIO来说,我们的业务线程是在IO操作准备好时(调用Selector中的select()方法),得到通知(select()方法返回,表示有准备好的Channel),接着就由这个线程自行进行IO操作(通过Channel进行read/write操作),在第一代NIO中,每个线程可以持有多个IO通道并选择使用它们,但实际上一个线程只能选择操作一个IO,而IO操作本身是同步的。

2. NIO改进

对于真正实现异步非阻塞,的输入输出操作,NIO被改进和升级为第二代NIO——,即AIO机制。

因此,AIO相比于NIO,则更加进了一步,它不是在IO准备好时再通知线程,而是在IO操作已经完成后,再给线程发出通知。AIO不会被封杀。这时我们的业务逻辑会变成回调函数,IO操作完成后系统会自动触发。这相当于

在AIO,读写的时候只需要直接调用API的读写方法。这两种方法是异步的。对于读操作,当有一个流要读时,操作系统会将可读流流式传输到read方法的缓冲区中,并通知应用程序。对于写操作,当操作系统写完写方法传递的流时,操作系统主动通知应用程序。也就是说,可以理解为读/写方法是异步的,完成后会主动调用回调函数。

以下四个异步通道主要是在Java.nio.channels包下添加的:

异步套接字通道

异步服务器套接字通道

异步文件通道

异步数据图形通道

在AIO套接字编程中,服务器通道是异步服务器套接字通道。这个类提供了一个开放的()静态工厂,一个绑定服务器IP地址(和端口号)的bind()方法,以及一个接收用户连接请求的accept()。客户端使用的通道是异步套接字通道,除了开放静态工厂方法之外,它还提供读写方法。

在AIO编程中,发出一个事件(接受读写等)后。),应该指定事件处理类(回调函数)。AIO的事件处理类是CompletionHandlerv,a .这个接口定义了以下两个方法,分别在异步操作成功或失败时调用。

作废完成(V结果,A附件);

作废失败(可抛exc,A附件);

3. AIO与NIO的实际区别

在JAVA NIO框架中,我们谈到了一个重要的概念“选择器”。它负责轮询操作系统中的IO事件,而不是应用程序查询中的所有注册通道,管理当前注册的通道集,并定位事件发生的通道。

然而,在JAVA AIO框架中,由于应用程序不是“轮询”模式,而是订阅通知模式,因此不再需要“选择器”,而是使用通道通道来注册和直接监听操作系统。异步IO采用“订阅-通知”模式:即应用程序向操作系统注册IO监听,然后继续做自己的事情。当操作系统发生IO事件,并且准备好数据后,在主动通知应用程序,触发相应的函数。,让AIO真正实现了异步阻断模式。(AIO取决于操作系统的实现)

和同步IO一样,异步IO也是操作系统支持的。微软的视窗系统提供了异步输入输出技术:IOCP(输入输出复合体

ionPort,I/O完成端口);
Linux下由于没有这种异步IO技术,所以使用的是epoll(类似于Selector的一种多路复用IO技术的实现)对异步IO进行模拟。

AIO与NIO的实际区别是什么

2. AIO中的API使用

AIO与NIO的实际区别是什么

    1. java.nio.channels.AsynchronousChannel:这是一个接口,用来标记一个channel支持异步IO操作。有主要的三个子类AsynchronousFileChannel、AsynchronousSocketChannel和AsynchronousServerSocketChannel,分别对应FileChannel、SocketChannel以及ServerSocketChannel。(很奇怪为什么没有AsynchronousDatagramChannel)

    2. AsynchronousChannelGroup:异步channel的分组管理,目的是为了资源共享。一个AsynchronousChannelGroup绑定一个线程池,这个线程池执行三个任务:等待IO事件、处理IO数据和派发CompletionHandler。AsynchronousServerSocketChannel创建的时候可以传入一个AsynchronousChannelGroup,那么通过AsynchronousServerSocketChannel创建的 AsynchronousSocketChannel将同属于一个组,共享资源,(可以理解为相当于Selector)。AsynchronousChannelGroup需要绑定线程池来创建,通过三个静态方法来创建,可以需要根据具体应用相应调整。

public abstract class AsynchronousChannelGroup {
    public static AsynchronousChannelGroup withFixedThreadPool(int nThreads, ThreadFactory threadFactory);
    public static AsynchronousChannelGroup withCachedThreadPool(ExecutorService executor,int initialSize);
    public static AsynchronousChannelGroup withThreadPool(ExecutorService executor);
}

    3. CompletionHandler:异步IO操作结果的回调接口,用于定义在IO操作完成后所作的回调工作。AIO的API允许两种方式来处理异步操作的结果,返回的Future模式或者注册CompletionHandler,常用CompletionHandler的方式,这些handler的调用是由AsynchronousChannelGroup的线程池派发的。显然,线程池的大小是性能的关键因素。

CompletionHandler接口有两个个方法,分别对应于处理成功、失败、被取消(通过返回的Future)情况下的回调处理:

public interface CompletionHandler<V,A> {  
  
    void completed(V result, A attachment);  
  
    void failed(Throwable exc, A attachment);  
}

    4. ByteBuffer:负责承载通信过程中需要读、写的消息。

AsynchronousServerSocketChannel

    使用方式主要为三步:打开通道、绑定监听端口、接收客户端连接请求。

    1. 打开(创建)通道

可以通过调用AsynchronousServerSocketChannel的静态方法open()来创建AsynchronousServerSocketChannel实例

        try {
            AsynchronousServerSocketChannel serverSocket = AsynchronousServerSocketChannel.open();
        }catch (IOException e) {
            e.printStackTrace();
        }

或者在open()方法传入AsynchronousChannelGroup参数,设置通道分组,以实现组内通道资源共享。如果通道打开失败,就会抛出IOException

        try {
            ExecutorService pool = Executors.newCachedThreadPool();
            AsynchronousChannelGroup group = AsynchronousChannelGroup.withCachedThreadPool(pool, 1024);
            AsynchronousServerSocketChannel serverSocket = AsynchronousServerSocketChannel.open(group);
        }catch (IOException e) {
            e.printStackTrace();
        }

AsynchronousChannelGroup封装了处理由绑定到组的异步通道所触发的I/O操作完成所需的机制。每个AsynchronousChannelGroup关联了一个被用于提交处理I/O事件和分发消费在组内通道上执行的异步操作结果的completion-handlers的线程池。除了处理I/O事件,该线程池还有可能处理其他一些用于支持完成异步I/O操作的任务。从上面例子可以看到,通过指定AsynchronousChannelGroup的方式打开AsynchronousServerSocketChannel,可以定制server channel执行的线程池。如果不指定AsynchronousChannelGroup,则AsynchronousServerSocketChannel会归类到一个默认的分组中。

    2. 绑定监听端口和地址

通过调用bind()方法来绑定要监听的端口。

try {
            ExecutorService pool = Executors.newCachedThreadPool();
            AsynchronousChannelGroup group = AsynchronousChannelGroup.withCachedThreadPool(pool, 1024);
            AsynchronousServerSocketChannel serverSocket = AsynchronousServerSocketChannel.open(group);
            int port = 8888;
            serverSocket.bind(new InetSocketAddress(port));
        }catch (IOException e) {
            e.printStackTrace();
        }

    3. 监听和接收客户端连接请求

监听客户端连接请求,主要通过调用accept()方法完成。accept()有两个重载方法:

public abstract <A> void accept(A,CompletionHandler<AsynchronousSocketChannel,? super A>);
public abstract Future<AsynchronousSocketChannel> accept();

这两个重载方法的行为方式完全相同,提供CompletionHandler回调参数或者返回一个Future<T>类型变量。

Future版本的accept方法通过Future接口可以调用Future.get()方法阻塞等待调用结果,返回一个AsynchronousSocketChannel对象。

        try {
            ExecutorService pool = Executors.newCachedThreadPool();
            AsynchronousChannelGroup group = AsynchronousChannelGroup.withCachedThreadPool(pool, 1024);
            AsynchronousServerSocketChannel serverSocket = AsynchronousServerSocketChannel.open(group);
            int port = 8888;
            serverSocket.bind(new InetSocketAddress(port));
            while(true) {
                Future<AsynchronousSocketChannel> accept = serverSocket.accept();
                AsynchronousSocketChannel socket = accept.get();//阻塞方法,获取AsynchronousSocketChannel
                //通过获取的Socket来进行网络IO操作
                //但一般不这样使用,因为这样就会导致变得和第一代NIO一样了,所以基本都是使用另一种CompletionHandler的重载方法
            }
        }catch (IOException e1) {
            e1.printStackTrace();
        }catch (InterruptedException e2) {
            e2.printStackTrace();
        } catch (ExecutionException e3) {
            e3.printStackTrace();
        }

而CompletionHandler回调参数版本则相反,真正的数据IO处理并不会放在当前线程中,而是通过一个回调方法处理,处理逻辑代码就写在CompletionHandler中的completed方法中,因为该方法会在AsynchronousServerSocketChannel成功接收到一个AsynchronousSocketChannel,回调执行,而如果AsynchronousServerSocketChannel接受AsynchronousSocketChannel失败,就会回调failed方法。

serverSocketChannel
.accept(serverSocketChannel, new CompletionHandler<AsynchronousSocketChannel,
        AsynchronousServerSocketChannel>() {
          @Override
          public void completed(final AsynchronousSocketChannel result,
                                final AsynchronousServerSocketChannel attachment) {
            // 接收到新的客户端连接,此时本次accept已经完成
            // 继续监听下一个客户端连接到来
            serverSocketChannel.accept(serverSocketChannel,this);
            // result即和该客户端的连接会话
            // 此时可以通过result与客户端进行交互
          }
          ...
        });

为什么会在completed方法中调用accept方法:因为当一个新的客户端建立连接之后,就会回调completed方法,一个AsynchronousServerSocketChannel会与多个客户端建立连接,此时就需要继续调用accept方法来接受更多的客户端连接。

    4. 设置TCP连接属性:通过一个AsynchronousServerSocketChannel建立的连接肯定是TCP连接了,所以通过该对象我们可以设置TCP连接的一些属性。

// 设置socket选项,比如设置保持TCP连接,也就是TCP长连接
serverSocketChannel.setOption(StandardSocketOptions.SO_KEEPALIVE,true);
// 获取socket选项设置
boolean keepAlive = serverSocketChannel.getOption(StandardSocketOptions.SO_KEEPALIVE);

获取本地IP地址

InetSocketAddress address = (InetSocketAddress) serverSocketChannel.getLocalAddress();

AsynchronousSocketChannel

    1. 创建连接

首先需要调用open方法创建一个AsynchronousSocketChannel对象,然后通过connect方法与服务端建立连接。connect方法也有两个重载版本

一个版本是返回Future对象,另一种是传入CompletionHandler参数对象

            AsynchronousSocketChannel socket = AsynchronousSocketChannel.open();
//            Future future = socket.connect(new InetSocketAddress("localhost",8888));
//            future.get();
            socket.connect(new InetSocketAddress("localhost", 8888),
                    socket, new CompletionHandler<Void, AsynchronousSocketChannel>() {
                        @Override
                        public void completed(Void result, AsynchronousSocketChannel attachment) {
                        }
                        @Override
                        public void failed(Throwable exc, AsynchronousSocketChannel attachment) {
                        }
                    });

    2. 写数据

    构建一个ByteBuffer对象并调用socketChannel.write(ByteBuffer)方法异步发送消息,并通过CompletionHandler回调接收处理发送结果:

ByteBuffer writeBuf = ByteBuffer.wrap("From socketChannel:Hello i am socketChannel".getBytes());
socketChannel.write(writeBuf, null, new CompletionHandler<Integer, Object>() {
  @Override
  public void completed(final Integer result, final Object attachment) {
    // 发送完成,result:总共写入的字节数
  }
  @Override
  public void failed(final Throwable exc, final Object attachment) {
    // 发送失败
  }
});

    3. 读数据

    构建一个指定接收长度的ByteBuffer用于接收数据,调用socketChannel.read()方法读取消息并通过CompletionHandler处理读取结果:

ByteBuffer readBuffer = ByteBuffer.allocate(128);
socketChannel.read(readBuffer, null, new CompletionHandler<Integer, Object>() {
  @Override
  public void completed(final Integer result, final Object attachment) {
    // 读取完成,result:实际读取的字节数。如果通道中没有数据可读则result=-1。
  }
  @Override
  public void failed(final Throwable exc, final Object attachment) {
    // 读取失败
  }
});

    4. 通过AsynchronousSocketChannel也可以设置设置/获取socket选项(TCP连接属性)

// 设置socket选项
socketChannel.setOption(StandardSocketOptions.SO_KEEPALIVE,true);
// 获取socket选项设置
boolean keepAlive = socketChannel.getOption(StandardSocketOptions.SO_KEEPALIVE);

CompletionHandler

    1. AIO中定义的异步通道允许指定一个CompletionHandler处理器消费一个异步操作的结果(也就是当准备好IO数据通道后,就回调CompletionHandler中的方法,使用IO数据通道进行IO处理,这也就导致了异步操作,不在等候IO通道的就绪,也不用将IO操作在当前线程中执行,而是采用回调的方式)。从上文中也可以看到,AIO中大部分的异步I/O操作接口都封装了一个带CompletionHandler类型参数的重载方法,使用CompletionHandler可以很方便地处理AIO中的异步I/O操作结果。CompletionHandler是一个具有两个泛型类型参数的接口,声明了两个接口方法:

public interface CompletionHandler<V,A> {
    void completed(V result, A attachment);
    void failed(Throwable exc, A attachment);
}

AIO以及NIO存在的问题

    NIO以及AIOU虽然实现了异步非阻塞网络IO操作,但是,其依旧具有一些缺点:

  1. 虽然JAVA NIO 和 JAVA AIO框架提供了多路复用IO/异步IO的支持,但是并没有提供上层“信息格式”的良好封装。例如前两者并没有提供针对 ProtocolBuffer、JSON这些信息格式的封装,但是Netty框架提供了这些数据格式封装(基于责任链模式的编码和解码功能)

  2. 要编写一个可靠的、易维护的、高性能的(注意它们的排序)NIO/AIO服务器应用。除了框架本身要兼容实现各类操作系统的实现外。更重要的是它应该还要处理很多上层特有服务,例如:客户端的权限、还有上面提到的信息格式封装、简单的数据读取。这些Netty框架都提供了响应的支持。

  3. JAVA NIO框架存在一个poll/epoll bug:Selector doesn’t block on Selector.select(timeout),不能block意味着CPU的使用率会变成100%(这是底层JNI的问题,上层要处理这个异常实际上也好办)。当然这个bug只有在Linux内核上才能重现。这个问题在JDK 1.7版本中还没有被完全解决:http://bugs.java.com/bugdatabase/view_bug.do?bug_id=2147719。虽然Netty 4.0中也是基于JAVA NIO框架进行封装的(上文中已经给出了Netty中NioServerSocketChannel类的介绍),但是Netty已经将这个bug进行了处理。

到此,相信大家对“AIO与NIO的实际区别是什么”有了更深的了解,不妨来实际操作一番吧!这里是网站,更多相关内容可以进入相关频道进行查询,关注我们,继续学习!

内容来源网络,如有侵权,联系删除,本文地址:https://www.230890.com/zhan/93204.html

(0)

相关推荐

  • idea 远程debug

    技术idea 远程debug idea 远程debug1.pom.xml里配置jvmArguments参数 -Xdebug -Xrunjdwp:transport=dt_socket,address=5

    礼包 2021年11月26日
  • 怎么理解C#中Queue的线程安全问题

    技术怎么理解C#中Queue的线程安全问题怎么理解C#中Queue的线程安全问题,很多新手对此不是很清楚,为了帮助大家解决这个难题,下面小编将为大家详细讲解,有这方面需求的人可以来学习下,希望你能有所收获。通过集合枚举在

    攻略 2021年11月11日
  • 能歌善舞近义词,手舞足蹈是近义词成语吗

    技术能歌善舞近义词,手舞足蹈是近义词成语吗手舞足蹈是近义词成语能歌善舞近义词。手舞足蹈   shǒu wǔ zú dǎo   【解释】蹈:顿足踏地。两手舞动,两只脚也跳了起来。形容高兴到了极点。也手乱舞、脚乱跳的狂态。 

    生活 2021年10月27日
  • 怎么分析Python网络爬虫四大选择器正则表达式、BS4、Xpath、CSS

    技术怎么分析Python网络爬虫四大选择器正则表达式、BS4、Xpath、CSS怎么分析Python网络爬虫四大选择器正则表达式、BS4、Xpath、CSS,相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因

    攻略 2021年12月4日
  • 哪个平台买粉丝可靠?抖音经济刷粉丝网站!

    技术哪个平台买粉丝可靠?抖音经济刷粉丝网站!哪个平台买粉丝可靠?抖音经济刷粉丝网站!玩抖音的用户那么多,但不是所有人都把抖音玩好了。就看抖音粉丝这个数据,有的人的抖音粉丝几十上百万,抖音人气高涨;有的人的粉丝几十上百,抖

    测评 2021年11月11日
  • react hooks组件间的传值方式是什么

    技术react hooks组件间的传值方式是什么这篇文章给大家介绍react hooks组件间的传值方式是什么,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。父传子通过props传值,使用useSta

    攻略 2021年11月12日