大話 Select、Poll、Epoll

原文:https://cloud.tencent.com/developer/article/1005481javascript

提到select、poll、epoll相信你們都耳熟能詳了,三個都是IO多路複用的機制,能夠監視多個描述符的讀/寫等事件,一旦某個描述符就緒(通常是讀或者寫事件發生了),就可以將發生的事件通知給關心的應用程序去處理該事件。本質上,select、poll、epoll本質上都是同步I/O,相信你們都讀過Richard Stevens的經典書籍UNP(UNIX:registered: Network Programming),書中給出了5種IO模型:html

[1] blocking IO - 阻塞IO [2] nonblocking IO - 非阻塞IO [3] IO multiplexing - IO多路複用 [4] signal driven IO - 信號驅動IO [5] asynchronous IO - 異步IOjava

其中前面4種IO均可以歸類爲synchronous IO - 同步IO,在介紹select、poll、epoll以前,首先介紹一下這幾種IO模型,signal driven IO平時用的比較少,這裏就不介紹了。linux

1. IO - 同步、異步、阻塞、非阻塞

下面以network IO中的read讀操做爲切入點,來說述同步(synchronous) IO和異步(asynchronous) IO、阻塞(blocking) IO和非阻塞(non-blocking)IO的異同。通常狀況下,一次網絡IO讀操做會涉及兩個系統對象:(1) 用戶進程(線程)Process;(2)內核對象kernel,兩個處理階段:編程

[1] Waiting for the data to be ready - 等待數據準備好 [2] Copying the data from the kernel to the process - 將數據從內核空間的buffer拷貝到用戶空間進程的buffer 

IO模型的異同點就是區分在這兩個系統對象、兩個處理階段的不一樣上。數組

1.1 同步IO 之 Blocking IO

如上圖所示,用戶進程process在Blocking IO讀recvfrom操做的兩個階段都是等待的。在數據沒準備好的時候,process原地等待kernel準備數據。kernel準備好數據後,process繼續等待kernel將數據copy到本身的buffer。在kernel完成數據的copy後process纔會從recvfrom系統調用中返回。服務器

1.2 同步IO 之 NonBlocking IO

從圖中能夠看出,process在NonBlocking IO讀recvfrom操做的第一個階段是不會block等待的,若是kernel數據還沒準備好,那麼recvfrom會馬上返回一個EWOULDBLOCK錯誤。當kernel準備好數據後,進入處理的第二階段的時候,process會等待kernel將數據copy到本身的buffer,在kernel完成數據的copy後process纔會從recvfrom系統調用中返回。網絡

1.3 同步IO 之 IO multiplexing

IO多路複用,就是咱們熟知的select、poll、epoll模型。從圖上可見,在IO多路複用的時候,process在兩個處理階段都是block住等待的。初看好像IO多路複用沒什麼用,其實select、poll、epoll的優點在於能夠以較少的代價來同時監聽處理多個IO。數據結構

1.4 異步IO

從上圖看出,異步IO要求process在recvfrom操做的兩個處理階段上都不能等待,也就是process調用recvfrom後馬上返回,kernel自行去準備好數據並將數據從kernel的buffer中copy到process的buffer在通知process讀操做完成了,而後process在去處理。遺憾的是,linux的網絡IO中是不存在異步IO的,linux的網絡IO處理的第二階段老是阻塞等待數據copy完成的。真正意義上的網絡異步IO是Windows下的IOCP(IO完成端口)模型。併發

不少時候,咱們比較容易混淆non-blocking IO和asynchronous IO,認爲是同樣的。可是經過上圖,幾種IO模型的比較,會發現non-blocking IO和asynchronous IO的區別仍是很明顯的,non-blocking IO僅僅要求處理的第一階段不block便可,而asynchronous IO要求兩個階段都不能block住。

2 Linux的socket 事件wakeup callback機制

言歸正傳,在介紹select、poll、epoll前,有必要說說linux(2.6+)內核的事件wakeup callback機制,這是IO多路複用機制存在的本質。Linux經過socket睡眠隊列來管理全部等待socket的某個事件的process,同時經過wakeup機制來異步喚醒整個睡眠隊列上等待事件的process,通知process相關事件發生。一般狀況,socket的事件發生的時候,其會順序遍歷socket睡眠隊列上的每一個process節點,調用每一個process節點掛載的callback函數。在遍歷的過程當中,若是遇到某個節點是排他的,那麼就終止遍歷,整體上會涉及兩大邏輯:(1)睡眠等待邏輯;(2)喚醒邏輯。

