溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

AIO與NIO的實際區(qū)別是什么

發(fā)布時間:2021-11-15 11:14:44 來源:億速云 閱讀:547 作者:iii 欄目:大數(shù)據(jù)

本篇內(nèi)容主要講解“AIO與NIO的實際區(qū)別是什么”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實用性強。下面就讓小編來帶大家學(xué)習(xí)“AIO與NIO的實際區(qū)別是什么”吧!

1. NIO2.0——AIO

    1. 從某種程度上來說,NIO依然是同步阻塞的

雖然NIO中Channel(網(wǎng)絡(luò)Channel)和Buffer可以實現(xiàn)非阻塞的read/write操作,而且Selector提供了多路復(fù)用的功能,使得可以在一個線程中管理使用多個IO通道,避免了傳統(tǒng)IO的存在的問題。但是,NIO中在Selector進行調(diào)用select()方法進行通道選擇時,其依舊是同步阻塞的,而且由于多個Channel注冊于Selector上,這個方法會同時阻塞多個IO請求操作,盡管select()方法可以設(shè)置超時返回,但依舊是不利的。

換句話說,雖然NIO在網(wǎng)絡(luò)操作中,提供了非阻塞的read/write方法,但是NIO的IO行為還是同步的。對于NIO來說,我們的業(yè)務(wù)線程是在IO操作準(zhǔn)備好時(調(diào)用Selector中的select()方法),得到通知(select()方法返回,表示有準(zhǔn)備好的Channel),接著就由這個線程自行進行IO操作(通過Channel進行read/write操作),在第一代NIO中,每個線程可以持有多個IO通道并選擇使用,但實際上一個線程還是只能選擇操作一個IO,IO操作本身是同步的。

    2. NIO改進
為了真正實現(xiàn)異步非阻塞的IO操作,在NIO的基礎(chǔ)上進行改進,升級為2代NIO——即AIO機制。

AIO相比于NIO,則更加進了一步,它不是在IO準(zhǔn)備好時再通知線程,而是在IO操作已經(jīng)完成后,再給線程發(fā)出通知。因此AIO是不會阻塞的,此時我們的業(yè)務(wù)邏輯將變成一個回調(diào)函數(shù),等待IO操作完成后,由系統(tǒng)自動觸發(fā)。也就是相當(dāng)于

在AIO中,當(dāng)進行讀寫操作時,只須直接調(diào)用API的read或write方法即可。這兩種方法均為異步的,對于讀操作而言,當(dāng)有流可讀取時,操作系統(tǒng)會將可讀的流傳入read方法的緩沖區(qū),并通知應(yīng)用程序;對于寫操作而言,當(dāng)操作系統(tǒng)將write方法傳遞的流寫入完畢時,操作系統(tǒng)主動通知應(yīng)用程序。 即可以理解為,read/write方法都是異步的,完成后會主動調(diào)用回調(diào)函數(shù)。

主要在Java.nio.channels包下增加了下面四個異步通道:

  • AsynchronousSocketChannel

  • AsynchronousServerSocketChannel

  • AsynchronousFileChannel

  • AsynchronousDatagramChannel

在AIO socket編程中,服務(wù)端通道是AsynchronousServerSocketChannel,這個類提供了一個open()靜態(tài)工廠,一個bind()方法用于綁定服務(wù)端IP地址(還有端口號),另外還提供了accept()用于接收用戶連接請求。在客戶端使用的通道是AsynchronousSocketChannel,這個通道處理提供open靜態(tài)工廠方法外,還提供了read和write方法。

在AIO編程中,發(fā)出一個事件(accept read write等)之后要指定事件處理類(回調(diào)函數(shù)),AIO中的事件處理類是CompletionHandler<V,A>,這個接口定義了如下兩個方法,分別在異步操作成功和失敗時被回調(diào)。

void completed(V result, A attachment);

