epoll詳解(轉載)

什麼是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:註冊新的fdepfd中;

EPOLL_CTL_MOD:修改已經註冊的fd的監聽事件;

EPOLL_CTL_DEL:從epfd中刪除一個fd

 

第三個參數是需要監聽的fd

第四個參數是告訴內核需要監聽什麼事,struct epoll_event結構如下:

  1. //保存觸發事件的某個文件描述符相關的數據(與具體使用方式有關)  
  2.   
  3. typedef union epoll_data {  
  4.     void *ptr;  
  5.     int fd;  
  6.     __uint32_t u32;  
  7.     __uint64_t u64;  
  8. } epoll_data_t;  
  9.  //感興趣的事件和被觸發的事件  
  10. struct epoll_event {  
  11.     __uint32_t events; /* Epoll events */  
  12.     epoll_data_t data; /* User data variable */  
  13. };  

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()時便得到通知。

 

Epoll2種工作方式-水平觸發(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缺省的工作方式,並且同時支持blockno-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網卡驅動架構。

 

linuxepoll如何實現高效處理百萬句柄的

開發高性能網絡程序時,windows開發者們言必稱iocplinux開發者們則言必稱epoll。大家都明白epoll是一種IO多路複用技術,可以非常高效的處理數以百萬計的socket句柄,比起以前的selectpoll效率高大發了。我們用起epoll來都感覺挺爽,確實快,那麼,它到底爲什麼可以高速處理這麼多併發連接呢?

 

使用起來很清晰,首先要調用epoll_create建立一個epoll對象。參數size是內核保證能夠正確處理的最大句柄數,多於這個最大數時內核可不保證效果。

 

epoll_ctl可以操作上面建立的epoll,例如,將剛建立的socket加入到epoll中讓其監控,或者把 epoll正在監控的某個socket句柄移出epoll,不再監控它等等。

 

epoll_wait在調用時,在給定的timeout時間內,當在監控的所有句柄中有事件發生時,就返回用戶態的進程。

 

從上面的調用方式就可以看到epollselect/poll的優越之處:因爲後者每次調用時都要傳遞你所要監控的所有socketselect/poll系統調用,這意味着需要將用戶態的socket列表copy到內核態,如果以萬計的句柄會導致每次都要copy幾十幾百KB的內存到內核態,非常低效。而我們調用epoll_wait時就相當於以往調用select/poll,但是這時卻不用傳遞socket句柄給內核,因爲內核已經在epoll_ctl中拿到了要監控的句柄列表。

 

所以,實際上在你調用epoll_create後,內核就已經在內核態開始準備幫你存儲要監控的句柄了,每次調用epoll_ctl只是在往內核的數據結構裏塞入新的socket句柄。

 當一個進程調用epoll_creaqte方法時,Linux內核會創建一個eventpoll結構體,這個結構體中有兩個成員與epoll的使用方式密切相關:

  1. /* 
  2.  
  3.  171 * This structure is stored inside the "private_data" member of the file 
  4.  
  5.  172 * structure and represents the main data structure for the eventpoll 
  6.  
  7.  173 * interface. 
  8.  
  9.  174 */  
  10.   
  11.  175struct eventpoll {  
  12.   
  13.  176        /* Protect the access to this structure */  
  14.   
  15.  177        spinlock_t lock;  
  16.   
  17.  178  
  18.   
  19.  179        /* 
  20.  
  21.  180         * This mutex is used to ensure that files are not removed 
  22.  
  23.  181         * while epoll is using them. This is held during the event 
  24.  
  25.  182         * collection loop, the file cleanup path, the epoll file exit 
  26.  
  27.  183         * code and the ctl operations. 
  28.  
  29.  184         */  
  30.   
  31.  185        struct mutex mtx;  
  32.   
  33.  186  
  34.   
  35.  187        /* Wait queue used by sys_epoll_wait() */  
  36.   
  37.  188        wait_queue_head_t wq;  
  38.   
  39.  189  
  40.   
  41.  190        /* Wait queue used by file->poll() */  
  42.   
  43.  191        wait_queue_head_t poll_wait;  
  44.   
  45.  192  
  46.   
  47.  193        /* List of ready file descriptors */  
  48.   
  49.  194        struct list_head rdllist;  
  50.   
  51.  195  
  52.   
  53.  196        /* RB tree root used to store monitored fd structs */  
  54.   
  55.  197        struct rb_root rbr;//紅黑樹根節點,這棵樹存儲着所有添加到epoll中的事件,也就是這個epoll監控的事件  
  56.  198  
  57.  199        /* 
  58.  200         * This is a single linked list that chains all the "struct epitem" that 
  59.  201         * happened while transferring ready events to userspace w/out 
  60.  202         * holding ->lock. 
  61.  203         */  
  62.  204        struct epitem *ovflist;  
  63.  205  
  64.  206        /* wakeup_source used when ep_scan_ready_list is running */  
  65.  207        struct wakeup_source *ws;  
  66.  208  
  67.  209        /* The user that created the eventpoll descriptor */  
  68.  210        struct user_struct *user;  
  69.  211  
  70.  212        struct file *file;  
  71.  213  
  72.  214        /* used to optimize loop detection check */  
  73.  215        int visited;  
  74.  216        struct list_head visited_list_link;//雙向鏈表中保存着將要通過epoll_wait返回給用戶的、滿足條件的事件  
  75.  217};  

每一個epoll對象都有一個獨立的eventpoll結構體,這個結構體會在內核空間中創造獨立的內存,用於存儲使用epoll_ctl方法向epoll對象中添加進來的事件。這樣,重複的事件就可以通過紅黑樹而高效的識別出來。

在epoll中,對於每一個事件都會建立一個epitem結構體:

  1. /* 
  2.  130 * Each file descriptor added to the eventpoll interface will 
  3.  131 * have an entry of this type linked to the "rbr" RB tree. 
  4.  132 * Avoid increasing the size of this struct, there can be many thousands 
  5.  133 * of these on a server and we do not want this to take another cache line. 
  6.  134 */  
  7.  135struct epitem {  
  8.  136        /* RB tree node used to link this structure to the eventpoll RB tree */  
  9.  137        struct rb_node rbn;  
  10.  138  
  11.  139        /* List header used to link this structure to the eventpoll ready list */  
  12.  140        struct list_head rdllink;  
  13.  141  
  14.  142        /* 
  15.  143         * Works together "struct eventpoll"->ovflist in keeping the 
  16.  144         * single linked chain of items. 
  17.  145         */  
  18.  146        struct epitem *next;  
  19.  147  
  20.  148        /* The file descriptor information this item refers to */  
  21.  149        struct epoll_filefd ffd;  
  22.  150  
  23.  151        /* Number of active wait queue attached to poll operations */  
  24.  152        int nwait;  
  25.  153  
  26.  154        /* List containing poll wait queues */  
  27.  155        struct list_head pwqlist;  
  28.  156  
  29.  157        /* The "container" of this item */  
  30.  158        struct eventpoll *ep;  
  31.  159  
  32.  160        /* List header used to link this item to the "struct file" items list */  
  33.  161        struct list_head fllink;  
  34.  162  
  35.  163        /* wakeup_source used when EPOLLWAKEUP is set */  
  36.  164        struct wakeup_source __rcu *ws;  
  37.  165  
  38.  166        /* The structure that describe the interested events and the source fd */  
  39.  167        struct epoll_event event;  
  40.  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)來查詢所有的網絡接口,看哪一個可以讀,哪一個可以寫了。基本的語法爲:

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程序都使用下面的框架:

  1. for( ; ; )  
  2.    {  
  3.        nfds = epoll_wait(epfd,events,20,500);  
  4.        for(i=0;i<nfds;++i)  
  5.        {  
  6.            if(events[i].data.fd==listenfd) //有新的連接  
  7.            {  
  8.                connfd = accept(listenfd,(sockaddr *)&clientaddr, &clilen); //accept這個連接  
  9.                ev.data.fd=connfd;  
  10.                ev.events=EPOLLIN|EPOLLET;  
  11.                epoll_ctl(epfd,EPOLL_CTL_ADD,connfd,&ev); //將新的fd添加到epoll的監聽隊列中  
  12.            }  
  13.   
  14.            else if( events[i].events&EPOLLIN ) //接收到數據,讀socket  
  15.            {  
  16.                n = read(sockfd, line, MAXLINE)) < 0    //讀  
  17.                ev.data.ptr = md;     //md爲自定義類型,添加數據  
  18.                ev.events=EPOLLOUT|EPOLLET;  
  19.                epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev);//修改標識符,等待下一個循環時發送數據,異步處理的精髓  
  20.            }  
  21.            else if(events[i].events&EPOLLOUT) //有數據待發送,寫socket  
  22.            {  
  23.                struct myepoll_data* md = (myepoll_data*)events[i].data.ptr;    //取數據  
  24.                sockfd = md->fd;  
  25.                send( sockfd, md->ptr, strlen((char*)md->ptr), 0 );        //發送數據  
  26.                ev.data.fd=sockfd;  
  27.                ev.events=EPOLLIN|EPOLLET;  
  28.                epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev); //修改標識符,等待下一個循環時接收數據  
  29.            }  
  30.            else  
  31.            {  
  32.                //其他的處理  
  33.            }  
  34.        }  
  35.    }  

