關(guān)于BIO | NIO | AIO的討論一直存在,有時(shí)候也很容易讓人混淆,就我的理解,給出一個(gè)解釋:
BIO | NIO | AIO,本身的描述都是在Java語(yǔ)言的基礎(chǔ)上的。而描述IO,我們需要從兩個(gè)層面:
BIO | NIO | AIO 以Java的角度,理解,linux c里也有AIO的概念(庫(kù)),這些概念不知道什么原因被炒火起來(lái),這里只從Java角度入手。
在JDK1.4之前,用Java編寫(xiě)網(wǎng)絡(luò)請(qǐng)求,都是建立一個(gè)ServerSocket,然后,客戶端建立Socket時(shí)就會(huì)詢問(wèn)是否有線程可以處理,如果沒(méi)有,要么等待,要么被拒絕。即:一個(gè)連接,要求Server對(duì)應(yīng)一個(gè)處理線程。
在Java里的由來(lái),在JDK1.4及以后版本中提供了一套API來(lái)專門操作非阻塞I/O,我們可以在java.nio包及其子包中找到相關(guān)的類和接口。由于這套API是JDK新提供的I/O API,因此,也叫New I/O,這就是包名nio的由來(lái)。這套API由三個(gè)主要的部分組成:緩沖區(qū)(Buffers)、通道(Channels)和非阻塞I/O的核心類組成。在理解NIO的時(shí)候,需要區(qū)分,說(shuō)的是New I/O還是非阻塞IO,New I/O是Java的包,NIO是非阻塞IO概念。這里講的是后面一種。
NIO本身是基于事件驅(qū)動(dòng)思想來(lái)完成的,其主要想解決的是BIO的大并發(fā)問(wèn)題: 在使用同步I/O的網(wǎng)絡(luò)應(yīng)用中,如果要同時(shí)處理多個(gè)客戶端請(qǐng)求,或是在客戶端要同時(shí)和多個(gè)服務(wù)器進(jìn)行通訊,就必須使用多線程來(lái)處理。也就是說(shuō),將每一個(gè)客戶端請(qǐng)求分配給一個(gè)線程來(lái)單獨(dú)處理。這樣做雖然可以達(dá)到我們的要求,但同時(shí)又會(huì)帶來(lái)另外一個(gè)問(wèn)題。由于每創(chuàng)建一個(gè)線程,就要為這個(gè)線程分配一定的內(nèi)存空間(也叫工作存儲(chǔ)器),而且操作系統(tǒng)本身也對(duì)線程的總數(shù)有一定的限制。如果客戶端的請(qǐng)求過(guò)多,服務(wù)端程序可能會(huì)因?yàn)椴豢爸刎?fù)而拒絕客戶端的請(qǐng)求,甚至服務(wù)器可能會(huì)因此而癱瘓。
NIO基于Reactor,當(dāng)socket有流可讀或可寫(xiě)入socket時(shí),操作系統(tǒng)會(huì)相應(yīng)的通知引用程序進(jìn)行處理,應(yīng)用再將流讀取到緩沖區(qū)或?qū)懭氩僮飨到y(tǒng)。
也就是說(shuō),這個(gè)時(shí)候,已經(jīng)不是一個(gè)連接就要對(duì)應(yīng)一個(gè)處理線程了,而是有效的請(qǐng)求,對(duì)應(yīng)一個(gè)線程,當(dāng)連接沒(méi)有數(shù)據(jù)時(shí),是沒(méi)有工作線程來(lái)處理的。
與NIO不同,當(dāng)進(jìn)行讀寫(xiě)操作時(shí),只須直接調(diào)用API的read或write方法即可。這兩種方法均為異步的,對(duì)于讀操作而言,當(dāng)有流可讀取時(shí),操作系統(tǒng)會(huì)將可讀的流傳入read方法的緩沖區(qū),并通知應(yīng)用程序;對(duì)于寫(xiě)操作而言,當(dāng)操作系統(tǒng)將write方法傳遞的流寫(xiě)入完畢時(shí),操作系統(tǒng)主動(dòng)通知應(yīng)用程序。
即可以理解為,read/write方法都是異步的,完成后會(huì)主動(dòng)調(diào)用回調(diào)函數(shù)。
在JDK1.7中,這部分內(nèi)容被稱作NIO.2,主要在java.nio.channels包下增加了下面四個(gè)異步通道:
其中的read/write方法,會(huì)返回一個(gè)帶回調(diào)函數(shù)的對(duì)象,當(dāng)執(zhí)行完讀取/寫(xiě)入操作后,直接調(diào)用回調(diào)函數(shù)。
說(shuō)道實(shí)現(xiàn)原理,還要從操作系統(tǒng)的IO模型上了解
按照《Unix網(wǎng)絡(luò)編程》的劃分,IO模型可以分為:阻塞IO、非阻塞IO、IO復(fù)用、信號(hào)驅(qū)動(dòng)IO和異步IO,按照POSIX標(biāo)準(zhǔn)來(lái)劃分只分為兩類:同步IO和異步IO。如何區(qū)分呢?首先一個(gè)IO操作其實(shí)分成了兩個(gè)步驟:發(fā)起IO請(qǐng)求和實(shí)際的IO操作,同步IO和異步IO的區(qū)別就在于第二個(gè)步驟是否阻塞,如果實(shí)際的IO讀寫(xiě)阻塞請(qǐng)求進(jìn)程,那么就是同步IO,因此阻塞IO、非阻塞IO、IO復(fù)用、信號(hào)驅(qū)動(dòng)IO都是同步IO,如果不阻塞,而是操作系統(tǒng)幫你做完IO操作再將結(jié)果返回給你,那么就是異步IO。阻塞IO和非阻塞IO的區(qū)別在于第一步,發(fā)起IO請(qǐng)求是否會(huì)被阻塞,如果阻塞直到完成那么就是傳統(tǒng)的阻塞IO,如果不阻塞,那么就是非阻塞IO。
收到操作系統(tǒng)的IO模型,又不得不提select/poll/epoll/iocp,關(guān)于這四個(gè)的理解,不多做解釋,自己還沒(méi)理解到位。
可以理解的說(shuō)明是:在Linux 2.6以后,java NIO的實(shí)現(xiàn),是通過(guò)epoll來(lái)實(shí)現(xiàn)的,這點(diǎn)可以通過(guò)jdk的源代碼發(fā)現(xiàn)。而AIO,在windows上是通過(guò)IOCP實(shí)現(xiàn)的,在linux上還是通過(guò)epoll來(lái)實(shí)現(xiàn)的。
這里強(qiáng)調(diào)一點(diǎn):AIO,這是I/O處理模式,而epoll等都是實(shí)現(xiàn)AIO的一種編程模型;換句話說(shuō),AIO是一種接口標(biāo)準(zhǔn),各家操作系統(tǒng)可以實(shí)現(xiàn)也可以不實(shí)現(xiàn)。在不同操作系統(tǒng)上在高并發(fā)情況下最好都采用操作系統(tǒng)推薦的方式。Linux上還沒(méi)有真正實(shí)現(xiàn)網(wǎng)絡(luò)方式的AIO。
說(shuō)到底層,要說(shuō)Linux系統(tǒng)編程,這里自己也不熟悉,有待后來(lái)人補(bǔ)充了。
只籠統(tǒng)的說(shuō)一個(gè):AIO實(shí)現(xiàn)
在windows上,AIO的實(shí)現(xiàn)是通過(guò)IOCP來(lái)完成的,看JDK的源代碼,可以發(fā)現(xiàn)
WindowsAsynchronousSocketChannelImpl
看實(shí)現(xiàn)接口:
implements Iocp.OverlappedChannel
再看實(shí)現(xiàn)方法:里面的read0/write0方法是native方法,調(diào)用的jvm底層實(shí)現(xiàn),虛擬機(jī)技術(shù)不熟悉,不獻(xiàn)丑了。
在linux上,AIO的實(shí)現(xiàn)是通過(guò)epoll來(lái)完成的,看JDK源碼,可以發(fā)現(xiàn),實(shí)現(xiàn)源碼是:
UnixAsynchronousSocketChannelImpl
看實(shí)現(xiàn)接口:
implements Port.PollableChannel
這是與windows最大的區(qū)別,poll的實(shí)現(xiàn),在linux2.6后,默認(rèn)使用epoll。
這樣就可以理解了。
寫(xiě)在最后:Java開(kāi)發(fā)為基礎(chǔ)的,對(duì)于操作系統(tǒng)底層的認(rèn)知是沒(méi)有C語(yǔ)言為基礎(chǔ)的大牛好的,語(yǔ)言決定了思維方式,古人誠(chéng)不欺我
最后,幾篇解釋的不錯(cuò)的文章:
聯(lián)系客服