void failed(Throwable exc, A attachment);

    3. AIO與NIO的實際區(qū)別

    在JAVA NIO框架中,我們說到了一個重要概念“selector”(選擇器)。它負(fù)責(zé)代替應(yīng)用查詢中所有已注冊的通道到操作系統(tǒng)中進行IO事件輪詢、管理當(dāng)前注冊的通道集合,定位發(fā)生事件的通道等操操作。

    但是在JAVA AIO框架中,由于應(yīng)用程序不是“輪詢”方式,而是訂閱-通知方式,所以不再需要“selector”(選擇器)了,改由channel通道直接到操作系統(tǒng)注冊監(jiān)聽。異步IO則采用“訂閱-通知”模式:即應(yīng)用程序向操作系統(tǒng)注冊IO監(jiān)聽,然后繼續(xù)做自己的事情。當(dāng)操作系統(tǒng)發(fā)生IO事件,并且準(zhǔn)備好數(shù)據(jù)后,在主動通知應(yīng)用程序,觸發(fā)相應(yīng)的函數(shù)。這就使得AIO真正意義上實現(xiàn)了異步阻塞模式。(AIO是依賴于操作系統(tǒng)的實現(xiàn)的)

和同步IO一樣,異步IO也是由操作系統(tǒng)進行支持的。微軟的windows系統(tǒng)提供了一種異步IO技術(shù):IOCP(I/O CompletionPort,I/O完成端口);
Linux下由于沒有這種異步IO技術(shù),所以使用的是epoll(類似于Selector的一種多路復(fù)用IO技術(shù)的實現(xiàn))對異步IO進行模擬。

AIO與NIO的實際區(qū)別是什么

2. AIO中的API使用

AIO與NIO的實際區(qū)別是什么

    1. java.nio.channels.AsynchronousChannel:這是一個接口,用來標(biāo)記一個channel支持異步IO操作。有主要的三個子類AsynchronousFileChannel、AsynchronousSocketChannel和AsynchronousServerSocketChannel,分別對應(yīng)FileChannel、SocketChannel以及ServerSocketChannel。(很奇怪為什么沒有AsynchronousDatagramChannel)

    2. AsynchronousChannelGroup:異步channel的分組管理,目的是為了資源共享。一個AsynchronousChannelGroup綁定一個線程池,這個線程池執(zhí)行三個任務(wù):等待IO事件、處理IO數(shù)據(jù)和派發(fā)CompletionHandler。AsynchronousServerSocketChannel創(chuàng)建的時候可以傳入一個AsynchronousChannelGroup,那么通過AsynchronousServerSocketChannel創(chuàng)建的 AsynchronousSocketChannel將同屬于一個組,共享資源,(可以理解為相當(dāng)于Selector)。AsynchronousChannelGroup需要綁定線程池來創(chuàng)建,通過三個靜態(tài)方法來創(chuàng)建,可以需要根據(jù)具體應(yīng)用相應(yīng)調(diào)整。

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操作結(jié)果的回調(diào)接口,用于定義在IO操作完成后所作的回調(diào)工作。AIO的API允許兩種方式來處理異步操作的結(jié)果,返回的Future模式或者注冊CompletionHandler,常用CompletionHandler的方式,這些handler的調(diào)用是由AsynchronousChannelGroup的線程池派發(fā)的。顯然,線程池的大小是性能的關(guān)鍵因素。

CompletionHandler接口有兩個個方法,分別對應(yīng)于處理成功、失敗、被取消(通過返回的Future)情況下的回調(diào)處理:

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

    4. ByteBuffer:負(fù)責(zé)承載通信過程中需要讀、寫的消息。

AsynchronousServerSocketChannel

    使用方式主要為三步:打開通道、綁定監(jiān)聽端口、接收客戶端連接請求。

    1. 打開(創(chuàng)建)通道

可以通過調(diào)用AsynchronousServerSocketChannel的靜態(tài)方法open()來創(chuàng)建AsynchronousServerSocketChannel實例

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

或者在open()方法傳入AsynchronousChannelGroup參數(shù),設(shè)置通道分組,以實現(xiàn)組內(nèi)通道資源共享。如果通道打開失敗,就會拋出IOException

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

AsynchronousChannelGroup封裝了處理由綁定到組的異步通道所觸發(fā)的I/O操作完成所需的機制。每個AsynchronousChannelGroup關(guān)聯(lián)了一個被用于提交處理I/O事件和分發(fā)消費在組內(nèi)通道上執(zhí)行的異步操作結(jié)果的completion-handlers的線程池。除了處理I/O事件,該線程池還有可能處理其他一些用于支持完成異步I/O操作的任務(wù)。從上面例子可以看到,通過指定AsynchronousChannelGroup的方式打開AsynchronousServerSocketChannel,可以定制server channel執(zhí)行的線程池。如果不指定AsynchronousChannelGroup,則AsynchronousServerSocketChannel會歸類到一個默認(rèn)的分組中。

    2. 綁定監(jiān)聽端口和地址

通過調(diào)用bind()方法來綁定要監(jiān)聽的端口。

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. 監(jiān)聽和接收客戶端連接請求

監(jiān)聽客戶端連接請求,主要通過調(diào)用accept()方法完成。accept()有兩個重載方法:

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

這兩個重載方法的行為方式完全相同,提供CompletionHandler回調(diào)參數(shù)或者返回一個Future<T>類型變量。

Future版本的accept方法通過Future接口可以調(diào)用Future.get()方法阻塞等待調(diào)用結(jié)果,返回一個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來進行網(wǎng)絡(luò)IO操作
                //但一般不這樣使用,因為這樣就會導(dǎo)致變得和第一代NIO一樣了,所以基本都是使用另一種CompletionHandler的重載方法
            }


        }catch (IOException e1) {
            e1.printStackTrace();
        }catch (InterruptedException e2) {
            e2.printStackTrace();
        } catch (ExecutionException e3) {
            e3.printStackTrace();
        }

而CompletionHandler回調(diào)參數(shù)版本則相反,真正的數(shù)據(jù)IO處理并不會放在當(dāng)前線程中,而是通過一個回調(diào)方法處理,處理邏輯代碼就寫在CompletionHandler中的completed方法中,因為該方法會在AsynchronousServerSocketChannel成功接收到一個AsynchronousSocketChannel,回調(diào)執(zhí)行,而如果AsynchronousServerSocketChannel接受AsynchronousSocketChannel失敗,就會回調(diào)failed方法。

serverSocketChannel
.accept(serverSocketChannel, new CompletionHandler<AsynchronousSocketChannel,
        AsynchronousServerSocketChannel>() {
          @Override
          public void completed(final AsynchronousSocketChannel result,
                                final AsynchronousServerSocketChannel attachment) {
            // 接收到新的客戶端連接,此時本次accept已經(jīng)完成
            // 繼續(xù)監(jiān)聽下一個客戶端連接到來
            serverSocketChannel.accept(serverSocketChannel,this);
            // result即和該客戶端的連接會話
            // 此時可以通過result與客戶端進行交互
          }
          ...
        });

為什么會在completed方法中調(diào)用accept方法:因為當(dāng)一個新的客戶端建立連接之后,就會回調(diào)completed方法,一個AsynchronousServerSocketChannel會與多個客戶端建立連接,此時就需要繼續(xù)調(diào)用accept方法來接受更多的客戶端連接。

    4. 設(shè)置TCP連接屬性:通過一個AsynchronousServerSocketChannel建立的連接肯定是TCP連接了,所以通過該對象我們可以設(shè)置TCP連接的一些屬性。

// 設(shè)置socket選項,比如設(shè)置保持TCP連接,也就是TCP長連接
serverSocketChannel.setOption(StandardSocketOptions.SO_KEEPALIVE,true);
// 獲取socket選項設(shè)置
boolean keepAlive = serverSocketChannel.getOption(StandardSocketOptions.SO_KEEPALIVE);

獲取本地IP地址

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

AsynchronousSocketChannel

    1. 創(chuàng)建連接

首先需要調(diào)用open方法創(chuàng)建一個AsynchronousSocketChannel對象,然后通過connect方法與服務(wù)端建立連接。connect方法也有兩個重載版本

一個版本是返回Future對象,另一種是傳入CompletionHandler參數(shù)對象

            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. 寫數(shù)據(jù)

    構(gòu)建一個ByteBuffer對象并調(diào)用socketChannel.write(ByteBuffer)方法異步發(fā)送消息,并通過CompletionHandler回調(diào)接收處理發(fā)送結(jié)果:

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) {
    // 發(fā)送完成,result:總共寫入的字節(jié)數(shù)
  }

  @Override
  public void failed(final Throwable exc, final Object attachment) {
    // 發(fā)送失敗
  }
});

    3. 讀數(shù)據(jù)

    構(gòu)建一個指定接收長度的ByteBuffer用于接收數(shù)據(jù),調(diào)用socketChannel.read()方法讀取消息并通過CompletionHandler處理讀取結(jié)果:

