一、什么是io多路復用
在bio模型中,一個io請求對應一個線程,造成線程極大浪費,且沒有數據發送的情況下,線程也一直阻塞等待,資源利用率不高。
在nio模型中,多個io請求歸一個線程管理,當io就緒的時候,線程通知應用程序進行數據讀寫,這樣不用創建很多線程,線程也不用一直阻塞等待io就緒,這樣的技術稱為io多路復用。
io多路復用通過一種機制,可以監視多個文件描述符,一旦某個文件描述就緒(),能夠通知應用程序進行相應的io讀寫操作。
二、為什么用io多路復用
io多路復用解決了多線程io阻塞問題,避免創建很多線程,及降低線程上下文切換的開銷,提升資源利用率。
三、io多路復用模型原理分析
監視多個文件描述符的機制不同,io多路復用技術主要有,poll,epoll,本質上都是阻塞io,因為他們都需要在io就緒后線程標識符 有什么用,自己負責進行數據在用戶空間和內核空間之間copy。非阻塞io無需自己負責數據copy,操作系統會把數據從內核空間copy到用戶空間。
1.:int (int nfds, *, *, *, *);
監視多個文件描述符的屬性變化(可讀、可寫、錯誤異常)。監視的文件描述符有3類:,,,調用 線程會阻塞,知道有描述符就緒,才返回,之后通過遍歷fdset來找到就緒的描述符。
nfds: 要監視的文件描述符的范圍,在 Linux 上最大值一般為1024。
: 監視的可讀描述符集合,只要有文件描述符即將進行讀操作,這個文件描述符就存儲到這。
: 監視的可寫描述符集合,只要有文件描述符即將進行寫操作,這個文件描述符就存儲到這里。
: 監視的錯誤異常描述符集合。
: 超時時間,它告知內核等待任何一個文件描述符就緒可花多少時間。有3中設置:
1)永遠等待,僅有一個描述符就緒后返回;
2)不等待,檢查所有描述符看其是否就緒,是一種輪訓方式,返回就緒的個數;
3)等待固定時間,返回就緒的個數。
優點:所有平臺都支持,不存在兼容問題。
缺點:1)每次調用,都要把fe集合從用戶態copy到內核態,fd很多時,開銷很大。同時在內核態要遍歷fd,看其是否就緒,開銷也很大。
2.poll:int poll( fds, nfds, int );
poll的本質和一樣,沒有太大差別,管理多個描述符也是進行輪詢,根據描述符狀態進行處理,但poll沒有最大描述符個數限制(限制1024個),但是隨著數量的增加,性能也有下降。poll() 和 () 同樣存在一個缺點就是,包含大量文件描述符的數組被整體復制于用戶態和內核的地址空間之間,而不論這些文件描述符是否就緒,它的開銷隨著文件描述符數量的增加而線性增大。
fds:不同于 () ,使用三個位置來表示三個 fd集合,poll() 使用一個 的指針,指向一個 結構體數組,結構體中包括了要監視的文件描述符和事件,感興趣的事件由結構中 來確定,調用后實際發生的時間將被填寫在結構體的 域中,如下:
{
int fd;/ 文件描述符 /
short ; / 感興趣的事件 /
short ; / 實際發生了的事件 */
};
nfds: 指定第一個參數數組元素個數,并不是限制監視的文件描述符個數,poll不限制文件描述符個數。
: 指定等待的毫秒數,無論 I/O 是否就緒,poll() 都會返回。當等待時間為 0 時,poll() 函數立即返回,為 -1 則使 poll() 一直阻塞直到一個感興趣的事件發生后返回。
poll() 返回結構體中 域不為 0 的文件描述符個數;如果在超時前沒有任何事件發生,poll()返回 0;
返回-1說明有錯誤發生,具體錯誤有以下集中:
EBADF:一個或多個結構體中指定的文件描述符無效。
:fds 指針指向的地址超出進程的地址空間。
EINTR:請求的事件之前產生一個信號,調用可以重新發起。
:nfds 參數超出 值。
:可用內存不足,無法完成請求。
poll和非常類似,只是fd集合的方式不同,poll使用結構體數組,用set,其他都差不多。
3.epoll:
int (int size);創建一個epoll文件描述符,管理其他多個文件描述符。
size: 用來告訴內核這個監聽的數目一共有多大,參數 size 并不是限制了 epoll 所能監聽的描述符最大個數,只是對內核初始分配內部數據結構的一個建議。當創建好 epoll 描述符后,它就是會占用一個 fd 值,在使用完 epoll 后,必須調用 close() 關閉,否則可能導致 fd 被耗盡。
int (int epfd, int op, int fd, *event);事件注冊,它不同于 () 是在遍歷描述符時告訴內核要關注什么事件,而是在這里先注冊要關注的事件。
epfd: epoll 專用的文件描述符,()的返回值
op: 表示動作,用三個宏來表示:
:注冊新的 fd 到 epfd 中;
:修改已經注冊的fd的監聽事件;
:從 epfd 中刪除一個 fd;
fd: 需要監視的文件描述符
event: 告訴內核要監聽什么事件,主要有以下事件:
:表示對應的文件描述符可以讀(包括對端 正常關閉);
:表示對應的文件描述符可以寫;
:表示對應的文件描述符有緊急的數據可讀;
:表示對應的文件描述符發生錯誤;
:表示對應的文件描述符被掛斷;
:將 EPOLL 設為邊緣觸發(Edge )模式,這是相對于水平觸發(Level )來說的。
:只監聽一次事件,當監聽完這次事件之后,如果還需要繼續監聽這個 的話,需要再次把這個 加入到 EPOLL 隊列里。
int ( int epfd, * , int , int );注冊在epoll中已經發生的事件,類似于 () 調用,輪訓事件表,看是否有事件發生。
epfd: epoll 專用的文件描述符,()的返回值
:epoll 將會把發生的事件添加到 數組中, 不可以是空指針,內核只負責把數據復制到這個 數組中,不會去幫助我們在用戶態中分配內存。
: 告之內核這個 有多大。
: 超時時間,為 -1 時,函數為阻塞,知道有事件發生。
返回就緒的文件描述符個數。
epoll 對文件描述符的操作有兩種模式:LT(level :水平觸發模式,)和 ET(edge :邊沿觸發模式)。
LT模式:當 檢測到描述符事件發生并將此事件通知應用程序后線程標識符 有什么用,應用程序可以不處理該事件,下次調用 時,會再次向應用程序通知此事件,直到事件被處理,可能會重復處理事件。
ET 模式:當 檢測到描述符事件發生并將此事件通知應用程序后,應用程序必須立即處理該事件,如果不處理,下次調用 時,不會再次向應用程序通知此事件,可能會丟失事件處理。
在 /poll中,內核對所有監視的文件描述符進行掃描,返回就緒的個數;而 epoll() 事先通過 () 來注冊一個文件描述符及感興趣的事件,一旦某個文件描述符上感興趣的事件發生后,內核會采用類似 的回調機制,將發生的事件填入()的結構中,當進程調用 () 時便得到就緒的個數。
優點:
1.監視的描述符數量不受限制。
2.效率不會隨著監視 fd 的數量的增長而下降。(),poll() 需要自己不斷輪詢所有 fd 集合來發現就緒設備,當fd集合很大時,開銷很大。而epoll是通過事件回調機制發現就緒的設備,當設備就緒后,回進行回調,不需要輪訓。
3.(),poll() 每次調用都要把 fd 集合從用戶態往內核態拷貝一次,而epoll通過內核與用戶空間共享一塊內存,避免了無畏的內存拷貝(mmap機制)。