epoll的程序實例

  1.  #include <stdio.h>  
  2. #include <stdlib.h>  
  3. #include <unistd.h>  
  4. #include <errno.h>  
  5. #include <sys/socket.h>  
  6. #include <netdb.h>  
  7. #include <fcntl.h>  
  8. #include <sys/epoll.h>  
  9. #include <string.h>  
  10.   
  11. #define MAXEVENTS 64  
  12.   
  13. //函數:  
  14. //功能:創建和綁定一個TCP socket  
  15. //參數:端口  
  16. //返回值:創建的socket  
  17. static int  
  18. create_and_bind (char *port)  
  19. {  
  20.   struct addrinfo hints;  
  21.   struct addrinfo *result, *rp;  
  22.   int s, sfd;  
  23.   
  24.   memset (&hints, 0, sizeof (struct addrinfo));  
  25.   hints.ai_family = AF_UNSPEC;     /* Return IPv4 and IPv6 choices */  
  26.   hints.ai_socktype = SOCK_STREAM; /* We want a TCP socket */  
  27.   hints.ai_flags = AI_PASSIVE;     /* All interfaces */  
  28.   
  29.   s = getaddrinfo (NULL, port, &hints, &result);  
  30.   if (s != 0)  
  31.     {  
  32.       fprintf (stderr, "getaddrinfo: %s\n", gai_strerror (s));  
  33.       return -1;  
  34.     }  
  35.   
  36.   for (rp = result; rp != NULL; rp = rp->ai_next)  
  37.     {  
  38.       sfd = socket (rp->ai_family, rp->ai_socktype, rp->ai_protocol);  
  39.       if (sfd == -1)  
  40.         continue;  
  41.   
  42.       s = bind (sfd, rp->ai_addr, rp->ai_addrlen);  
  43.       if (s == 0)  
  44.         {  
  45.           /* We managed to bind successfully! */  
  46.           break;  
  47.         }  
  48.   
  49.       close (sfd);  
  50.     }  
  51.   
  52.   if (rp == NULL)  
  53.     {  
  54.       fprintf (stderr, "Could not bind\n");  
  55.       return -1;  
  56.     }  
  57.   
  58.   freeaddrinfo (result);  
  59.   
  60.   return sfd;  
  61. }  
  62.   
  63.   
  64. //函數  
  65. //功能:設置socket爲非阻塞的  
  66. static int  
  67. make_socket_non_blocking (int sfd)  
  68. {  
  69.   int flags, s;  
  70.   
  71.   //得到文件狀態標誌  
  72.   flags = fcntl (sfd, F_GETFL, 0);  
  73.   if (flags == -1)  
  74.     {  
  75.       perror ("fcntl");  
  76.       return -1;  
  77.     }  
  78.   
  79.   //設置文件狀態標誌  
  80.   flags |= O_NONBLOCK;  
  81.   s = fcntl (sfd, F_SETFL, flags);  
  82.   if (s == -1)  
  83.     {  
  84.       perror ("fcntl");  
  85.       return -1;  
  86.     }  
  87.   
  88.   return 0;  
  89. }  
  90.   
  91. //端口由參數argv[1]指定  
  92. int  
  93. main (int argc, char *argv[])  
  94. {  
  95.   int sfd, s;  
  96.   int efd;  
  97.   struct epoll_event event;  
  98.   struct epoll_event *events;  
  99.   
  100.   if (argc != 2)  
  101.     {  
  102.       fprintf (stderr, "Usage: %s [port]\n", argv[0]);  
  103.       exit (EXIT_FAILURE);  
  104.     }  
  105.   
  106.   sfd = create_and_bind (argv[1]);  
  107.   if (sfd == -1)  
  108.     abort ();  
  109.   
  110.   s = make_socket_non_blocking (sfd);  
  111.   if (s == -1)  
  112.     abort ();  
  113.   
  114.   s = listen (sfd, SOMAXCONN);  
  115.   if (s == -1)  
  116.     {  
  117.       perror ("listen");  
  118.       abort ();  
  119.     }  
  120.   
  121.   //除了參數size被忽略外,此函數和epoll_create完全相同  
  122.   efd = epoll_create1 (0);  
  123.   if (efd == -1)  
  124.     {  
  125.       perror ("epoll_create");  
  126.       abort ();  
  127.     }  
  128.   
  129.   event.data.fd = sfd;  
  130.   event.events = EPOLLIN | EPOLLET;//讀入,邊緣觸發方式  
  131.   s = epoll_ctl (efd, EPOLL_CTL_ADD, sfd, &event);  
  132.   if (s == -1)  
  133.     {  
  134.       perror ("epoll_ctl");  
  135.       abort ();  
  136.     }  
  137.   
  138.   /* Buffer where events are returned */  
  139.   events = calloc (MAXEVENTS, sizeof event);  
  140.   
  141.   /* The event loop */  
  142.   while (1)  
  143.     {  
  144.       int n, i;  
  145.   
  146.       n = epoll_wait (efd, events, MAXEVENTS, -1);  
  147.       for (i = 0; i < n; i++)  
  148.         {  
  149.           if ((events[i].events & EPOLLERR) ||  
  150.               (events[i].events & EPOLLHUP) ||  
  151.               (!(events[i].events & EPOLLIN)))  
  152.             {  
  153.               /* An error has occured on this fd, or the socket is not 
  154.                  ready for reading (why were we notified then?) */  
  155.               fprintf (stderr, "epoll error\n");  
  156.               close (events[i].data.fd);  
  157.               continue;  
  158.             }  
  159.   
  160.           else if (sfd == events[i].data.fd)  
  161.             {  
  162.               /* We have a notification on the listening socket, which 
  163.                  means one or more incoming connections. */  
  164.               while (1)  
  165.                 {  
  166.                   struct sockaddr in_addr;  
  167.                   socklen_t in_len;  
  168.                   int infd;  
  169.                   char hbuf[NI_MAXHOST], sbuf[NI_MAXSERV];  
  170.   
  171.                   in_len = sizeof in_addr;  
  172.                   infd = accept (sfd, &in_addr, &in_len);  
  173.                   if (infd == -1)  
  174.                     {  
  175.                       if ((errno == EAGAIN) ||  
  176.                           (errno == EWOULDBLOCK))  
  177.                         {  
  178.                           /* We have processed all incoming 
  179.                              connections. */  
  180.                           break;  
  181.                         }  
  182.                       else  
  183.                         {  
  184.                           perror ("accept");  
  185.                           break;  
  186.                         }  
  187.                     }  
  188.   
  189.                                   //將地址轉化爲主機名或者服務名  
  190.                   s = getnameinfo (&in_addr, in_len,  
  191.                                    hbuf, sizeof hbuf,  
  192.                                    sbuf, sizeof sbuf,  
  193.                                    NI_NUMERICHOST | NI_NUMERICSERV);//flag參數:以數字名返回  
  194.                                   //主機地址和服務地址  
  195.   
  196.                   if (s == 0)  
  197.                     {  
  198.                       printf("Accepted connection on descriptor %d "  
  199.                              "(host=%s, port=%s)\n", infd, hbuf, sbuf);  
  200.                     }  
  201.   
  202.                   /* Make the incoming socket non-blocking and add it to the 
  203.                      list of fds to monitor. */  
  204.                   s = make_socket_non_blocking (infd);  
  205.                   if (s == -1)  
  206.                     abort ();  
  207.   
  208.                   event.data.fd = infd;  
  209.                   event.events = EPOLLIN | EPOLLET;  
  210.                   s = epoll_ctl (efd, EPOLL_CTL_ADD, infd, &event);  
  211.                   if (s == -1)  
  212.                     {  
  213.                       perror ("epoll_ctl");  
  214.                       abort ();  
  215.                     }  
  216.                 }  
  217.               continue;  
  218.             }  
  219.           else  
  220.             {  
  221.               /* We have data on the fd waiting to be read. Read and 
  222.                  display it. We must read whatever data is available 
  223.                  completely, as we are running in edge-triggered mode 
  224.                  and won't get a notification again for the same 
  225.                  data. */  
  226.               int done = 0;  
  227.   
  228.               while (1)  
  229.                 {  
  230.                   ssize_t count;  
  231.                   char buf[512];  
  232.   
  233.                   count = read (events[i].data.fd, buf, sizeof(buf));  
  234.                   if (count == -1)  
  235.                     {  
  236.                       /* If errno == EAGAIN, that means we have read all 
  237.                          data. So go back to the main loop. */  
  238.                       if (errno != EAGAIN)  
  239.                         {  
  240.                           perror ("read");  
  241.                           done = 1;  
  242.                         }  
  243.                       break;  
  244.                     }  
  245.                   else if (count == 0)  
  246.                     {  
  247.                       /* End of file. The remote has closed the 
  248.                          connection. */  
  249.                       done = 1;  
  250.                       break;  
  251.                     }  
  252.   
  253.                   /* Write the buffer to standard output */  
  254.                   s = write (1, buf, count);  
  255.                   if (s == -1)  
  256.                     {  
  257.                       perror ("write");  
  258.                       abort ();  
  259.                     }  
  260.                 }  
  261.   
  262.               if (done)  
  263.                 {  
  264.                   printf ("Closed connection on descriptor %d\n",  
  265.                           events[i].data.fd);  
  266.   
  267.                   /* Closing the descriptor will make epoll remove it 
  268.                      from the set of descriptors which are monitored. */  
  269.                   close (events[i].data.fd);  
  270.                 }  
  271.             }  
  272.         }  
  273.     }  
  274.   
  275.   free (events);  
  276.   
  277.   close (sfd);  
  278.   
  279.   return EXIT_SUCCESS;  
  280. }  

運行方式:

在一個終端運行此程序:epoll.out PORT

另一個終端:telnet  127.0.0.1 PORT

截圖:



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