ByteBuffer readBuffer = ByteBuffer.allocate(128);
socketChannel.read(readBuffer, null, new CompletionHandler<Integer, Object>() {
  @Override
  public void completed(final Integer result, final Object attachment) {
    // 讀取完成,result:實際讀取的字節(jié)數(shù)。如果通道中沒有數(shù)據(jù)可讀則result=-1。
  }

  @Override
  public void failed(final Throwable exc, final Object attachment) {
    // 讀取失敗
  }
});

    4. 通過AsynchronousSocketChannel也可以設(shè)置設(shè)置/獲取socket選項(TCP連接屬性)

// 設(shè)置socket選項
socketChannel.setOption(StandardSocketOptions.SO_KEEPALIVE,true);
// 獲取socket選項設(shè)置
boolean keepAlive = socketChannel.getOption(StandardSocketOptions.SO_KEEPALIVE);

CompletionHandler

    1. AIO中定義的異步通道允許指定一個CompletionHandler處理器消費一個異步操作的結(jié)果(也就是當(dāng)準(zhǔn)備好IO數(shù)據(jù)通道后,就回調(diào)CompletionHandler中的方法,使用IO數(shù)據(jù)通道進行IO處理,這也就導(dǎo)致了異步操作,不在等候IO通道的就緒,也不用將IO操作在當(dāng)前線程中執(zhí)行,而是采用回調(diào)的方式)。從上文中也可以看到,AIO中大部分的異步I/O操作接口都封裝了一個帶CompletionHandler類型參數(shù)的重載方法,使用CompletionHandler可以很方便地處理AIO中的異步I/O操作結(jié)果。CompletionHandler是一個具有兩個泛型類型參數(shù)的接口,聲明了兩個接口方法:

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

AIO以及NIO存在的問題

    NIO以及AIOU雖然實現(xiàn)了異步非阻塞網(wǎng)絡(luò)IO操作,但是,其依舊具有一些缺點:

  1. 雖然JAVA NIO 和 JAVA AIO框架提供了多路復(fù)用IO/異步IO的支持,但是并沒有提供上層“信息格式”的良好封裝。例如前兩者并沒有提供針對 ProtocolBuffer、JSON這些信息格式的封裝,但是Netty框架提供了這些數(shù)據(jù)格式封裝(基于責(zé)任鏈模式的編碼和解碼功能)

  2. 要編寫一個可靠的、易維護的、高性能的(注意它們的排序)NIO/AIO服務(wù)器應(yīng)用。除了框架本身要兼容實現(xiàn)各類操作系統(tǒng)的實現(xiàn)外。更重要的是它應(yīng)該還要處理很多上層特有服務(wù),例如:客戶端的權(quán)限、還有上面提到的信息格式封裝、簡單的數(shù)據(jù)讀取。這些Netty框架都提供了響應(yīng)的支持。

  3. JAVA NIO框架存在一個poll/epoll bug:Selector doesn’t block on Selector.select(timeout),不能block意味著CPU的使用率會變成100%(這是底層JNI的問題,上層要處理這個異常實際上也好辦)。當(dāng)然這個bug只有在Linux內(nèi)核上才能重現(xiàn)。這個問題在JDK 1.7版本中還沒有被完全解決:http://bugs.java.com/bugdatabase/view_bug.do?bug_id=2147719。雖然Netty 4.0中也是基于JAVA NIO框架進行封裝的(上文中已經(jīng)給出了Netty中NioServerSocketChannel類的介紹),但是Netty已經(jīng)將這個bug進行了處理。

到此,相信大家對“AIO與NIO的實際區(qū)別是什么”有了更深的了解,不妨來實際操作一番吧!這里是億速云網(wǎng)站,更多相關(guān)內(nèi)容可以進入相關(guān)頻道進行查詢,關(guān)注我們,繼續(xù)學(xué)習(xí)!

向AI問一下細節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI