三種I/O複用函數的比較總結

下列情況下可以用到I/O多路轉接技術:
1.客戶端程序要同時處理多個socket。(比如非阻塞的connect技術)
2.客戶端程序要同時處理用戶輸入和網絡連接。
3.TCP服務器要同時處理監聽socket和連接socket。(I/O複用使用最多的場合)
4.服務器要同時處理TCP請求和UDP請求。
5.服務器要同時監聽多個端口,或者處理多種服務。(xinetd服務)
I/O複用雖然可以同時監聽多個文件描述符,但他本身是阻塞的,並且,當多個文件描述符同時就緒時。如果不採用額外的措施,程序就只能按順序依次處理其中的每一個文件描述符,這樣I/O複用看起來就像是串行的,要實現兵法就需要用多進程或多線程實現。
select:
int select(int nfds, fd_set* readfds, fd_set* writefds, fd_set* exceptfds, struct timecal* timeout);
nfds:指定被監聽的文件描述符的總數。它通常被設置爲select監聽的所有文件的描述符中的最大值加1.
readfds、writefds、excepfds:分別指向可讀可寫和異常事件對應的文件描述符集合。應用程序調用select函數時,通過這三個參數傳入自己感興趣的文件描述符select調用返回時,內核將修改他們來通知應用程序哪些文件描述符已經就緒fd_set類型結構體只包含一個整形數組,數組每一位標記一個文件描述符。這樣就限制了select能同時處理的文件描述符的總量。
timeout:設置select函數的超時時間。它是一個timeval類型的指針,內核將修改它告訴應用程序select等待了多久。應爲select調用完成後timeout的值是不能完全信任的。
文件描述符就緒條件:
在網絡編程中,下列情況下socket可讀:
socket內核接收緩衝區中接收緩衝區中的字節數大於或等於其低水位標記SO_RCVLOWAT。這時,我們可以無阻塞的讀該socket,並讀操作返回0。
socket通信雙方關閉連接,此時對該socket的讀操作將返回0。
監聽socket上有新的連接請求。
socket上有未處理的錯誤。可以用getsockopt來清除錯誤。
下列情況下socket可寫:
socket內核發送緩衝區中你給的可用字節數大於過等於其低水位標記SO_SNDLOWAT。
socket的寫文件描述符被關閉,對寫操作被關閉的socket執行寫操作會觸發一個SIGPIPE信號,這是,可以隨意寫。
socket使用非阻塞connect連接成功或者失敗之後。
socket上有未處理的錯誤,同上。

poll
int poll(struct pollfd* fds, nfds_t nfds, int timeout)
與select類似,也是在指定時間內輪詢一定量的文件描述符,以測試其中是否有已經就緒者。
fds是一個pollfd類型的數組。指定用戶感興趣的文件描述符上發生的可讀可寫和異常事件。pollfd類型結構體中的events告訴poll監聽fd上的哪些事情,是一系列時間的按位或。revents成員由內核修改,通知應用程序上fd實際發生了哪些事件。
GUN爲poll系統調用增加了一個POLLRDHUP事件,它在socket上接收到對方關閉連接的請求之後觸發。這樣,應用程序就可以區分socket上接收到的是有效數據還是關閉連接的請求,並做出相應的處理。但是使用POLLRDHUP的事件,需要在代碼最開始處定義_GUN_SOURCE。nfds指定被監聽的事件集合fds的大小。

