咱們都知道unix(like)世界裏,一切皆文件,而文件是什麼呢?文件就是一串二進制流而已,無論socket,仍是FIFO、管道、終端,對咱們來講,一切都是文件,一切都是流。在信息 交換的過程當中,咱們都是對這些流進行數據的收發操做,簡稱爲I/O操做(input and output),往流中讀出數據,系統調用read,寫入數據,系統調用write。不過話說回來了 ,計算機裏有這麼多的流,我怎麼知道要操做哪一個流呢?對,就是文件描述符,即一般所說的fd,一個fd就是一個整數,因此,對這個整數的操做,就是對這個文件(流)的操做。咱們建立一個socket,經過系統調用會返回一個文件描述符,那麼剩下對socket的操做就會轉化爲對這個描述符的操做。不能不說這又是一種分層和抽象的思想。vim
什麼是程序的阻塞呢?想象這種情形,好比你等快遞,但快遞一直沒來,你會怎麼作?有兩種方式:網絡
很顯然,你沒法忍受第二種方式,不只耽擱本身的時間,也會讓快遞很想打你。
而在計算機世界,這兩種情形就對應阻塞和非阻塞忙輪詢。socket
先說說阻塞,由於一個線程只能處理一個套接字的I/O事件,若是想同時處理多個,能夠利用非阻塞忙輪詢的方式,僞代碼以下:函數
while true { for i in stream[] { if i has data read until unavailable } }
while true { select(streams[]) //這一步死在這裏,知道有一個流有I/O事件時,才往下執行 for i in streams[] { if i has data read until unavailable } }
可是依然有個問題,咱們從select那裏僅僅知道了,有I/O事件發生了,卻並不知道是哪那幾個流(可能有一個,多個,甚至所有),咱們只能無差異輪詢全部流,找出能讀出數據,或者寫入數據的流,對他們進行操做。因此select具備O(n)的無差異輪詢複雜度,同時處理的流越多,無差異輪詢時間就越長。spa
epoll能夠理解爲event poll,不一樣於忙輪詢和無差異輪詢,epoll會把哪一個流發生了怎樣的I/O事件通知咱們。因此咱們說epoll其實是事件驅動(每一個事件關聯上fd)的,此時咱們對這些流的操做都是有意義的。(複雜度下降到了O(1))僞代碼以下:線程
while true { active_stream[] = epoll_wait(epollfd) for i in active_stream[] { read or write till } }
能夠看到,select和epoll最大的區別就是:select只是告訴你必定數目的流有事件了,至於哪一個流有事件,還得你一個一個地去輪詢,而epoll會把發生的事件告訴你,經過發生的事件,就天然而然定位到哪一個流了。不能不說epoll跟select相比,是質的飛躍,我以爲這也是一種犧牲空間,換取時間的思想,畢竟如今硬件愈來愈便宜了。代理
好了,咱們講了這麼多,再來總結一下,到底什麼是I/O多路複用。
先講一下I/O模型:
首先,輸入操做通常包含兩個步驟:unix
其次瞭解一下經常使用的3種I/O模型:code
最普遍的模型是阻塞I/O模型,默認狀況下,全部套接口都是阻塞的。 進程調用recvfrom系統調用,整個過程是阻塞的,直到數據複製到進程緩衝區時才返回(固然,系統調用被中斷也會返回)。接口
當咱們把一個套接口設置爲非阻塞時,就是在告訴內核,當請求的I/O操做沒法完成時,不要將進程睡眠,而是返回一個錯誤。當數據沒有準備好時,內核當即返回EWOULDBLOCK錯誤,第四次調用系統調用時,數據已經存在,這時將數據複製到進程緩衝區中。這其中有一個操做時輪詢(polling)。
此模型用到select和poll函數,這兩個函數也會使進程阻塞,select先阻塞,有活動套接字才返回,可是和阻塞I/O不一樣的是,這兩個函數能夠同時阻塞多個I/O操做,並且能夠同時對多個讀操做,多個寫操做的I/O函數進行檢測,直到有數據可讀或可寫(就是監聽多個socket)。select被調用後,進程會被阻塞,內核監視全部select負責的socket,當有任何一個socket的數據準備好了,select就會返回套接字可讀,咱們就能夠調用recvfrom處理數據。
正由於阻塞I/O只能阻塞一個I/O操做,而I/O複用模型可以阻塞多個I/O操做,因此才叫作多路複用。