(1)睡眠等待邏輯:涉及select、poll、epoll_wait的阻塞等待邏輯

[1]select、poll、epoll_wait陷入內核,判斷監控的socket是否有關心的事件發生了,若是沒,則爲當前process構建一個wait_entry節點,而後插入到監控socket的sleep_list [2]進入循環的schedule直到關心的事件發生了 [3]關心的事件發生後,將當前process的wait_entry節點從socket的sleep_list中刪除。 

(2)喚醒邏輯:

[1]socket的事件發生了,而後socket順序遍歷其睡眠隊列,依次調用每一個wait_entry節點的callback函數 [2]直到完成隊列的遍歷或遇到某個wait_entry節點是排他的才中止。 [3]通常狀況下callback包含兩個邏輯:1.wait_entry自定義的私有邏輯;2.喚醒的公共邏輯,主要用於將該wait_entry的process放入CPU的就緒隊列,讓CPU隨後能夠調度其執行。 

下面就上面的兩大邏輯,分別闡述select、poll、epoll的異同,爲何epoll可以比select、poll高效。

3 大話Select—1024

在一個高性能的網絡服務上,大多狀況下一個服務進程(線程)process須要同時處理多個socket,咱們須要公平對待全部socket,對於read而言,那個socket有數據可讀,process就去讀取該socket的數據來處理。因而對於read,一個樸素的需求就是關心的N個socket是否有數據」可讀」,也就是咱們期待」可讀」事件的通知,而不是盲目地對每一個socket調用recv/recvfrom來嘗試接收數據。咱們應該block在等待事件的發生上,這個事件簡單點就是」關心的N個socket中一個或多個socket有數據可讀了」,當block解除的時候,就意味着,咱們必定能夠找到一個或多個socket上有可讀的數據。另外一方面,根據上面的socket wakeup callback機制,咱們不知道何時,哪一個socket會有讀事件發生,因而,process須要同時插入到這N個socket的sleep_list上等待任意一個socket可讀事件發生而被喚醒,當時process被喚醒的時候,其callback裏面應該有個邏輯去檢查具體那些socket可讀了。

因而,select的多路複用邏輯就清晰了,select爲每一個socket引入一個poll邏輯,該poll邏輯用於收集socket發生的事件,對於可讀事件來講,簡單僞碼以下:

poll() { //其餘邏輯 if (recieve queque is not empty) { sk_event |= POLL_IN; } //其餘邏輯 } 

接下來就到select的邏輯了,下面是select的函數原型:5個參數,後面4個參數都是in/out類型(值可能會被修改返回)

int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout); 

當用戶process調用select的時候,select會將須要監控的readfds集合拷貝到內核空間(假設監控的僅僅是socket可讀),而後遍歷本身監控的socket sk,挨個調用sk的poll邏輯以便檢查該sk是否有可讀事件,遍歷完全部的sk後,若是沒有任何一個sk可讀,那麼select會調用schedule_timeout進入schedule循環,使得process進入睡眠。若是在timeout時間內某個sk上有數據可讀了,或者等待timeout了,則調用select的process會被喚醒,接下來select就是遍歷監控的sk集合,挨個收集可讀事件並返回給用戶了,相應的僞碼以下:

for (sk in readfds) { sk_event.evt = sk.poll(); sk_event.sk = sk; ret_event_for_process; } 

經過上面的select邏輯過程分析,相信你們都意識到,select存在兩個問題:

[1] 被監控的fds須要從用戶空間拷貝到內核空間 爲了減小數據拷貝帶來的性能損壞,內核對被監控的fds集合大小作了限制,而且這個是經過宏控制的,大小不可改變(限制爲1024)。 [2] 被監控的fds集合中,只要有一個有數據可讀,整個socket集合就會被遍歷一次調用sk的poll函數收集可讀事件 因爲當初的需求是樸素,僅僅關心是否有數據可讀這樣一個事件,當事件通知來的時候,因爲數據的到來是異步的,咱們不知道事件來的時候,有多少個被監控的socket有數據可讀了,因而,只能挨個遍歷每一個socket來收集可讀事件。 

到這裏,咱們有三個問題須要解決:

(1)被監控的fds集合限制爲1024,1024過小了,咱們但願可以有個比較大的可監控fds集合 (2)fds集合須要從用戶空間拷貝到內核空間的問題,咱們但願不須要拷貝 (3)當被監控的fds中某些有數據可讀的時候,咱們但願通知更加精細一點,就是咱們但願可以從通知中獲得有可讀事件的fds列表,而不是須要遍歷整個fds來收集。