epoll
Linux特有的I/O複用函數。epoll使用一組函數來完成任務,而不是單個函數。epoll把用戶關心的文件描述符放在內核裏的一個時間表中。(事件表在內核中的實現其實就是一顆紅黑樹,紅黑樹的查找效率高,找到之後放到內核的就緒隊列中)。epoll_create就是創建紅黑樹和就緒隊列過程 。
int epoll_wait(int size)
size參數不起作用,只是告訴內核,它需要的事件表有多大。該函數返回的文件描述符將用作其他所有的epoll系統調用的第一個參數,表示要訪問的內核事件表。
int epoll_ctl(int epfd, int op, int fd, struct epoll_event* event)
fd是要操作的文件描述符,op參數則指定操作類型。EPOLL_CTL_ADD(往事件表(epfd)上添加fd上的事),EPOLL_CTL_DEL,EPOLL_CTL_MOD。event是epoll_event類型的結構體指針。
epoll_wait
epoll系列函數調用的主要接口。在一組超時時間內等待一組文件描述符上的事件。
int epoll_wait(int epfd, struct epoll_event* event, int maxevents, int timeout)
timeout指超時時間,maxevents指定最多監聽多少個事件,必須大於0。epoll_wait函數如果檢測到事件,就將所有就緒的時間從內核事件表(epfd)中複製到它的第二個參數events指向的數組中去。這個數組只用於輸出epoll_wait檢測到的就緒事件。
LT和ET模式
epoll對文件描述符的操作有兩種模式:LT(level trigger,水平觸發)和ET(edge trigger,邊沿觸發)。LT是默認的工作模式,這種模式下,epoll相當於一個效率極高的poll。當往epoll內核事件表註冊添加一個文件描述符上的EPOLLET事件時,epoll將以ET模式來操作該文件描述符。ET模式是epoll的高效工作模式。
LT:當epoll_wait檢測到其上有事件發生並將此事件通知到應用程序後,應用程序可以不立即處理該事件。這樣,當應用程序下一次調用epoll_wait時,epoll_wait還會再次嚮應用程序通告此事。直到事件被處理。
ET:當epoll_wait檢測到其上有事件發生並將此事件通知應用程序後,應用程序必須立即處理該事件,因爲後續的epoll_wait調用將不再向應用程序通知這一事件。
ET在很大程度上降低了同一個epoll事件被重複觸發的次數。因此,ET效率比LT模式高。

防止一個socket上的事件被觸發多次:使用epoll的EPOLLONESHOT事件實現
即使我們使用ET工作模式,一個socket上的某個事件也可能被觸發多次。這在併發程序就會引起一個問題。比如,一個線程(進程),在讀取完某個socket上的數據後開始處理這些數據,而在數據處理的過程中該socket上又有新數據可讀,這時EPOLLIN再次被觸發,此時,又有一個線程被喚醒來讀取這些新的數據。於是就出現了兩個線程同時操作一個socket的局面。我們期望的是一個socket在任意一個時刻都只被一個線程處理。註冊了EPOLLONESHOT事件
的文件描述符,操作系統最多觸發其上註冊的一個事件,且只觸發一次,除非我們用epoll_wait函數重置該文件描述符上註冊的EPOLLOONTSHOT事件。這樣,當一個線程在處理某個socket時,其他線程是不可能有機會操作該socket的。註冊了EPOLLONESHOT事件的socket一旦被被某個線程處理完畢,該線程就應該立即重置這個 socket傻昂的EPOLLONESHOT事件,以確保這個socket下次可讀時,其EPOLLONESHOT能被觸發,進而讓其他工作線程有機會繼續處理這個socket。
三種I/O複用函數的比較



系統調用 select poll epoll
事件集合 用戶通過3個參數分別傳入感興趣的可讀可寫及異常事件,內核通過對這些參數的在線修改來反饋其中的就緒事件。這使得用戶每次調用select都要重置這3個參數 統一處理所有事件類型,因此只需一個事件集參數。用戶通過pollfd.events傳入感興趣的事件,內核通過修改pollfd.revents反饋其中就緒的事件 內核通過一個事件表直接管理用戶感興趣的所有事件。因此每次調用epoll_wait系統調用的參數events僅用來反饋就緒事件
應用程序索引就緒文件描述符的時間複雜度 O(n) O(n) O(1)
最大支持文件描述符個數 一般有最大限制 65535 65535
工作模式 LT LT 支持ET高效模式
內核實現和工作效率 採用輪詢方式來檢測就緒事件,算法複雜度爲O(n) 採用輪詢方式來檢測就緒事件,算法複雜度爲O(n) 採用回調方式來檢測就緒事件,算法複雜度爲O(1)

epoll高效體現在哪裏?
1.底層用紅黑樹維護一個事件集,用一個就緒隊列來存放就緒事件,查找效率很高。
2.使用mmap(內存映射技術)加速內核與用戶空間的消息傳遞,減少拷貝,提高效率
3.內核採用callback回調機制,激活這個文件描述符(將節點放到就緒隊列中去)調用epoll_wait時就會被通知。
4.I/O效率不隨fd數目的增加而線性下降。






































發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章