1. 程式人生 > >[紙上談兵]Java IO詳解(一)基礎

[紙上談兵]Java IO詳解(一)基礎

宣告:這個文章只是從其他文章抄過來加上自己的理解組成的文章。可能會存在各種問題,還請指正.

關於IO這塊,對於我來講一直是個知識肓區,尤其在同步、非同步、阻塞、非阻塞這塊,有一點認識,但很不清晰,所以寫這篇文章,它不能讓我100%理解,但我希望以後可以在這個文章基礎上繼續加深對io的理解.

一、作業系統相關基礎知識
學習JAVA IO前,我們先需要了解一下作業系統相關概念.因為常用為Linux作業系統,所以這裡的概念或說明以Linux為主.
1.檔案描述符(File descriptor 簡寫 fd)
Linux的核心將所有外部裝置都可以看做一個檔案來操作。那麼我們對與外部裝置的操作都可以看做對檔案進行操作。我們對一個檔案的讀寫,都通過呼叫核心提供的系統呼叫;核心給我們返回一個file descriptor(fd,檔案描述符)。對一個socket的讀寫也會有相應的描述符,稱為socketfd(socket描述符)。描述符就是一個數字(可以理解為一個索引),指向核心中一個結構體(檔案路徑,資料區,等一些屬性)。應用程式對檔案的讀寫就通過對描述符的讀寫完成。

2.使用者空間與核心空間
我個人簡單理解的是使用者程式不能直接和硬體打交道(為了安全和方便),所以有了一層核心空間.使用者程式執行的使用者空間。這個僅僅方便理解,一定是極期不準確的.

3.快取 I/O

快取 I/O 又被稱作標準 I/O,大多數檔案系統的預設 I/O 操作都是快取 I/O。在 Linux 的快取 I/O 機制中,作業系統會將 I/O 的資料快取在檔案系統的頁快取( page cache )中,也就是說,資料會先被拷貝到作業系統核心的緩衝區中,然後才會從作業系統核心的緩衝區拷貝到應用程式的地址空間.

個人理解:快取I/O大家一看到快取兩個字,不要以為僅僅用於讀。其實寫也有快取(緩衝區)

 

二、IO 主要的階段

 

以read為例讀取資料主要由兩個階段(這個流程必須要牢牢記住,這個樣子下面才可以講,寫流程與read相似)

1. 核心準備資料(即下圖中的2、3)。即使用者空間(即我們自己的應用)呼叫核心的方法讀取資料,核心就會從硬碟上讀取資料,存放到核心的緩衝區。這個階段即核心準備階段
2. 將資料從核心拷貝到使用者空間(即下圖中的4)。核心從硬碟上讀取資料後,並非直接反回給應用程式,


三、Linux IO模式
主要有5種IO 模式,分為兩大類同步IO和非同步IO(注意,這裡是按同步和非同步分的,沒有按阻塞非阻塞區分)
同步IO模式包括
- 阻塞 I/O(blocking IO)
- 非阻塞 I/O(nonblocking IO)
- I/O 多路複用( IO multiplexing)
- 訊號驅動 I/O( signal driven IO)

非同步IO模式包括

 

-  非同步 I/O(asynchronous IO)

 

重複一次:阻塞IO、非阻塞IO、IO複用、訊號驅動IO都是同步I/O模型(看完下面的詳解,你會更清楚的)