4 大話poll—雞肋

select遺留的三個問題中,問題(1)是用法限制問題,問題(2)和(3)則是性能問題。poll和select很是類似,poll並沒着手解決性能問題,poll只是解決了select的問題(1)fds集合大小1024限制問題。下面是poll的函數原型,poll改變了fds集合的描述方式,使用了pollfd結構而不是select的fd_set結構,使得poll支持的fds集合限制遠大於select的1024。poll雖然解決了fds集合大小1024的限制問題,可是,它並沒改變大量描述符數組被總體複製於用戶態和內核態的地址空間之間,以及個別描述符就緒觸發總體描述符集合的遍歷的低效問題。poll隨着監控的socket集合的增長性能線性降低,poll不適合用於大併發場景。

int poll(struct pollfd *fds, nfds_t nfds, int timeout); 

5 大話epoll—終極武功

select遺留的三個問題,問題(1)是比較好解決,poll簡單兩三下就解決掉了,可是poll的解決有點雞肋。要解決問題(2)和(3)彷佛比較棘手,要怎麼解決呢?咱們知道,在計算機行業中,有兩種解決問題的思想:

[1] 計算機科學領域的任何問題, 均可以經過添加一箇中間層來解決 [2] 變集中(中央)處理爲分散(分佈式)處理 

下面,咱們看看,epoll在解決select的遺留問題(2)和(3)的時候,怎麼運用這兩個思想的。

5.1 fds集合拷貝問題的解決

對於IO多路複用,有兩件事是必需要作的(對於監控可讀事件而言):1. 準備好須要監控的fds集合;2. 探測並返回fds集合中哪些fd可讀了。細看select或poll的函數原型,咱們會發現,每次調用select或poll都在重複地準備(集中處理)整個須要監控的fds集合。然而對於頻繁調用的select或poll而言,fds集合的變化頻率要低得多,咱們不必每次都從新準備(集中處理)整個fds集合。

因而,epoll引入了epoll_ctl系統調用,將高頻調用的epoll_wait和低頻的epoll_ctl隔離開。同時,epoll_ctl經過(EPOLL_CTL_ADD、EPOLL_CTL_MOD、EPOLL_CTL_DEL)三個操做來分散對須要監控的fds集合的修改,作到了有變化才變動,將select或poll高頻、大塊內存拷貝(集中處理)變成epoll_ctl的低頻、小塊內存的拷貝(分散處理),避免了大量的內存拷貝。同時,對於高頻epoll_wait的可讀就緒的fd集合返回的拷貝問題,epoll經過內核與用戶空間mmap(內存映射)同一塊內存來解決。mmap將用戶空間的一塊地址和內核空間的一塊地址同時映射到相同的一塊物理內存地址(無論是用戶空間仍是內核空間都是虛擬地址,最終要經過地址映射映射到物理地址),使得這塊物理內存對內核和對用戶都可見,減小用戶態和內核態之間的數據交換。

另外,epoll經過epoll_ctl來對監控的fds集合來進行增、刪、改,那麼必須涉及到fd的快速查找問題,因而,一個低時間複雜度的增、刪、改、查的數據結構來組織被監控的fds集合是必不可少的了。在linux 2.6.8以前的內核,epoll使用hash來組織fds集合,因而在建立epoll fd的時候,epoll須要初始化hash的大小。因而epoll_create(int size)有一個參數size,以便內核根據size的大小來分配hash的大小。在linux 2.6.8之後的內核中,epoll使用紅黑樹來組織監控的fds集合,因而epoll_create(int size)的參數size實際上已經沒有意義了。

5.2 按需遍歷就緒的fds集合

經過上面的socket的睡眠隊列喚醒邏輯咱們知道,socket喚醒睡眠在其睡眠隊列的wait_entry(process)的時候會調用wait_entry的回調函數callback,而且,咱們能夠在callback中作任何事情。爲了作到只遍歷就緒的fd,咱們須要有個地方來組織那些已經就緒的fd。爲此,epoll引入了一箇中間層,一個雙向鏈表(ready_list),一個單獨的睡眠隊列(single_epoll_wait_list),而且,與select或poll不一樣的是,epoll的process不須要同時插入到多路複用的socket集合的全部睡眠隊列中,相反process只是插入到中間層的epoll的單獨睡眠隊列中,process睡眠在epoll的單獨隊列上,等待事件的發生。同時,引入一箇中間的wait_entry_sk,它與某個socket sk密切相關,wait_entry_sk睡眠在sk的睡眠隊列上,其callback函數邏輯是將當前sk排入到epoll的ready_list中,並喚醒epoll的single_epoll_wait_list。而single_epoll_wait_list上睡眠的process的回調函數就明朗了:遍歷ready_list上的全部sk,挨個調用sk的poll函數收集事件,而後喚醒process從epoll_wait返回。 因而,整個過來能夠分爲如下幾個邏輯:

