什么是 epoll
epoll 是什么?按照 man 手冊的說法:是為處理大批量句柄而作了改進的 poll 。當然,這不是 2.6 內核才有的,它是在 2.5.44 內核中被引進的 (epoll(4) is a new API introduced in Linux kernel 2.5.44) ,它幾乎具備了之前所說的一切優點,被公認為 Linux2.6 下性能最好的多路 I/O 就緒通知方法。
?
epoll 的相關系統調用
epoll 只有 epoll_create,epoll_ctl,epoll_wait 3 個系統調用。
?
1. int epoll_create(int size);
創建一個 epoll 的句柄。自從 linux2.6.8 之后, size 參數是被忽略的 。需要注意的是,當創建好 epoll 句柄后,它就是會占用一個 fd 值,在 linux 下如果查看 /proc/ 進程 id/fd/ ,是能夠看到這個 fd 的,所以在使用完 epoll 后,必須調用 close() 關閉,否則可能導致 fd 被耗盡。
?
2. int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
epoll 的事件注冊函數 ,它不同于 select() 是在監聽事件時告訴內核要監聽什么類型的事件,而是在這里先注冊要監聽的事件類型。
第一個參數是 epoll_create() 的返回值。
第二個參數表示動作,用三個宏來表示:
EPOLL_CTL_ADD :注冊新的 fd 到 epfd 中;
EPOLL_CTL_MOD :修改已經注冊的 fd 的監聽事件;
EPOLL_CTL_DEL :從 epfd 中刪除一個 fd ;
?
第三個參數是需要監聽的 fd 。
第四個參數是告訴內核需要監聽什么事, struct epoll_event 結構如下:
[cpp] view plain
copy ?? ?? typedef ?union ?epoll_data?{??????void ?*ptr;?? ????int ?fd;?? ????__uint32_t?u32;?? ????__uint64_t?u64;?? }?epoll_data_t;?? ??? struct ?epoll_event?{??????__uint32_t?events;??? ????epoll_data_t?data;??? };??
events 可以是以下幾個宏的集合:
EPOLLIN? :表示對應的文件描述符可以讀(包括對端 SOCKET 正常關閉);
EPOLLOUT :表示對應的文件描述符可以寫;
EPOLLPRI :表示對應的文件描述符有緊急的數據可讀(這里應該表示有帶外數據到來);
EPOLLERR :表示對應的文件描述符發生錯誤;
EPOLLHUP :表示對應的文件描述符被掛斷;
EPOLLET : ? 將 EPOLL 設為邊緣觸發 (Edge Triggered) 模式,這是相對于水平觸發 (Level Triggered) 來說的。
EPOLLONESHOT :只監聽一次事件,當監聽完這次事件之后,如果還需要繼續監聽這個 socket 的話,需要再次把這個 socket 加入到 EPOLL 隊列里
3. int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
收集在epoll監控的事件中已經發送的事件。參數 events是分配好的epoll_event結構體數組,epoll將會把發生的事件賦值到events數組中(events不可以是空指針,內核只負責把數據復制到這個events數組中,不會去幫助我們在用戶態中分配內存) 。 maxevents 告之內核這個 events 有多大,這個 ?maxevents 的值不能大于創建 epoll_create() 時的 size ,參數 timeout 是超時時間(毫秒, 0 會立即返回, -1 將不確定,也有說法說是永久阻塞)。如果函數調用成功,返回對應 I/O 上已準備好的文件描述符數目,如返回 0 表示已超時。
?
epoll 工作原理
epoll 同樣只告知那些就緒的文件描述符,而且當我們調用 epoll_wait() 獲得就緒文件描述符時,返回的不是實際的描述符,而是一個代表就緒描述符數量的值,你只需要去 epoll 指定的一個數組中依次取得相應數量的文件描述符即可,這里也使用了內存映射( mmap )技術 ,這樣便徹底省掉了這些文件描述符在系統調用時復制的開銷。
?
另一個本質的改進在于 epoll 采用基于事件的就緒通知方式 。在 select/poll 中,進程只有在調用一定的方法后,內核才對所有監視的文件描述符進行掃描,而 epoll 事先通過 epoll_ctl() 來注冊一個文件描述符,一旦基于某個文件描述符就緒時,內核會采用類似 callback 的回調機制,迅速激活這個文件描述符,當進程調用 epoll_wait() 時便得到通知。
?
Epoll 的 2 種工作方式 - 水平觸發( LT )和邊緣觸發( ET )
假如有這樣一個例子:
1. 我們已經把一個用來從管道中讀取數據的文件句柄(RFD)添加到epoll描述符
2. 這個時候從管道的另一端被寫入了2KB的數據
3. 調用epoll_wait(2),并且它會返回RFD,說明它已經準備好讀取操作
4. 然后我們讀取了1KB的數據
5. 調用epoll_wait(2)......
Edge Triggered 工作模式:
如果我們在第1步將RFD添加到epoll描述符的時候使用了EPOLLET標志,那么在第5步調用epoll_wait(2)之后將有可能會掛起,因為剩余的數據還存在于文件的輸入緩沖區內,而且數據發出端還在等待一個針對已經發出數據的反饋信息。只有在監視的文件句柄上發生了某個事件的時候 ET 工作模式才會匯報事件。因此在第5步的時候,調用者可能會放棄等待仍在存在于文件輸入緩沖區內的剩余數據。在上面的例子中,會有一個事件產生在RFD句柄上,因為在第2步執行了一個寫操作,然后,事件將會在第3步被銷毀。因為第4步的讀取操作沒有讀空文件輸入緩沖區內的數據,因此我們在第5步調用 epoll_wait(2)完成后,是否掛起是不確定的。epoll工作在ET模式的時候,必須使用非阻塞套接口,以避免由于一個文件句柄的阻塞讀/阻塞寫操作把處理多個文件描述符的任務餓死。最好以下面的方式調用ET模式的epoll接口,在后面會介紹避免可能的缺陷。
? ?i ? ?基于非阻塞文件句柄
? ?ii ? 只有當read(2)或者write(2)返回EAGAIN時才需要掛起,等待。但這并不是說每次read()時都需要循環讀,直到讀到產生一個EAGAIN才認為此次事件處理完成,當read()返回的讀到的數據長度小于請求的數據長度時,就可以確定此時緩沖中已沒有數據了,也就可以認為此事讀事件已處理完成。
Level Triggered 工作模式
相反的,以LT方式調用epoll接口的時候,它就相當于一個速度比較快的poll(2),并且無論后面的數據是否被使用,因此他們具有同樣的職能。因為即使使用ET模式的epoll,在收到多個chunk的數據的時候仍然會產生多個事件。調用者可以設定EPOLLONESHOT標志,在 epoll_wait(2)收到事件后epoll會與事件關聯的文件句柄從epoll描述符中禁止掉。因此當EPOLLONESHOT設定后,使用帶有 EPOLL_CTL_MOD標志的epoll_ctl(2)處理文件句柄就成為調用者必須作的事情。
LT(level triggered) 是epoll缺省的工作方式 ,并且同時支持block 和no-block socket. 在這種做法中,內核告訴你一個文件描述符是否就緒了,然后你可以對這個就緒的fd 進行IO 操作。如果你不作任何操作,內核還是會繼續通知你?的,所以,這種模式編程出錯誤可能性要小一點。傳統的select/poll 都是這種模型的代表.
?
ET (edge-triggered) 是高速工作方式,只支持no-block socket,它效率要比LT更高 。ET與LT的區別在于, 當一個新的事件到來時,ET模式下當然可以從epoll_wait調用中獲取到這個事件,可是如果這次沒有把這個事件對應的套接字緩沖區處理完,在這個套接字中沒有新的事件再次到來時,在ET模式下是無法再次從epoll_wait調用中獲取這個事件的。而LT模式正好相反,只要一個事件對應的套接字緩沖區還有數據,就總能從epoll_wait中獲取這個事件。
因此,LT模式下開發基于epoll的應用要簡單些,不太容易出錯。而在ET模式下事件發生時,如果沒有徹底地將緩沖區數據處理完,則會導致緩沖區中的用戶請求得不到響應。
圖示說明:
Nginx默認采用ET模式來使用epoll。
?
epoll 的優點:
1. 支持一個進程打開大數目的 socket 描述符 (FD)
??? select? 最不能忍受的是一個進程所打開的 FD 是有一定限制的,由 FD_SETSIZE 設置,默認值是 2048 。對于那些需要支持的上萬連接數目的 IM 服務器來說顯然太少了。這時候你一是可以選擇修改這個宏然后重新編譯內核,不過資料也同時指出這樣會帶來網絡效率的下降,二是可以選擇多進程的解決方案 ( 傳統的 ?Apache 方案 ) ,不過雖然 linux 上面創建進程的代價比較小,但仍舊是不可忽視的,加上進程間數據同步遠比不上線程間同步的高效,所以也不是一種完美的方案。不過 ?epoll 則沒有這個限制,它所支持的 FD 上限是最大可以打開文件的數目 ,這個數字一般遠大于 2048, 舉個例子 , 在 1GB 內存的機器上大約是 10 萬左右,具體數目可以 cat /proc/sys/fs/file-max 察看 , 一般來說這個數目和系統內存關系很大。
?
2.IO 效率不隨 FD 數目增加而線性下降
???? 傳統的 select/poll 另一個致命弱點就是當你擁有一個很大的 socket 集合,不過由于網絡延時,任一時間只有部分的 socket 是 " 活躍 " 的,但是 select/poll 每次調用都會線性掃描全部的集合,導致效率呈現線性下降。但是 epoll 不存在這個問題,它只會對 " 活躍 " 的 socket 進行操作 --- 這是因為在內核實現中 epoll 是根據每個 fd 上面的 callback 函數實現的。那么,只有 " 活躍 " 的 socket 才會主動的去調用 ?callback 函數,其他 idle 狀態 socket 則不會,在這點上, epoll 實現了一個 " 偽 "AIO , 因為這時候推動力在 os 內核。在一些 ?benchmark 中,如果所有的 socket 基本上都是活躍的 --- 比如一個高速 LAN 環境, epoll 并不比 select/poll 有什么效率,相反,如果過多使用 epoll_ctl, 效率相比還有稍微的下降。但是一旦使用 idle connections 模擬 WAN 環境 ,epoll 的效率就遠在 select/poll 之上了。
?
3. 使用 mmap 加速內核與用戶空間的消息傳遞
???? 這點實際上涉及到 epoll 的具體實現了。無論是 select,poll 還是 epoll 都需要內核把 FD 消息通知給用戶空間 ,如何避免不必要的內存拷貝就很重要,在這點上, epoll 是通過內核于用戶空間 mmap 同一塊內存實現的。而如果你想我一樣從 2.5 內核就關注 epoll 的話,一定不會忘記手工 ?mmap 這一步的。
?
4. 內核微調
這一點其實不算 epoll 的優點了,而是整個 linux 平臺的優點。也許你可以懷疑 linux 平臺,但是你無法回避 linux 平臺賦予你微調內核的能力。比如,內核 TCP/IP 協議棧使用內存池管理 sk_buff 結構,那么可以在運行時期動態調整這個內存 pool(skb_head_pool) 的大小 ---? 通過 echo XXXX>/proc/sys/net/core/hot_list_length 完成。再比如 listen 函數的第 2 個參數 (TCP 完成 3 次握手的數據包隊列長度 ) ,也可以根據你平臺內存大小動態調整。更甚至在一個數據包面數目巨大但同時每個數據包本身大小卻很小的特殊系統上嘗試最新的 NAPI 網卡驅動架構。
?
linux 下 epoll 如何實現高效處理百萬句柄的
開發高性能網絡程序時,windows 開發者們言必稱iocp ,linux 開發者們則言必稱epoll 。大家都明白epoll 是一種IO 多路復用技術,可以非常高效的處理數以百萬計的socket 句柄,比起以前的select 和poll 效率高大發了。我們用起epoll 來都感覺挺爽,確實快,那么,它到底為什么可以高速處理這么多并發連接呢?
?
使用起來很清晰,首先要調用epoll_create 建立一個epoll 對象。參數size 是內核保證能夠正確處理的最大句柄數,多于這個最大數時內核可不保證效果。
?
epoll_ctl 可以操作上面建立的epoll ,例如,將剛建立的socket 加入到epoll 中讓其監控,或者把?epoll 正在監控的某個socket 句柄移出epoll ,不再監控它等等。
?
epoll_wait 在調用時,在給定的timeout 時間內,當在監控的所有句柄中有事件發生時,就返回用戶態的進程。
?
從上面的調用方式就可以看到epoll 比select/poll 的優越之處:因為后者每次調用時都要傳遞你所要監控的所有 socket 給 select/poll 系統調用,這意味著需要將用戶態的 socket 列表 copy 到內核態, 如果以萬計的句柄會導致每次都要copy 幾十幾百KB 的內存到內核態,非常低效。而我們調用epoll_wait 時就相當于以往調用select/poll ,但是這時卻不用傳遞 socket 句柄給內核,因為內核已經在 epoll_ctl 中拿到了要監控的句柄列表。
?
所以,實際上在你調用epoll_create 后,內核就已經在內核態開始準備幫你存儲要監控的句柄了,每次調用epoll_ctl 只是在往內核的數據結構里塞入新的socket 句柄。
?當一個進程調用epoll_creaqte方法時,Linux內核會創建一個eventpoll結構體,這個結構體中有兩個成員與epoll的使用方式密切相關:
[cpp] view plain
copy ? ? ? ? ? ? ? ? ?? ?? ?175struct?eventpoll?{?? ?? ?176?????????? ?? ?177????????spinlock_t?lock;?? ?? ?178?? ?? ?179????????? ? ? ? ? ? ? ? ? ? ?? ?? ?185????????struct ?mutex?mtx;?? ?? ?186?? ?? ?187?????????? ?? ?188????????wait_queue_head_t?wq;?? ?? ?189?? ?? ?190?????????? ?? ?191????????wait_queue_head_t?poll_wait;?? ?? ?192?? ?? ?193?????????? ?? ?194????????struct ?list_head?rdllist;?? ?? ?195?? ?? ?196?????????? ?? ?197????????struct ?rb_root?rbr;?? ?198?? ?199????????? ? ? ? ?? ?204????????struct ?epitem?*ovflist;?? ?205?? ?206?????????? ?207????????struct ?wakeup_source?*ws;?? ?208?? ?209?????????? ?210????????struct ?user_struct?*user;?? ?211?? ?212????????struct ?file?*file;?? ?213?? ?214?????????? ?215????????int ?visited;?? ?216????????struct ?list_head?visited_list_link;?? ?217};??
每一個epoll對象都有一個獨立的eventpoll結構體,這個結構體會在內核空間中創造獨立的內存,用于存儲使用epoll_ctl方法向epoll對象中添加進來的事件。這樣,重復的事件就可以通過紅黑樹而高效的識別出來。
在epoll中,對于每一個事件都會建立一個epitem結構體:
[cpp] view plain
copy ? ? ? ? ? ?? ?135struct?epitem?{?? ?136?????????? ?137????????struct ?rb_node?rbn;?? ?138?? ?139?????????? ?140????????struct ?list_head?rdllink;?? ?141?? ?142????????? ? ? ?? ?146????????struct ?epitem?*next;?? ?147?? ?148?????????? ?149????????struct ?epoll_filefd?ffd;?? ?150?? ?151?????????? ?152????????int ?nwait;?? ?153?? ?154?????????? ?155????????struct ?list_head?pwqlist;?? ?156?? ?157?????????? ?158????????struct ?eventpoll?*ep;?? ?159?? ?160?????????? ?161????????struct ?list_head?fllink;?? ?162?? ?163?????????? ?164????????struct ?wakeup_source?__rcu?*ws;?? ?165?? ?166?????????? ?167????????struct ?epoll_event?event;?? ?168};??
此外,epoll還維護了一個雙鏈表,用戶存儲發生的事件。 當epoll_wait 調用時,僅僅觀察這個list 鏈表里有沒有數據即eptime項即可。有數據就返回,沒有數據就sleep ,等到timeout 時間到后即使鏈表沒數據也返回。所以,epoll_wait 非常高效。
?
而且,通常情況下即使我們要監控百萬計的句柄,大多一次也只返回很少量的準備就緒句柄而已,所以,epoll_wait 僅需要從內核態copy 少量的句柄到用戶態而已,如何能不高效?!
?
那么,這個準備就緒list 鏈表是怎么維護的呢?當我們執行 epoll_ctl 時,除了把socket 放到epoll 文件系統里file 對象對應的紅黑樹上之外,還會給內核中斷處理程序注冊一個回調函數,告訴內核,如果這個句柄的中斷到了,就把它放到準備就緒list 鏈表里。 所以,當一個socket 上有數據到了,內核在把網卡上的數據copy 到內核中后就來把socket 插入到準備就緒鏈表里了。
?
如此,一顆紅黑樹,一張準備就緒句柄鏈表,少量的內核cache ,就幫我們解決了大并發下的socket 處理問題。執行epoll_create 時,創建了紅黑樹和就緒鏈表,執行epoll_ctl 時,如果增加socket 句柄,則檢查在紅黑樹中是否存在,存在立即返回,不存在則添加到樹干上,然后向內核注冊回調函數,用于當中斷事件來臨時向準備就緒鏈表中插入數據。執行epoll_wait 時立刻返回準備就緒鏈表里的數據即可。
?
epoll 的使用方法
那么究竟如何來使用 epoll 呢?其實非常簡單。
?
通過在包含一個頭文件 #include <sys/epoll.h>? 以及幾個簡單的 API 將可以大大的提高你的網絡服務器的支持人數。
?
首先通過 create_epoll(int maxfds) 來創建一個 epoll 的句柄。這個函數會返回一個新的 epoll 句柄,之后的所有操作將通過這個句柄來進行操作。在用完之后,記得用 close() 來關閉這個創建出來的 epoll 句柄。
?
之后在你的網絡主循環里面,每一幀的調用 epoll_wait(int epfd, epoll_event events, int max events, int timeout) 來查詢所有的網絡接口,看哪一個可以讀,哪一個可以寫了?;镜恼Z法為:
nfds = epoll_wait(kdpfd, events, maxevents, -1);
?
其中 kdpfd 為用 epoll_create 創建之后的句柄, events 是一個 epoll_event* 的指針,當 epoll_wait 這個函數操作成功之后, epoll_events 里面將儲存所有的讀寫事件。 max_events 是當前需要監聽的所有 socket 句柄數。最后一個 timeout 是 ?epoll_wait 的超時,為 0 的時候表示馬上返回,為 -1 的時候表示一直等下去,直到有事件返回,為任意正整數的時候表示等這么長的時間,如果一直沒有事件,則返回。一般如果網絡主循環是單獨的線程的話,可以用 -1 來等,這樣可以保證一些效率,如果是和主邏輯在同一個線程的話,則可以用 0 來保證主循環的效率。
?
epoll_wait 返回之后應該是一個循環,遍歷所有的事件。
?
?
幾乎所有的 epoll 程序都使用下面的框架:
[cpp] view plain
copy for (?;?;?)?????{?? ???????nfds?=?epoll_wait(epfd,events,20,500);?? ???????for (i=0;i<nfds;++i)?? ???????{?? ???????????if (events[i].data.fd==listenfd)??? ???????????{?? ???????????????connfd?=?accept(listenfd,(sockaddr?*)&clientaddr,?&clilen);??? ???????????????ev.data.fd=connfd;?? ???????????????ev.events=EPOLLIN|EPOLLET;?? ???????????????epoll_ctl(epfd,EPOLL_CTL_ADD,connfd,&ev);??? ???????????}?? ?? ???????????else ?if (?events[i].events&EPOLLIN?)??? ???????????{?? ???????????????n?=?read(sockfd,?line,?MAXLINE))?<?0?????? ???????????????ev.data.ptr?=?md;??????? ???????????????ev.events=EPOLLOUT|EPOLLET;?? ???????????????epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev);?? ???????????}?? ???????????else ?if (events[i].events&EPOLLOUT)??? ???????????{?? ???????????????struct ?myepoll_data*?md?=?(myepoll_data*)events[i].data.ptr;?????? ???????????????sockfd?=?md->fd;?? ???????????????send(?sockfd,?md->ptr,?strlen((char *)md->ptr),?0?);?????????? ???????????????ev.data.fd=sockfd;?? ???????????????ev.events=EPOLLIN|EPOLLET;?? ???????????????epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev);??? ???????????}?? ???????????else ?? ???????????{?? ????????????????? ???????????}?? ???????}?? ???}??
epoll 的程序實例
[cpp] view plain
copy ?#include?<stdio.h> ??#include?<stdlib.h> ??#include?<unistd.h> ??#include?<errno.h> ??#include?<sys/socket.h> ??#include?<netdb.h> ??#include?<fcntl.h> ??#include?<sys/epoll.h> ??#include?<string.h> ???? #define?MAXEVENTS?64 ???? ?? ?? ?? ?? static ?int ??create_and_bind?(char ?*port)?? {?? ??struct ?addrinfo?hints;?? ??struct ?addrinfo?*result,?*rp;?? ??int ?s,?sfd;?? ?? ??memset?(&hints,?0,?sizeof ?(struct ?addrinfo));?? ??hints.ai_family?=?AF_UNSPEC;??????? ??hints.ai_socktype?=?SOCK_STREAM;??? ??hints.ai_flags?=?AI_PASSIVE;??????? ?? ??s?=?getaddrinfo?(NULL,?port,?&hints,?&result);?? ??if ?(s?!=?0)?? ????{?? ??????fprintf?(stderr,?"getaddrinfo:?%s\n" ,?gai_strerror?(s));?? ??????return ?-1;?? ????}?? ?? ??for ?(rp?=?result;?rp?!=?NULL;?rp?=?rp->ai_next)?? ????{?? ??????sfd?=?socket?(rp->ai_family,?rp->ai_socktype,?rp->ai_protocol);?? ??????if ?(sfd?==?-1)?? ????????continue ;?? ?? ??????s?=?bind?(sfd,?rp->ai_addr,?rp->ai_addrlen);?? ??????if ?(s?==?0)?? ????????{?? ???????????? ??????????break ;?? ????????}?? ?? ??????close?(sfd);?? ????}?? ?? ??if ?(rp?==?NULL)?? ????{?? ??????fprintf?(stderr,?"Could?not?bind\n" );?? ??????return ?-1;?? ????}?? ?? ??freeaddrinfo?(result);?? ?? ??return ?sfd;?? }?? ?? ?? ?? ?? static ?int ??make_socket_non_blocking?(int ?sfd)?? {?? ??int ?flags,?s;?? ?? ???? ??flags?=?fcntl?(sfd,?F_GETFL,?0);?? ??if ?(flags?==?-1)?? ????{?? ??????perror?("fcntl" );?? ??????return ?-1;?? ????}?? ?? ???? ??flags?|=?O_NONBLOCK;?? ??s?=?fcntl?(sfd,?F_SETFL,?flags);?? ??if ?(s?==?-1)?? ????{?? ??????perror?("fcntl" );?? ??????return ?-1;?? ????}?? ?? ??return ?0;?? }?? ?? ?? int ??main?(int ?argc,?char ?*argv[])?? {?? ??int ?sfd,?s;?? ??int ?efd;?? ??struct ?epoll_event?event;?? ??struct ?epoll_event?*events;?? ?? ??if ?(argc?!=?2)?? ????{?? ??????fprintf?(stderr,?"Usage:?%s?[port]\n" ,?argv[0]);?? ??????exit?(EXIT_FAILURE);?? ????}?? ?? ??sfd?=?create_and_bind?(argv[1]);?? ??if ?(sfd?==?-1)?? ????abort?();?? ?? ??s?=?make_socket_non_blocking?(sfd);?? ??if ?(s?==?-1)?? ????abort?();?? ?? ??s?=?listen?(sfd,?SOMAXCONN);?? ??if ?(s?==?-1)?? ????{?? ??????perror?("listen" );?? ??????abort?();?? ????}?? ?? ???? ??efd?=?epoll_create1?(0);?? ??if ?(efd?==?-1)?? ????{?? ??????perror?("epoll_create" );?? ??????abort?();?? ????}?? ?? ??event.data.fd?=?sfd;?? ??event.events?=?EPOLLIN?|?EPOLLET;?? ??s?=?epoll_ctl?(efd,?EPOLL_CTL_ADD,?sfd,?&event);?? ??if ?(s?==?-1)?? ????{?? ??????perror?("epoll_ctl" );?? ??????abort?();?? ????}?? ?? ???? ??events?=?calloc?(MAXEVENTS,?sizeof ?event);?? ?? ???? ??while ?(1)?? ????{?? ??????int ?n,?i;?? ?? ??????n?=?epoll_wait?(efd,?events,?MAXEVENTS,?-1);?? ??????for ?(i?=?0;?i?<?n;?i++)?? ????????{?? ??????????if ?((events[i].events?&?EPOLLERR)?||?? ??????????????(events[i].events?&?EPOLLHUP)?||?? ??????????????(!(events[i].events?&?EPOLLIN)))?? ????????????{?? ??????????????? ?? ??????????????fprintf?(stderr,?"epoll?error\n" );?? ??????????????close?(events[i].data.fd);?? ??????????????continue ;?? ????????????}?? ?? ??????????else ?if ?(sfd?==?events[i].data.fd)?? ????????????{?? ??????????????? ?? ??????????????while ?(1)?? ????????????????{?? ??????????????????struct ?sockaddr?in_addr;?? ??????????????????socklen_t?in_len;?? ??????????????????int ?infd;?? ??????????????????char ?hbuf[NI_MAXHOST],?sbuf[NI_MAXSERV];?? ?? ??????????????????in_len?=?sizeof ?in_addr;?? ??????????????????infd?=?accept?(sfd,?&in_addr,?&in_len);?? ??????????????????if ?(infd?==?-1)?? ????????????????????{?? ??????????????????????if ?((errno?==?EAGAIN)?||?? ??????????????????????????(errno?==?EWOULDBLOCK))?? ????????????????????????{?? ??????????????????????????? ?? ??????????????????????????break ;?? ????????????????????????}?? ??????????????????????else ?? ????????????????????????{?? ??????????????????????????perror?("accept" );?? ??????????????????????????break ;?? ????????????????????????}?? ????????????????????}?? ?? ???????????????????????????????????? ??????????????????s?=?getnameinfo?(&in_addr,?in_len,?? ???????????????????????????????????hbuf,?sizeof ?hbuf,?? ???????????????????????????????????sbuf,?sizeof ?sbuf,?? ???????????????????????????????????NI_NUMERICHOST?|?NI_NUMERICSERV);?? ???????????????????????????????????? ?? ??????????????????if ?(s?==?0)?? ????????????????????{?? ??????????????????????printf("Accepted?connection?on?descriptor?%d?" ?? ?????????????????????????????"(host=%s,?port=%s)\n" ,?infd,?hbuf,?sbuf);?? ????????????????????}?? ?? ??????????????????? ?? ??????????????????s?=?make_socket_non_blocking?(infd);?? ??????????????????if ?(s?==?-1)?? ????????????????????abort?();?? ?? ??????????????????event.data.fd?=?infd;?? ??????????????????event.events?=?EPOLLIN?|?EPOLLET;?? ??????????????????s?=?epoll_ctl?(efd,?EPOLL_CTL_ADD,?infd,?&event);?? ??????????????????if ?(s?==?-1)?? ????????????????????{?? ??????????????????????perror?("epoll_ctl" );?? ??????????????????????abort?();?? ????????????????????}?? ????????????????}?? ??????????????continue ;?? ????????????}?? ??????????else ?? ????????????{?? ??????????????? ? ? ? ?? ??????????????int ?done?=?0;?? ?? ??????????????while ?(1)?? ????????????????{?? ??????????????????ssize_t?count;?? ??????????????????char ?buf[512];?? ?? ??????????????????count?=?read?(events[i].data.fd,?buf,?sizeof (buf));?? ??????????????????if ?(count?==?-1)?? ????????????????????{?? ??????????????????????? ?? ??????????????????????if ?(errno?!=?EAGAIN)?? ????????????????????????{?? ??????????????????????????perror?("read" );?? ??????????????????????????done?=?1;?? ????????????????????????}?? ??????????????????????break ;?? ????????????????????}?? ??????????????????else ?if ?(count?==?0)?? ????????????????????{?? ??????????????????????? ?? ??????????????????????done?=?1;?? ??????????????????????break ;?? ????????????????????}?? ?? ???????????????????? ??????????????????s?=?write?(1,?buf,?count);?? ??????????????????if ?(s?==?-1)?? ????????????????????{?? ??????????????????????perror?("write" );?? ??????????????????????abort?();?? ????????????????????}?? ????????????????}?? ?? ??????????????if ?(done)?? ????????????????{?? ??????????????????printf?("Closed?connection?on?descriptor?%d\n" ,?? ??????????????????????????events[i].data.fd);?? ?? ??????????????????? ?? ??????????????????close?(events[i].data.fd);?? ????????????????}?? ????????????}?? ????????}?? ????}?? ?? ??free?(events);?? ?? ??close?(sfd);?? ?? ??return ?EXIT_SUCCESS;?? }??
運行方式:
在一個終端運行此程序:epoll.out PORT
另一個終端:telnet ?127.0.0.1 PORT
截圖:
參考資料:
http://man7.org/linux/man-pages/man2/epoll_create.2.html
https://banu.com/blog/2/how-to-use-epoll-a-complete-example-in-c/
http://blog.csdn.net/sparkliang/article/details/4770655
?《深入理解Nginx模塊開發與架構解析》9.6小節
http://blog.csdn.net/eroswang/article/details/4481521
http://www.ccvita.com/515.html
http://blog.codingnow.com/2006/04/iocp_kqueue_epoll.html
《新程序員》:云原生和全面數字化實踐 50位技術專家共同創作,文字、視頻、音頻交互閱讀
總結
以上是生活随笔 為你收集整理的【Linux学习】epoll详解 的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔 網站內容還不錯,歡迎將生活随笔 推薦給好友。