下面詳細講解這5種IO模式(參考:https://www.cnblogs.com/diegodu/p/6823855.html   這個介紹的不錯,我就全面抄過來了)

1. 阻塞 I/O

阻塞I/O(blocking I/O)模型,程序呼叫recvfrom,其系統呼叫直到資料報到達且被拷貝到應用程序的緩衝區中或者發生錯誤才返回。程序從呼叫recvfrom開始到它返回的整段時間內是被阻塞的。(圖和文字多看幾次)

2. 非阻塞 I/O

當一個應用程序像這樣對一個非阻塞描述字迴圈呼叫recvfrom時,我們稱之為輪詢(polling)。應用程序持續輪詢核心,以檢視某個操作是否就緒。

個人理解:這裡我要說一下,我個人理解這個非阻塞 I/O,並非真正的非阻塞,前面已經講了,IO共分為兩個階段:1.核心準備資料階段。2.將資料從核心拷到使用者空間。  非阻塞IO在第二階段時仍然還是阻塞的,但第二階段我們我們一般不說是阻塞和非阻塞,而說同步非同步。這裡就可以很明顯的瞭解到。阻塞與非阻塞是用來描述第一階段(因為這一步是真正的讀取資料。即將資料從硬碟拷到作業系統的核心中)。同步與非同步是用來描述第二階段的

3. I/O 多路複用

個人理解:我之前僅僅看到這個圖時,總會產生疑惑,產生疑惑在於等待資料階段。這麼看也是阻塞的,和阻塞I/O看起來沒有區別。大家仔細理解一下我摘抄的下面一句話

   當用戶程序呼叫了select,那麼整個程序會被block,而同時,kernel會“監視”所有select負責的socket,當任何一個socket中的資料準備好了,select就會返回。這個時候使用者程序再呼叫read操作,將資料從kernel拷貝到使用者程序。在IO multiplexing Model中,實際中,對於每一個socket,一般都設定成為non-blocking,但是,如下面的圖所示,整個使用者的process其實是一直被block的。只不過process是被select這個函式block,而不是被socket IO給block。

   不知道大家是如何理解上面這句話的,我給大家解讀一下我的個人理解。首先一個select 對應多個socket(這裡針對的是網路),每個socket會設定成非阻塞的。select底層方法類似迴圈不停的遍歷每個socket是否有資料。select方法內部不停的迴圈遍歷每個socket是否有資料,這個是阻塞的,即select是阻塞的,至到socket有資料。這段至關重要,我之前不太理解,就是一直沒有理解明白這裡。也就是說下面這個圖其實是沒有很好的表達一個select對應多個socket情況下,他只表達了一個select 對應一個socket情況。

4. 訊號驅動 I/O

訊號驅動IO這個我沒有仔細去了解,因為看到說使用較少。但從下面這個圖我們可以瞭解到他的確是同步的,原因看第二階段,第二階段是同步的。

5. 非同步 I/O

從下面看,我們可以看到非同步I/O其實是非同步非阻塞的。因為第一階段是馬上返回的,並沒有阻塞。

 

I/O模型比較

 

根據上述5種IO模型,前4種模型-阻塞IO、非阻塞IO、IO複用、訊號驅動IO都是同步I/O模型,因為其中真正的I/O操作(recvfrom)將阻塞程序,在核心資料copy到使用者空間時都是阻塞的。這段話相關內其實在這個文章中出現過了三次了,要注意這個很重要了。其實反過來我們按第一階段(阻塞非阻塞)第二階段(非同步非非同步)來整理記IO模式其實也是可以的。

至此,你應該理解對於Linux 5種IO中對應是阻塞還是非阻塞,是同步還是非同步。如果你還不明白,再看一次或看一下我後面的那些參考文章或留言溝通。我有腦子不太好用,又不想去看作業系統相關的書(主要無法快速找到我想要的內容),所以我翻閱了大量的網上文章

 

四、IO模式-多路複用詳解(全是拷的)

為什麼這裡要重點說一下多路複用的IO模型,很簡單,因為JavaNIO底層就是這個模型,NIO是目前應用最多,效能還不錯的,看起來沒有AIO那麼複雜。

定義: IO多路複用,就是通過一種機制,一個程序可以監視多個描述符,一旦某個描述符就緒(一般是讀就緒或者寫就緒),能夠通知程式進行相應的讀寫操作.

Linux支援IO多路複用的系統呼叫有select、poll、epoll,這些呼叫都是核心級別的。但select、poll、epoll本質上都是同步I/O,先是block住等待就緒的socket,再是block住將資料從核心拷貝到使用者記憶體。

之所以本質上是同步IO原因:因為他們都需要在讀寫事件就緒後自己負責進行讀寫,也就是說這個讀寫過程是阻塞的,而非同步I/O則無需自己負責進行讀寫,非同步I/O的實現會負責把資料從核心拷貝到使用者空間。 -- 其實還是我前面提的,在第二階段是要進行等待的,這個等待我們定義為同步

大家要理解:linux多路複用在核心級別有三種實現方式select或poll或epoll.

epoll的效率更高,優化了select的輪詢操作,通過callback事件響應方式。 

poll跟select都能提供多路I/O複用的解決方案。在現在的Linux核心裡有都能夠支援,其中epoll是Linux所特有,而select則應該是POSIX所規定,一般作業系統均有實現(POSIX標準是一個針對作業系統(準確地說是針對類Unix作業系統)的標準化協議)

select:
select本質上是通過設定或者檢查存放fd標誌位的資料結構來進行下一步處理。
這樣所帶來的缺點是:
1、 單個程序可監視的fd數量被限制,即能監聽埠的大小有限。
一般來說這個數目和系統記憶體關係很大,具體數目可以cat /proc/sys/fs/file-max察看。32位機預設是1024個。64位機預設是2048.

2、 對socket進行掃描時是線性掃描,即採用輪詢的方法,效率較低:
當套接字比較多的時候,每次select()都要通過遍歷FD_SETSIZE個Socket來完成排程,不管哪個Socket是活躍的,都遍歷一遍。這會浪費很多CPU時間。如果能給套接字註冊某個回撥函式,當他們活躍時,自動完成相關操作,那就避免了輪詢,這正是epoll與kqueue做的。

3、需要維護一個用來存放大量fd的資料結構,這樣會使得使用者空間和核心空間在傳遞該結構時複製開銷大

poll:
poll本質上和select沒有區別,它將使用者傳入的陣列拷貝到核心空間,然後查詢每個fd對應的裝置狀態,如果裝置就緒則在裝置等待佇列中加入一項並繼續遍歷,如果遍歷完所有fd後沒有發現就緒裝置,則掛起當前程序,直到裝置就緒或者主動超時,被喚醒後它又要再次遍歷fd。這個過程經歷了多次無謂的遍歷。

它沒有最大連線數的限制,原因是它是基於連結串列來儲存的,但是同樣有一個缺點:
1、大量的fd的陣列被整體複製於使用者態和核心地址空間之間,而不管這樣的複製是不是有意義。                   
2、poll還有一個特點是“水平觸發”,如果報告了fd後,沒有被處理,那麼下次poll時會再次報告該fd。

epoll:
epoll有EPOLLLT和EPOLLET兩種觸發模式,LT是預設的模式,ET是“高速”模式。LT模式下,只要這個fd(fd是檔案描述符)還有資料可讀,每次 epoll_wait都會返回它的事件,提醒使用者程式去操作,而在ET(邊緣觸發)模式中,它只會提示一次,直到下次再有資料流入之前都不會再提示了,無論fd中是否還有資料可讀。所以在ET模式下,read一個fd的時候一定要把它的buffer讀光,也就是說一直讀到read的返回值小於請求值,或者 遇到EAGAIN錯誤。還有一個特點是,epoll使用“事件”的就緒通知方式,通過epoll_ctl註冊fd,一旦該fd就緒,核心就會採用類似callback的回撥機制來啟用該fd,epoll_wait便可以收到通知。

epoll為什麼要有EPOLLET觸發模式?
如果採用EPOLLLT模式的話,系統中一旦有大量你不需要讀寫的就緒檔案描述符,它們每次呼叫epoll_wait都會返回,這樣會大大降低處理程式檢索自己關心的就緒檔案描述符的效率.。而採用EPOLLET這種邊沿觸發模式的話,當被監控的檔案描述符上有可讀寫事件發生時,epoll_wait()會通知處理程式去讀寫。如果這次沒有把資料全部讀寫完(如讀寫緩衝區太小),那麼下次呼叫epoll_wait()時,它不會通知你,也就是它只會通知你一次,直到該檔案描述符上出現第二次可讀寫事件才會通知你!!!這種模式比水平觸發效率高,系統不會充斥大量你不關心的就緒檔案描述符

epoll的優點:
1、沒有最大併發連線的限制,能開啟的FD的上限遠大於1024(1G的記憶體上能監聽約10萬個埠);
2、效率提升,不是輪詢的方式,不會隨著FD數目的增加效率下降。只有活躍可用的FD才會呼叫callback函式;
即Epoll最大的優點就在於它只管你“活躍”的連線,而跟連線總數無關,因此在實際的網路環境中,Epoll的效率就會遠遠高於select和poll。
3、 記憶體拷貝,利用mmap()檔案對映記憶體加速與核心空間的訊息傳遞;即epoll使用mmap減少複製開銷。

 

五、JavaNIO

JavaNIO是基於多路複用技術的, Java NIO中的選擇器依賴作業系統核心的這些系統呼叫,即在Linux中NIO其實就是呼叫核心的select或poll或epoll實現的.至於選擇哪一種實現方式與核心版本有關,詳細看:https://blog.csdn.net/hsuxu/article/details/9876983

六、說明

關於JavaNIO不可能這麼少就講完了。後面我會再寫一篇講解一下Java BIO,AIO,NIO的實現,重點會在AIO和NIO上,因為這塊我一直了解的不太后,之後會分析一下Tomcat中的IO模型是怎麼樣的。如果一篇寫不開會多寫幾篇。原計劃還想分析一下Netty或dubbo中的模型,由於能力時間精力有限,可能會先分析到Tomcat Java IO這塊暫靠段落。

mysql這塊我會繼續整理。

雖然這篇文章很多不是我寫得。但是從我今天整理到現在寫完,也用了近六七個個小時。寫這樣的部落格都難,何況真自己從頭寫呢。。

七、參考資料nio
https://www.cnblogs.com/diegodu/p/6823855.html
https://www.cnblogs.com/dongguacai/p/5770287.html
https://www.jianshu.com/p/486b0965c296
https://blog.csdn.net/youyaecho/article/details/51799423

linux
https://www.cnblogs.com/jeakeven/p/5435916.html
https://www.cnblogs.com/zhaodahai/p/6831456.html

https://www.2cto.com/kf/201611/561895.html
https://www.cnblogs.com/zhaodahai/p/6831456.html


POSIX
https://blog.csdn.net/yongyu_it/article/details/77094089

java nio
https://blog.csdn.net/u014507083/article/details/73784898
http://baijiahao.baidu.com/s?id=1570735523203847&wfr=spider&for=pc
http://weixiaolu.iteye.com/blog/1479656

綜合:
https://blog.csdn.net/u014507083/article/details/73784898
https://www.cnblogs.com/losing-1216/p/5073051.html
https://blog.csdn.net/zxzx2966381/article/details/50420467


阻塞與掛起
https://www.cnblogs.com/hoobey/p/6915638.html
https://www.zhihu.com/question/42962803

Linux多路複用

https://www.cnblogs.com/jeakeven/p/5435916.html