(1)epoll_ctl EPOLL_CTL_ADD邏輯

[1] 構建睡眠實體wait_entry_sk,將當前socket sk關聯給wait_entry_sk,並設置wait_entry_sk的回調函數爲epoll_callback_sk [2] 將wait_entry_sk排入當前socket sk的睡眠隊列上 

回調函數epoll_callback_sk的邏輯以下:

[1] 將以前關聯的sk排入epoll的ready_list [2] 而後喚醒epoll的單獨睡眠隊列single_epoll_wait_list 

(2)epoll_wait邏輯

[1] 構建睡眠實體wait_entry_proc,將當前process關聯給wait_entry_proc,並設置回調函數爲epoll_callback_proc [2] 判斷epoll的ready_list是否爲空,若是爲空,則將wait_entry_proc排入epoll的single_epoll_wait_list中,隨後進入schedule循環,這會致使調用epoll_wait的process睡眠。 [3] wait_entry_proc被事件喚醒或超時醒來,wait_entry_proc將被從single_epoll_wait_list移除掉,而後wait_entry_proc執行回調函數epoll_callback_proc 

回調函數epoll_callback_proc的邏輯以下:

[1] 遍歷epoll的ready_list,挨個調用每一個sk的poll邏輯收集發生的事件,對於監控可讀事件而已,ready_list上的每一個sk都是有數據可讀的,這裏的遍歷必要的(不一樣於select/poll的遍歷,它無論有沒數據可讀都須要遍歷一些來判斷,這樣就作了不少無用功。) [2] 將每一個sk收集到的事件,經過epoll_wait傳入的events數組回傳並喚醒相應的process。 

(3)epoll喚醒邏輯 整個epoll的協議棧喚醒邏輯以下(對於可讀事件而言):

[1] 協議數據包到達網卡並被排入socket sk的接收隊列 [2] 睡眠在sk的睡眠隊列wait_entry被喚醒,wait_entry_sk的回調函數epoll_callback_sk被執行 [3] epoll_callback_sk將當前sk插入epoll的ready_list中 [4] 喚醒睡眠在epoll的單獨睡眠隊列single_epoll_wait_list的wait_entry,wait_entry_proc被喚醒執行回調函數epoll_callback_proc [5] 遍歷epoll的ready_list,挨個調用每一個sk的poll邏輯收集發生的事件 [6] 將每一個sk收集到的事件,經過epoll_wait傳入的events數組回傳並喚醒相應的process。 

epoll巧妙的引入一箇中間層解決了大量監控socket的無效遍歷問題。細心的同窗會發現,epoll在中間層上爲每一個監控的socket準備了一個單獨的回調函數epoll_callback_sk,而對於select/poll,全部的socket都公用一個相同的回調函數。正是這個單獨的回調epoll_callback_sk使得每一個socket都能單獨處理自身,當本身就緒的時候將自身socket掛入epoll的ready_list。同時,epoll引入了一個睡眠隊列single_epoll_wait_list,分割了兩類睡眠等待。process再也不睡眠在全部的socket的睡眠隊列上,而是睡眠在epoll的睡眠隊列上,在等待」任意一個socket可讀就緒」事件。而中間wait_entry_sk則代替process睡眠在具體的socket上,當socket就緒的時候,它就能夠處理自身了。

5.3 ET(Edge Triggered 邊沿觸發) vs LT(Level Triggered 水平觸發)

5.3.1 ET vs LT - 概念

說到Epoll就不能不說說Epoll事件的兩種模式了,下面是兩個模式的基本概念

  • Edge Triggered (ET) 邊沿觸發

.socket的接收緩衝區狀態變化時觸發讀事件,即空的接收緩衝區剛接收到數據時觸發讀事件

.socket的發送緩衝區狀態變化時觸發寫事件,即滿的緩衝區剛空出空間時觸發讀事件

僅在緩衝區狀態變化時觸發事件,好比數據緩衝去從無到有的時候(不可讀-可讀)

  • Level Triggered (LT) 水平觸發

