epoll水平/邊緣觸發模式設置阻塞/非阻塞IO事件觸發條件及次數


在IO多路復用技術中,epoll默認的事件觸發模式為Level_triggered(水平觸發)模式,即當被監控的文件描述符上有可讀/寫事件發生時,epoll_wait()會通知處理程序去讀寫。如果沒有把數據一次性全部讀寫完(如讀寫緩沖區太小),那么下次調用 epoll_wait()時,它還會通知在上沒讀寫完的文件描述符上繼續讀寫,當然如果你一直不去讀寫,會一直通知!如果系統中有大量你不需要讀寫的就緒文件描述符,而它們每次都會返回,這樣會大大降低處理程序檢索自己關心的就緒文件描述符的效率。

Edge_triggered(邊緣觸發):當被監控的文件描述符上有可讀寫事件發生時,epoll_wait()會通知處理程序去讀寫。如果這次沒有把數據全部讀寫完(如讀寫緩沖區太小),那么下次調用epoll_wait()時,它不會通知你,也就是它只會通知一次,直到該文件描述符上出現第二次可讀寫事件才會通知!這種模式比水平觸發效率高,系統不會充斥大量你不關心的就緒文件描述符!

阻塞IO:當讀一個阻塞的文件描述符時,如果在該文件描述符上沒有數據可讀,那么它會一直阻塞在調用函數那里,直到有數據可讀。當你去寫一個阻塞的文件描述符時,如果在該文件描述符上沒有空間(通常是緩沖區)可寫,那么它會一直阻塞,直到有空間可寫。以上的讀和寫統一指在某個文件描述符進行的操作,不單單指真正的讀數據,寫數據,還包括接收連接accept(),發起連接connect()等操作。

非阻塞IO:當你去讀寫一個非阻塞的文件描述符時,不管可不可以讀寫,它都會立即返回,返回成功說明讀寫操作完成了,返回失敗會設置相應errno狀態碼,根據這個errno可以進一步執行其他處理。它不會像阻塞IO那樣,進程阻塞在函數調用那里不動。

注意:epoll在不設置EPOLLET時,默認的事件觸發模式為水平觸發模式;socket在不設置非阻塞(nonblock)參數時,默認是阻塞的

  1)設置邊緣觸發:ev.events = EPOLLOUT | EPOLLET;

  2)設置非阻塞:int flag = fcntl(cfd, F_GETFL); flag |= O_NONBLOCK; fcntl(cfd, F_SETFL, flag);

以下是在Linux操作系統下,為了得出在水平觸發模式下阻塞IO、水平觸發模式下非阻塞IO、邊緣觸發模式下阻塞IO以及邊緣觸發模式下非阻塞IO中EPOLLOUT事件的觸發條件和相應的觸發次數所做的四組對比分析實驗(服務端讀取數據均采用循環讀取,因此水平觸發模式下和邊緣觸發模式下都能將數據完整讀取):

1 水平觸發(LT)模式結合阻塞IO(block)

可以看出在這種模式下,程序會阻塞在epoll_wait()函數這里,只有客服端有新的數據發送到服務端,EPOLLOUT事件(伴隨着EPOLLIN事件)才會觸發:

  1)讀緩沖區能夠一次存放客戶端發送過來的數據時,只觸發一次EPOLLOUT事件。

  2)讀緩沖區不能一次存放客戶端發送過來的數據時,會多次觸發EPOLLOUT事件,直到讀完客服端發送過來的數據。

2 水平觸發(LT)模式結合非阻塞IO(nonblock)

在水平觸發模式下,采用非阻塞IO情況下,程序不會阻塞在epoll_wait()這里,而是只要客服端與服務端建立的連接可用,即使客服端沒有發送數據,EPOLLOUT事件也會一直觸發(寫緩沖區可寫)。

3 邊緣觸發(ET)模式結合阻塞IO(block)

采用邊緣觸發模式,在阻塞IO的情況下,客服端發送過來的數據小於讀緩沖區大小時,程序會一直阻塞在recv()函數(具體時間轉入sk_wait_data函數,直到客戶端有新數據發送過來或者達到超時才會跳出此函數)這里,導致程序無法進行下一步操作。

4 邊緣觸發(ET)模式結合非阻塞IO(nonblock)

邊緣觸發模式下采用非阻塞IO,具體地說,采用邊緣觸發模式必須使用非阻塞IO,不然讀緩沖區數據沒有讀滿就會一直阻塞在recv()函數這里;改成了非阻塞IO之后,當讀緩沖區數據沒有讀滿或者讀到的數據為空時,recv()函數就會返回errno代碼EAGAIN 或者 EWOULDBLOCK,進而做相應的后續處理。這里采用了循環讀取數據處理,因此在邊緣觸發模式下,只要客服端發送過來的數據沒有一次讀完,會循環讀取直到讀取結束。

具體代碼參見:https://gitlab.com/JC_peng/epoll_test


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM