由於項目須要,接觸和使用了Netty,Netty是高性能NIO通訊框架,在業界擁有很好的口碑,但知其然不知其因此然。編程
因此本系列文章將從基礎開始學起,深刻細緻的學習NIO。本文主要是介紹五種I/O模型,概念是枯燥的,不過仍是得理解才行。安全
在網絡管理,Linux UNIX很類似.UNIX系統一直被用作高端應用或服務器系統,所以擁有一套完善的網絡管理機制和規則, Linux沿用了這些出色的規則,使網絡的可配置能力很強,爲系統管理提供了極大的靈活性.服務器
通俗一點講,就是在網絡方面Linux和UNIX是很是類似的,網絡模型大可借鑑UNIX網絡編程中的描述。網絡
這裏介紹四個概念,方便五種I/O模型的理解:框架
1.全部外部設備皆文件異步
Linux的內核將全部的外部設備都看做是一個文件來操做,對一個文件的讀寫操做會調用內核提供的系統命令,返回一個file descriptor(fd,文件描述符)。socket
面對一個socket也會有相應的描述符,成爲socketfd(socket描述符),描述符就是一個數字,他指向內核中的一個結構體(文件路徑,數據區等一些屬性)。函數
2.recvfrom()函數性能
ssize_t recvfrom(int sockfd, void *buff, size_t nbytes, int flags, strut sockaddr *from, socklen_t *addrlen);
該函數執行成功,則返回讀或寫的字節數,如出錯則爲-1。學習
*from參數,指向一個將由該函數在返回時填寫數據包發送者的協議地址的套接字地址結構,
*addrlen參數,套接字地址結構,而且該結構體中填寫的則放在addrlen所指的整數中返回給調用者
經過這兩個參數,咱們能夠知道是誰發送了數據包(udp狀況下),或是誰發送了數據包(TCP狀況下);
3.應用進程與內核
應用進程就是常規的程序,用戶程序,打開任務管理器,在應用分組就能夠看到應用進程,以下圖所示:
圖中紅框內的,都是應用進程。全部的應用進程都是運行在用戶態中。(用戶態的概念直接戳連接),運行時所處空間是用戶空間
內核就是操做系統的內核,它的做用是將應用進程與硬件分開。能夠這麼理解,全部涉及到I/O的操做都直接或者間接的通過內核程序。
若是應用進程能夠直接操做硬件,那麼一些病毒就會蓄意的對計算機硬件進行破壞,那就不可控制了。這樣的機制就保證了系統的安全性。運行時是處於內核態,所處空間是內核空間。
網絡傳輸數據,首先是內核先接收到數據,而後內核將數據拷貝到用戶態中供應用進程使用。
請先理解上面的基本概念,接下來將介紹五種傳統的I/O模型。
最傳統的一種IO模型,即在讀寫數據過程當中會發生阻塞現象。
在應用進程經過內核調用recvfrom()函數,其系統調用直到數據包到達且被複制到應用進程的緩衝區或者發生錯誤時纔會返回,在此期間會一直等待。
這句話太晦澀難懂了,簡單點說就是:應用進程經過內核調用recvfrom(),收到數據的話則將數據從內核態複製到用戶態,沒有收到就一直阻塞。
應用系統仍是調用recvfrom,可是他不會阻塞與此,而是不斷的去輪詢的是否有數據準備好,若是沒有準備好,就直接返回一個EWOULDBLOCK錯誤。
總結:
與同步阻塞I/O相比,若是數據準備好,不會一直阻塞與此,而是直接返回錯誤,接收到錯誤以後,就能夠乾點別的事,這是他的優勢。可是缺點也很明顯,
任務完成的響應延遲增大了。由於極可能在兩次輪詢之間,socketfd就處於read狀態了,因此致使總體的吞吐量降低了。
I/O多路複用技術的最大優點是系統開銷小,系統沒必要建立進程/線程,也沒必要維護這些進程/線程。
與同步非阻塞I/O不斷輪詢不一樣的是,I/O複用是使用一個線程循環輪詢socketfd集合是否處於read狀態。
Linux提供select/epoll,進程經過一個或者多個socketfd傳遞給select或poll系統調用,阻塞在select上,這樣select/poll能夠偵測到多個socketfd是否處於就緒狀態。
select/poll是順序掃描socketfd是否就緒,並且支持的fd頗有限。
Linux還提供了一個epoll系統調用,epoll基於事件驅動方式代替順序掃描,所以性能更高。當有fd就緒時,當即回調函數rollback。
關於select/poll,epoll
select/poll
該函數容許進程指示內核等待多個事件中的任何事件發生,而且只在有一個或多個事件發生或經歷一段時間指定的時間才喚醒它。
舉個例子,也就是說進程能夠通知內核在socketfd集合{1,2,3}進行偵聽,知道socketfd集合中任何一個可讀的話,就返回。這個等待的過程是阻塞的,它能夠偵聽多個,可是偵聽的數量是有限的。
看下官方關於epoll的解釋
The epoll API performs a similar task to poll: monitoring multiple file descriptors to see if I/O is possible on any of them.
The epoll API can be used either as an edge-triggered or a level-triggered interface and scales well to large numbers of watched file descriptors.
epoll和poll執行相似的任務,監控多個fd,若是多個fd中任何一個有I/O時間,便可及時發現。epoll的API既能夠做爲邊觸發,也能夠做爲水平觸發接口和可擴展到大量的監視fd。
epoll是Linux下多路複用IO接口select/poll的加強版本,它能顯著提升程序在大量鏈接中只有少許活躍的狀況下的系統CPU利用率.
epoll與select/poll的對比
一個進程可以打開socketfd的限制
select一個進程可以打開的FD是由FD_SETSIZE限制的,默認是2048,能夠選擇修改宏而後從新編譯服務器代碼,相關資料代表這樣會帶來網絡效率的降低。
epoll沒有打開FD數量的限制,在1GB內存的機器上大約是10萬左右,具體數目能夠cat /proc/sys/fs/file-max查看,通常來講這個數目和系統內存關係很大。
IO效率不會由於socketfd數量提升而線性降低
select和poll擁有一個很大的socketfd集合和,因爲每次都會調用線性掃描所有的集合,帶來的後果就是效率線性降低
epoll有着相對更好的解決方案,在很大的socketfd集合中,它只會對活躍的socketfd進行操做。epoll是根據每一個fd上面的callback函數實現的,只有活躍的socketfd纔會去調用callback函數
mmap加速內核與用戶空間的傳遞
不管是select/poll和epoll,都是須要經過內核將FD消息拷貝到用戶空間,拷貝是費時的。epoll經過內核與用戶控件mmap同一塊內存實現沒必要要的拷貝,從而加快效率。
總結:
進程經過調用內核中的select/poll/epoll,監聽socketfd集合的讀寫就緒狀態,多個socketfd都能在一個線程中交替完成,所謂的複用就是指使用的同一個線程。
I/O複用實際仍是同步I/O,歸根到底仍是應用進程主動向內核查詢狀態。
I/O多路複用是OS提供的最穩定的IO模型,大部分主流的應用都是基於此種IO模型構建的,好比NodeJS,Netty框架。
首先開啓套接字信號驅動I/O功能,並經過系統調用sigaction執行一個信號處理函數,此時系統繼續運行,並不會阻塞。
當數據準備就緒時,就爲該進程生成一個SIGIO信號,經過信號回調通知,通知應用進程調用recvfrom來讀取數據。
一句話簡單說:產品經理讓你改一個需求,而且讓你改好了告訴他,給他看一下,因而你就吭哧吭哧的作了,(產品經理就去忙別的事情了,好比又去改需求了)而且作好了叫了產品經理來看。
用戶進程進行aio_read系統調用以後,就去幹別的事情了。當socketfd數據準備好以後,內核直接複製數據到用戶空間,而後內核向用戶進程發送通知,數據準備好了。
總結:
整個I/O過程都是非阻塞的,這個是真正的異步非阻塞。
理解五種I/O模型,有助於理解網絡I/O,寫出更健壯的代碼。
在實際工程項目中,廣泛使用I/O複用模型,本章重點介紹了I/O複用模型,Java中的NIO也是基於此。學習I/O模型有助於更好的理解NIO,學習Netty框架。
勿在浮沙築高樓
參考:
《UNIX網絡編程》
《Netty權威指南》