.socket接收緩衝區不爲空,有數據可讀,則讀事件一直觸發

.socket發送緩衝區不滿能夠繼續寫入數據,則寫事件一直觸發

符合思惟習慣,epoll_wait返回的事件就是socket的狀態

一般狀況下,你們都認爲ET模式更爲高效,其實是不是呢?下面咱們來講說兩種模式的本質:

咱們來回顧一下,5.2節(3)epoll喚醒邏輯 的第五個步驟

[5] 遍歷epoll的ready_list,挨個調用每一個sk的poll邏輯收集發生的事件 

你們是否是有個疑問呢:掛在ready_list上的sk何時會被移除掉呢?其實,sk從ready_list移除的時機正是區分兩種事件模式的本質。由於,經過上面的介紹,咱們知道ready_list是否爲空是epoll_wait是否返回的條件。因而,在兩種事件模式下,步驟5以下:

對於Edge Triggered (ET) 邊沿觸發:

[5] 遍歷epoll的ready_list,將sk從ready_list中移除,而後調用該sk的poll邏輯收集發生的事件 

對於Level Triggered (LT) 水平觸發:

[5.1] 遍歷epoll的ready_list,將sk從ready_list中移除,而後調用該sk的poll邏輯收集發生的事件 [5.2] 若是該sk的poll函數返回了關心的事件(對於可讀事件來講,就是POLL_IN事件),那麼該sk被從新加入到epoll的ready_list中。 

對於可讀事件而言,在ET模式下,若是某個socket有新的數據到達,那麼該sk就會被排入epoll的ready_list,從而epoll_wait就必定能收到可讀事件的通知(調用sk的poll邏輯必定能收集到可讀事件)。因而,咱們一般理解的緩衝區狀態變化(從無到有)的理解是不許確的,準確的理解應該是是否有新的數據達到緩衝區。

而在LT模式下,某個sk被探測到有數據可讀,那麼該sk會被從新加入到read_list,那麼在該sk的數據被所有取走前,下次調用epoll_wait就必定可以收到該sk的可讀事件(調用sk的poll邏輯必定能收集到可讀事件),從而epoll_wait就能返回。

5.3.2 ET vs LT - 性能

經過上面的概念介紹,咱們知道對於可讀事件而言,LT比ET多了兩個操做:(1)對ready_list的遍歷的時候,對於收集到可讀事件的sk會從新放入ready_list;(2)下次epoll_wait的時候會再次遍歷上次從新放入的sk,若是sk自己沒有數據可讀了,那麼此次遍歷就變得多餘了。 在服務端有海量活躍socket的時候,LT模式下,epoll_wait返回的時候,會有海量的socket sk從新放入ready_list。若是,用戶在第一次epoll_wait返回的時候,將有數據的socket都處理掉了,那麼下次epoll_wait的時候,上次epoll_wait從新入ready_list的sk被再次遍歷就有點多餘,這個時候LT確實會帶來一些性能損失。然而,實際上會存在不少多餘的遍歷麼?

先不說第一次epoll_wait返回的時候,用戶進程可否都將有數據返回的socket處理掉。在用戶處理的過程當中,若是該socket有新的數據上來,那麼協議棧發現sk已經在ready_list中了,那麼就不須要再次放入ready_list,也就是在LT模式下,對該sk的再次遍歷不是多餘的,是有效的。同時,咱們迴歸epoll高效的場景在於,服務器有海量socket,可是活躍socket較少的狀況下才會體現出epoll的高效、高性能。所以,在實際的應用場合,絕大多數狀況下,ET模式在性能上並不會比LT模式具備壓倒性的優點,至少,目前尚未實際應用場合的測試表面ET比LT性能更好。

5.3.3 ET vs LT - 複雜度

咱們知道,對於可讀事件而言,在阻塞模式下,是沒法識別隊列空的事件的,而且,事件通知機制,僅僅是通知有數據,並不會通知有多少數據。因而,在阻塞模式下,在epoll_wait返回的時候,咱們對某個socket_fd調用recv或read讀取並返回了一些數據的時候,咱們不能再次直接調用recv或read,由於,若是socket_fd已經無數據可讀的時候,進程就會阻塞在該socket_fd的recv或read調用上,這樣就影響了IO多路複用的邏輯(咱們但願是阻塞在全部被監控socket的epoll_wait調用上,而不是單獨某個socket_fd上),形成其餘socket餓死,即便有數據來了,也沒法處理。

