什麼是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就緒通知方法。html
epoll的相關係統調用
epoll只有epoll_create,epoll_ctl,epoll_wait 3個系統調用。node
1. int epoll_create(int size);linux
建立一個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);windows
epoll的事件註冊函數,它不一樣於select()是在監聽事件時告訴內核要監聽什麼類型的事件,而是在這裏先註冊要監聽的事件類型。數組
第一個參數是epoll_create()的返回值。服務器
第二個參數表示動做,用三個宏來表示:網絡
EPOLL_CTL_ADD:註冊新的fd到epfd中;數據結構
EPOLL_CTL_MOD:修改已經註冊的fd的監聽事件;架構
EPOLL_CTL_DEL:從epfd中刪除一個fd;
第三個參數是須要監聽的fd。
第四個參數是告訴內核須要監聽什麼事,struct epoll_event結構以下:
//保存觸發事件的某個文件描述符相關的數據(與具體使用方式有關)
typedef union epoll_data {
void *ptr;
int fd;
__uint32_t u32;
__uint64_t u64;
} epoll_data_t;
//感興趣的事件和被觸發的事件
struct epoll_event {
__uint32_t events; /* Epoll events */
epoll_data_t data; /* User data variable */
};
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的使用方式密切相關:
/*
171 * This structure is stored inside the "private_data" member of the file
172 * structure and represents the main data structure for the eventpoll
173 * interface.
174 */
175struct eventpoll {
176 /* Protect the access to this structure */
177 spinlock_t lock;
178
179 /*
180 * This mutex is used to ensure that files are not removed
181 * while epoll is using them. This is held during the event
182 * collection loop, the file cleanup path, the epoll file exit
183 * code and the ctl operations.
184 */
185 struct mutex mtx;
186
187 /* Wait queue used by sys_epoll_wait() */
188 wait_queue_head_t wq;
189
190 /* Wait queue used by file->poll() */
191 wait_queue_head_t poll_wait;
192
193 /* List of ready file descriptors */
194 struct list_head rdllist;
195
196 /* RB tree root used to store monitored fd structs */
197 struct rb_root rbr;//紅黑樹根節點,這棵樹存儲着全部添加到epoll中的事件,也就是這個epoll監控的事件
198
199 /*
200 * This is a single linked list that chains all the "struct epitem" that
201 * happened while transferring ready events to userspace w/out
202 * holding ->lock.
203 */
204 struct epitem *ovflist;
205
206 /* wakeup_source used when ep_scan_ready_list is running */
207 struct wakeup_source *ws;
208
209 /* The user that created the eventpoll descriptor */
210 struct user_struct *user;
211
212 struct file *file;
213
214 /* used to optimize loop detection check */
215 int visited;
216 struct list_head visited_list_link;//雙向鏈表中保存着將要經過epoll_wait返回給用戶的、知足條件的事件
217};
每個epoll對象都有一個獨立的eventpoll結構體,這個結構體會在內核空間中創造獨立的內存,用於存儲使用epoll_ctl方法向epoll對象中添加進來的事件。這樣,重複的事件就能夠經過紅黑樹而高效的識別出來。
在epoll中,對於每個事件都會創建一個epitem結構體:
/*
130 * Each file descriptor added to the eventpoll interface will
131 * have an entry of this type linked to the "rbr" RB tree.
132 * Avoid increasing the size of this struct, there can be many thousands
133 * of these on a server and we do not want this to take another cache line.
134 */
135struct epitem {
136 /* RB tree node used to link this structure to the eventpoll RB tree */
137 struct rb_node rbn;
138
139 /* List header used to link this structure to the eventpoll ready list */
140 struct list_head rdllink;
141
142 /*
143 * Works together "struct eventpoll"->ovflist in keeping the
144 * single linked chain of items.
145 */
146 struct epitem *next;
147
148 /* The file descriptor information this item refers to */
149 struct epoll_filefd ffd;
150
151 /* Number of active wait queue attached to poll operations */
152 int nwait;
153
154 /* List containing poll wait queues */
155 struct list_head pwqlist;
156
157 /* The "container" of this item */
158 struct eventpoll *ep;
159
160 /* List header used to link this item to the "struct file" items list */
161 struct list_head fllink;
162
163 /* wakeup_source used when EPOLLWAKEUP is set */
164 struct wakeup_source __rcu *ws;
165
166 /* The structure that describe the interested events and the source fd */
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)來查詢全部的網絡接口,看哪個能夠讀,哪個能夠寫了。基本的語法爲:
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程序都使用下面的框架:
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); //accept這個鏈接
ev.data.fd=connfd;
ev.events=EPOLLIN|EPOLLET;
epoll_ctl(epfd,EPOLL_CTL_ADD,connfd,&ev); //將新的fd添加到epoll的監聽隊列中
}
else if( events[i].events&EPOLLIN ) //接收到數據,讀socket
{
n = read(sockfd, line, MAXLINE)) < 0 //讀
ev.data.ptr = md; //md爲自定義類型,添加數據
ev.events=EPOLLOUT|EPOLLET;
epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev);//修改標識符,等待下一個循環時發送數據,異步處理的精髓
}
else if(events[i].events&EPOLLOUT) //有數據待發送,寫socket
{
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的程序實例
#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
//函數:
//功能:建立和綁定一個TCP socket
//參數:端口
//返回值:建立的socket
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; /* Return IPv4 and IPv6 choices */
hints.ai_socktype = SOCK_STREAM; /* We want a TCP socket */
hints.ai_flags = AI_PASSIVE; /* All interfaces */
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)
{
/* We managed to bind successfully! */
break;
}
close (sfd);
}
if (rp == NULL)
{
fprintf (stderr, "Could not bind\n");
return -1;
}
freeaddrinfo (result);
return sfd;
}
//函數
//功能:設置socket爲非阻塞的
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;
}
//端口由參數argv[1]指定
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 ();
}
//除了參數size被忽略外,此函數和epoll_create徹底相同
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 ();
}
/* Buffer where events are returned */
events = calloc (MAXEVENTS, sizeof event);
/* The event loop */
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)))
{
/* An error has occured on this fd, or the socket is not
ready for reading (why were we notified then?) */
fprintf (stderr, "epoll error\n");
close (events[i].data.fd);
continue;
}
else if (sfd == events[i].data.fd)
{
/* We have a notification on the listening socket, which
means one or more incoming connections. */
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))
{
/* We have processed all incoming
connections. */
break;
}
else
{
perror ("accept");
break;
}
}
//將地址轉化爲主機名或者服務名
s = getnameinfo (&in_addr, in_len,
hbuf, sizeof hbuf,
sbuf, sizeof sbuf,
NI_NUMERICHOST | NI_NUMERICSERV);//flag參數:以數字名返回
//主機地址和服務地址
if (s == 0)
{
printf("Accepted connection on descriptor %d "
"(host=%s, port=%s)\n", infd, hbuf, sbuf);
}
/* Make the incoming socket non-blocking and add it to the
list of fds to monitor. */
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
{
/* We have data on the fd waiting to be read. Read and
display it. We must read whatever data is available
completely, as we are running in edge-triggered mode
and won't get a notification again for the same
data. */
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, that means we have read all
data. So go back to the main loop. */
if (errno != EAGAIN)
{
perror ("read");
done = 1;
}
break;
}
else if (count == 0)
{
/* End of file. The remote has closed the
connection. */
done = 1;
break;
}
/* Write the buffer to standard output */
s = write (1, buf, count);
if (s == -1)
{
perror ("write");
abort ();
}
}
if (done)
{
printf ("Closed connection on descriptor %d\n",
events[i].data.fd);
/* Closing the descriptor will make epoll remove it
from the set of descriptors which are monitored. */
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
(原文地址:https://blog.csdn.net/xiajun07061225/article/details/9250579)