本文主要探討的問題有如下兩個:linux
在介紹網絡模型以前,先簡單介紹一些基本概念。編程
文件描述符(file descriptor,簡稱 fd)在形式上是一個非負整數。實際上,它是一個索引值,指向內核爲每個進程所維護的該進程打開文件的記錄表。當程序打開一個現有文件或者建立一個新文件時,內核向進程返回一個文件描述符。在程序設計中,一些涉及底層的程序編寫每每會圍繞着文件描述符展開。可是文件描述符這一律念每每只適用於UNIX、Linux這樣的操做系統。數組
在 Linux 中,內核將全部的外部設備都當作一個文件來進行操做,而對一個文件的讀寫操做會調用內核提供的系統命令,返回一個 fd,對一個 socket 的讀寫也會有相應的描述符,稱爲 socketfd(socket 描述符),實際上描述符就是一個數字,它指向內核中的一個結構體(文件路徑、數據區等一些屬性)。
安全
如今操做系統都是採用虛擬存儲器,那麼對32位操做系統而言,它的尋址空間(虛擬存儲空間)爲4G(2的32次方)。操心繫統的核心是內核,獨立於普通的應用程序,能夠訪問受保護的內存空間,也有訪問底層硬件設備的全部權限。爲了保證用戶進程不能直接操做內核,保證內核的安全,操心繫統將虛擬空間劃分爲兩部分,一部分爲內核空間,一部分爲用戶空間。針對 linux 操做系統而言(以32位操做系統爲例)網絡
每一個進程能夠經過系統調用進入內核,所以,Linux 內核由系統內的全部進程共享。因而,從具體進程的角度來看,每一個進程能夠擁有 4G 字節的虛擬空間。數據結構
當一個進程在執行時,CPU 的全部寄存器中的值、進程的狀態以及堆棧中的內容被稱爲該進程的上下文。多線程
當內核須要切換到另外一個進程時,它須要保存當前進程的全部狀態,即保存當前進程的上下文,以便在再次執行該進程時,可以必獲得切換時的狀態執行下去。在 Linux 中,當前進程上下文均保存在進程的任務數據結構中。在發生中斷時,內核就在被中斷進程的上下文中,在內核態下執行中斷服務例程。但同時會保留全部須要用到的資源,以便中繼服務結束時能恢復被中斷進程的執行。併發
根據 UNIX 網絡編程對 IO 模型的分類,UNIX 提供瞭如下 5 種 IO 模型。異步
最經常使用的 IO 模型就是阻塞 IO 模型,在缺省條件下,全部文件操做都是阻塞的,以 socket 讀爲例來介紹一下此模型,以下圖所示。socket
在用戶空間調用 recvfrom
,系統調用直到數據包達到且被複制到應用進程的緩衝區中或中間發生異常返回,在這個期間進程會一直等待。進程從調用 recvfrom
開始到它返回的整段時間內都是被阻塞的,所以,被稱爲阻塞 IO 模型。
recvfrom
從應用到內核的時,若是該緩衝區沒有數據,就會直接返回 EWOULDBLOCK
錯誤,通常都對非阻塞 IO 模型進行輪詢檢查這個狀態,看看內核是否是有數據到來,流程以下圖所示。
也就是說非阻塞的 recvform
系統調用調用以後,進程並無被阻塞,內核立刻返回給進程。
recvform
系統調用。重複上面的過程,循環往復的進行 recvform
系統調用,這個過程一般被稱之爲輪詢。輪詢檢查內核數據,直到數據準備好,再拷貝數據到進程,進行數據處理。須要注意,拷貝數據整個過程,進程仍然是屬於阻塞的狀態。
在 Linux 下,能夠經過設置 socket 使其變爲 non-blocking。
Linux 提供 select、poll、epoll,進程經過講一個或者多個 fd 傳遞給 select、poll、epoll 系統調用,阻塞在 select 操做(這個是內核級別的調用)上,這樣的話,能夠同時監聽多個 fd 是否處於就緒狀態。其中,
這個後面詳細講述,具體流程以下圖所示。
多路複用的特色是經過一種機制一個進程能同時等待 IO 文件描述符,內核監視這些文件描述符(套接字描述符),其中的任意一個進入讀就緒狀態,select, poll,epoll 函數就能夠返回,它最大的優點就是能夠同時處理多個鏈接。
首先須要開啓 socket 信號驅動 IO 功能,並經過系統調用 sigaction
執行一個信號處理函數(非阻塞,當即返回)。當數據就緒時,會爲該進程生成一個 SIGIO 信號,經過信號回調通知應用程序調用 recvfrom
來讀取數據,並通知主循環喊出處理數據,流程以下圖所示。
告知內核啓動某個事件,並讓內核在整個操做完成後(包括將數據從內核複製到用戶本身的緩衝區)經過咱們,流程以下圖所示。
與信號驅動模式的主要區別是:
內核是經過嚮應用程序發送 signal 或執行一個基於線程的回調函數來完成此次 IO 處理過程,告訴用戶 read 操做已經完成,在 Linux 中,通知的方式是信號:
IO 多路複用經過把多個 IO 阻塞複用到同一個 select 的阻塞上,從而使得系統在單線程的狀況下,能夠同時處理多個 client 請求,與傳統的多線程/多進程模型相比,IO 多路複用的最大優點是系統開銷小,系統不須要建立新的額外的進程或線程,也不須要維護這些進程和線程的運行,節省了系統資源,IO 多路複用的主要場景以下:
IO 多路複用實際上就是經過一種機制,一個進程能夠監視多個描 fd,一旦某個 fd 就緒(通常是讀就緒或者寫就緒),可以通知程序進行相應的讀寫操做,目前支持 IO 多路複用的系統有 select、pselect、poll、epoll,但它們本質上都是同步 IO。
在 Linux 網絡編程中,最初是選用 select 作輪詢和網絡事件通知,然而 select 的一些固有缺陷致使了它的應用受到了很大的限制,最終 Linux 選擇 epoll。
select 函數監視的 fd 分3類,分別是 writefds
、readfds
、和 exceptfds
。調用後select 函數會阻塞,直到有 fd 就緒(有數據 可讀、可寫、或者有 except),或者超時(timeout 指定等待時間,若是當即返回設爲 null 便可),函數返回。當select函數返回後,能夠經過遍歷 fdset,來找到就緒的 fd。
select 目前幾乎在全部的平臺上支持,其良好跨平臺支持也是它的一個優勢。select 的一個最大的缺陷就是單個進程對打開的 fd 是有必定限制的,它由 FD_SETSIZE
限制,默認值是1024,若是修改的話,就須要從新編譯內核,不過這會帶來網絡效率的降低。
select 和 poll 另外一個缺陷就是隨着 fd 數目的增長,可能只有不多一部分 socket 是活躍的,可是 select/poll 每次調用時都會線性掃描所有的集合,致使效率呈現線性的降低。
poll 本質上和 select 沒有區別,它將用戶傳入的數組拷貝到內核空間,而後查詢每一個 fd 對應的設備狀態,若是設備就緒則在設備等待隊列中加入一項並繼續遍歷,若是遍歷完全部 fd 後沒有發現就緒設備,則掛起當前進程,直到設備就緒或者主動超時,被喚醒後它又要再次遍歷 fd。這個過程經歷了屢次無謂的遍歷。
它沒有最大鏈接數的限制,緣由是它是基於鏈表來存儲的,可是一樣如下兩個缺點:
epoll 支持水平觸發和邊緣觸發,最大的特色在於邊緣觸發,它只告訴進程哪些 fd 變爲就緒態,而且只會通知一次。還有一個特色是,epoll 使用【事件】的就緒通知方式,經過 epoll_ctl
註冊 fd,一旦該 fd 就緒,內核就會採用相似 callback 的回調機制來激活該 fd,epoll_wait
即可以收到通知。
epoll的優勢:
mmap
同一塊內存實現。epoll 對 fd 的操做有兩種模式:LT(level trigger)和ET(edge trigger)。LT 模式是默認模式,LT 模式與 ET 模式的區別以下:
epoll_wait
檢測到描述符事件發生並將此事件通知應用程序,應用程序能夠不當即處理該事件,下次調用 epoll_wait
時,會再次響應應用程序並通知此事件;epoll_wait
檢測到描述符事件發生並將此事件通知應用程序,應用程序必須當即處理該事件,若是不處理,下次調用 epoll_wait
時,不會再次響應應用程序並通知此事件。類別 | select | poll | epoll |
---|---|---|---|
支持的最大鏈接數 | 由 FD_SETSIZE 限制 |
基於鏈表存儲,沒有限制 | 受系統最大句柄數限制 |
fd 劇增的影響 | 線性掃描 fd 致使性能很低 | 同 select | 基於 fd 上 callback 實現,沒有性能降低的問題 |
消息傳遞機制 | 內核須要將消息傳遞到用戶空間,須要內核拷貝 | 同 select | epoll 經過內核與用戶空間共享內存來實現 |
介紹完 IO 多路複用以後,後續咱們看一下 Java 網絡編程中的 NIO 模型及其背後的實現機制。
==========END==========