接下來,咱們只能再次調用epoll_wait來探測一些socket_fd,看是否還有數據可讀。在LT模式下,若是socket_fd還有數據可讀,那麼epoll_wait就必定可以返回,接着,咱們就能夠對該socket_fd調用recv或read讀取數據。然而,在ET模式下,儘管socket_fd仍是數據可讀,可是若是沒有新的數據上來,那麼epoll_wait是不會通知可讀事件的。這個時候,epoll_wait阻塞住了,這下子坑爹了,明明有數據你不處理,非要等新的數據來了在處理,那麼咱們就死扛咯,看誰先忍不住。

等等,在阻塞模式下,不是不能用ET的麼?是的,正是由於有這樣的缺點,ET強制須要在非阻塞模式下使用。在ET模式下,epoll_wait返回socket_fd有數據可讀,咱們必需要讀完全部數據才能離開。由於,若是不讀完,epoll不會在通知你了,雖然有新的數據到來的時候,會再次通知,可是咱們並不知道新數據會不會來,以及何時會來。因爲在阻塞模式下,咱們是沒法經過recv/read來探測空數據事件,因而,咱們必須採用非阻塞模式,一直read直到EAGAIN。所以,ET要求socket_fd非阻塞也就不難理解了。

另外,epoll_wait本來的語意是:監控並探測socket是否有數據可讀(對於讀事件而言)。LT模式保留了其本來的語意,只要socket還有數據可讀,它就能不斷反饋,因而,咱們想何時讀取處理均可以,咱們永遠有再次poll的機會去探測是否有數據能夠處理,這樣帶來了編程上的很大方便,不容易死鎖形成某些socket餓死。相反,ET模式修改了epoll_wait本來的語意,變成了:監控並探測socket是否有新的數據可讀。

因而,在epoll_wait返回socket_fd可讀的時候,咱們須要當心處理,要否則會形成死鎖和socket餓死現象。典型如listen_fd返回可讀的時候,咱們須要不斷的accept直到EAGAIN。假設同時有三個請求到達,epoll_wait返回listen_fd可讀,這個時候,若是僅僅accept一次拿走一個請求去處理,那麼就會留下兩個請求,若是這個時候一直沒有新的請求到達,那麼再次調用epoll_wait是不會通知listen_fd可讀的,因而epoll_wait只能睡眠到超時才返回,遺留下來的兩個請求一直得不處處理,處於餓死狀態。

5.3.4 ET vs LT - 總結

最後總結一下,ET和LT模式下epoll_wait返回的條件

  • ET - 對於讀操做

[1] 當接收緩衝buffer內待讀數據增長的時候時候(由空變爲不空的時候、或者有新的數據進入緩衝buffer)

[2] 調用epoll_ctl(EPOLL_CTL_MOD)來改變socket_fd的監控事件,也就是從新mod socket_fd的EPOLLIN事件,而且接收緩衝buffer內還有數據沒讀取。(這裏不能是EPOLL_CTL_ADD的緣由是,epoll不容許重複ADD的,除非先DEL了,再ADD) 由於epoll_ctl(ADD或MOD)會調用sk的poll邏輯來檢查是否有關心的事件,若是有,就會將該sk加入到epoll的ready_list中,下次調用epoll_wait的時候,就會遍歷到該sk,而後會從新收集到關心的事件返回。

  • ET - 對於寫操做

[1] 發送緩衝buffer內待發送的數據減小的時候(由滿狀態變爲不滿狀態的時候、或者有部分數據被髮出去的時候) [2] 調用epoll_ctl(EPOLL_CTL_MOD)來改變socket_fd的監控事件,也就是從新mod socket_fd的EPOLLOUT事件,而且發送緩衝buffer還沒滿的時候。

  • LT - 對於讀操做 LT就簡單多了,惟一的條件就是,接收緩衝buffer內有可讀數據的時候
  • LT - 對於寫操做 LT就簡單多了,惟一的條件就是,發送緩衝buffer還沒滿的時候

在絕大多少狀況下,ET模式並不會比LT模式更爲高效,同時,ET模式帶來了很差理解的語意,這樣容易形成編程上面的複雜邏輯和坑點。所以,建議仍是採用LT模式來編程更爲舒爽。

參考資料

http://blog.chinaunix.net/uid-28541347-id-4238524.html

http://blog.csdn.net/historyasamirror/article/details/5778378

http://blog.csdn.net/dog250/article/details/50528373

http://blog.csdn.net/zhangskd/article/details/16986931

相關文章
相關標籤/搜索