名詞解釋:man epoll之後,得到如下結果:
NAME
epoll - I/O event notification facility
SYNOPSIS
#include <sys/epoll.h>
DESCRIPTION
epoll is a variant of poll(2) that can be used either as Edge or Level
Triggered interface and scales well to large numbers of watched fds.
Three system calls are provided to set up and control an epoll set:
epoll_create(2), epoll_ctl(2), epoll_wait(2).
An epoll set is connected to a file descriptor created by epoll_cre-
ate(2). Interest for certain file descriptors is then registered via
epoll_ctl(2). Finally, the actual wait is started by epoll_wait(2).
其實,一切的解釋都是多餘的,按照我目前的了解,EPOLL模型似乎隻有一種格式,是以大家隻要參考我下面的代碼,就能夠對EPOLL有所了解了,代碼的解釋都已經在注釋中:
while (TRUE)
{
int nfds = epoll_wait (m_epoll_fd, m_events, MAX_EVENTS, EPOLL_TIME_OUT);//等待EPOLL時間的發生,相當于監聽,至于相關的端口,需要在初始化EPOLL的時候綁定。
if (nfds <= 0)
continue;
m_bOnTimeChecking = FALSE;
G_CurTime = time(NULL);
for (int i=0; i<nfds; i++)
{
try
{
if (m_events[i].data.fd == m_listen_http_fd)//如果新監測到一個HTTP使用者連接配接到綁定的HTTP端口,建立新的連接配接。由于我們新采用了SOCKET連接配接,是以基本沒用。
{
OnAcceptHttpEpoll ();
}
else if (m_events[i].data.fd == m_listen_sock_fd)//如果新監測到一個SOCKET使用者連接配接到了綁定的SOCKET端口,建立新的連接配接。
OnAcceptSockEpoll ();
else if (m_events[i].events & EPOLLIN)//如果是已經連接配接的使用者,并且收到資料,那麼進行讀入。
OnReadEpoll (i);
OnWriteEpoll (i);//檢視目前的活動連接配接是否有需要寫出的資料。
}
catch (int)
PRINTF ("CATCH捕獲錯誤/n");
continue;
}
m_bOnTimeChecking = TRUE;
OnTimer ();//進行一些定時的操作,主要就是删除一些短線使用者等。
}
其實EPOLL的精華,按照我目前的了解,也就是上述的幾段短短的代碼,看來時代真的不同了,以前如何接受大量使用者連接配接的問題,現在卻被如此輕松的搞定,真是讓人不得不感歎。
今天搞了一天的epoll,想做一個高并發的代理程式。剛開始真是郁悶,一直搞不通,網上也有幾篇介紹epoll的文章。但都不深入,沒有将一些注意的地方講明。以至于走了很多彎路,現将自己的一些了解共享給大家,以少走彎路。
epoll用到的所有函數都是在頭檔案sys/epoll.h中聲明,有什麼地方不明白或函數忘記了可以去看一下。
epoll和select相比,最大不同在于:
1epoll傳回時已經明确的知道哪個sokcet fd發生了事件,不用再一個個比對。這樣就提高了效率。
2select的FD_SETSIZE是有限止的,而epoll是沒有限止的隻與系統資源有關。
1、epoll_create函數
函數聲明:int epoll_create(int size)
該函數生成一個epoll專用的檔案描述符。它其實是在核心申請一空間,用來存放你想關注的socket fd上是否發生以及發生了什麼事件。size就是你在這個epoll fd上能關注的最大socket fd數。随你定好了。隻要你有空間。可參見上面與select之不同2.
22、epoll_ctl函數
函數聲明:int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)
該函數用于控制某個epoll檔案描述符上的事件,可以注冊事件,修改事件,删除事件。
參數:
epfd:由 epoll_create 生成的epoll專用的檔案描述符;
op:要進行的操作例如注冊事件,可能的取值EPOLL_CTL_ADD 注冊、EPOLL_CTL_MOD 修 改、EPOLL_CTL_DEL 删除
fd:關聯的檔案描述符;
event:指向epoll_event的指針;
如果調用成功傳回0,不成功傳回-1
用到的資料結構
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 */
};
如:
struct epoll_event ev;
//設定與要處理的事件相關的檔案描述符
ev.data.fd=listenfd;
//設定要處理的事件類型
ev.events=EPOLLIN|EPOLLET;
//注冊epoll事件
epoll_ctl(epfd,EPOLL_CTL_ADD,listenfd,&ev);
常用的事件類型:
EPOLLIN :表示對應的檔案描述符可以讀;
EPOLLOUT:表示對應的檔案描述符可以寫;
EPOLLPRI:表示對應的檔案描述符有緊急的資料可讀
EPOLLERR:表示對應的檔案描述符發生錯誤;
EPOLLHUP:表示對應的檔案描述符被挂斷;
EPOLLET:表示對應的檔案描述符有事件發生;
3、epoll_wait函數
函數聲明:int epoll_wait(int epfd,struct epoll_event * events,int maxevents,int timeout)
該函數用于輪詢I/O事件的發生;
參數:
epfd:由epoll_create 生成的epoll專用的檔案描述符;
epoll_event:用于回傳代處理事件的數組;
maxevents:每次能處理的事件數;
timeout:等待I/O事件發生的逾時值(機關我也不太清楚);-1相當于阻塞,0相當于非阻塞。一般用-1即可
傳回發生事件數。
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <fcntl.h>
#include <sys/epoll.h>
#include <sys/time.h>
#include <sys/resource.h>
#define MAXBUF 1024
#define MAXEPOLLSIZE 10000
/*
setnonblocking - 設定句柄為非阻塞方式
*/
int setnonblocking(int sockfd)
{
if (fcntl(sockfd, F_SETFL, fcntl(sockfd, F_GETFD, 0)|O_NONBLOCK) == -1)
{
return -1;
}
return 0;
}
handle_message - 處理每個 socket 上的消息收發
int handle_message(int new_fd)
char buf[MAXBUF + 1];
int len;
/* 開始處理每個新連接配接上的資料收發 */
bzero(buf, MAXBUF + 1);
/* 接收用戶端的消息 */
len = recv(new_fd, buf, MAXBUF, 0);
if (len > 0)
printf
("%d接收消息成功:'%s',共%d個位元組的資料/n",
new_fd, buf, len);
}
else
if (len < 0)
printf
("消息接收失敗!錯誤代碼是%d,錯誤資訊是'%s'/n",
errno, strerror(errno));
close(new_fd);
/* 處理每個新連接配接上的資料收發結束 */
return len;
/************關于本文檔********************************************
*filename: epoll-server.c
*purpose: 示範epoll處理海量socket連接配接的方法
*wrote by: zhoulifa(<a href="mailto:[email protected]">[email protected]</a>) 周立發(<a href="http://zhoulifa.bokee.com">http://zhoulifa.bokee.com</a>)
Linux愛好者 Linux知識傳播者 SOHO族 開發者 最擅長C語言
*date time:2007-01-31 21:00
*Note: 任何人可以任意複制代碼并運用這些文檔,當然包括你的商業用途
* 但請遵循GPL
*Thanks to:Google
*Hope:希望越來越多的人貢獻自己的力量,為科學技術發展出力
* 科技站在巨人的肩膀上進步更快!感謝有開源前輩的貢獻!
*********************************************************************/
int main(int argc, char **argv)
int listener, new_fd, kdpfd, nfds, n, ret, curfds;
socklen_t len;
struct sockaddr_in my_addr, their_addr;
unsigned int myport, lisnum;
struct epoll_event ev;
struct epoll_event events[MAXEPOLLSIZE];
struct rlimit rt;
myport = 5000;
lisnum = 2;
/* 設定每個程序允許打開的最大檔案數 */
rt.rlim_max = rt.rlim_cur = MAXEPOLLSIZE;
if (setrlimit(RLIMIT_NOFILE, &rt) == -1)
perror("setrlimit");
exit(1);
else
{
printf("設定系統資源參數成功!/n");
/* 開啟 socket 監聽 */
if ((listener = socket(PF_INET, SOCK_STREAM, 0)) == -1)
perror("socket");
printf("socket 建立成功!/n");
setnonblocking(listener);
bzero(&my_addr, sizeof(my_addr));
my_addr.sin_family = PF_INET;
my_addr.sin_port = htons(myport);
my_addr.sin_addr.s_addr = INADDR_ANY;
if (bind(listener, (struct sockaddr *) &my_addr, sizeof(struct sockaddr)) == -1)
perror("bind");
}
printf("IP 位址和端口綁定成功/n");
if (listen(listener, lisnum) == -1)
perror("listen");
printf("開啟服務成功!/n");
/* 建立 epoll 句柄,把監聽 socket 加入到 epoll 集合裡 */
kdpfd = epoll_create(MAXEPOLLSIZE);
len = sizeof(struct sockaddr_in);
ev.events = EPOLLIN | EPOLLET;
ev.data.fd = listener;
if (epoll_ctl(kdpfd, EPOLL_CTL_ADD, listener, &ev) < 0)
fprintf(stderr, "epoll set insertion error: fd=%d/n", listener);
else
printf("監聽 socket 加入 epoll 成功!/n");
curfds = 1;
while (1)
/* 等待有事件發生 */
nfds = epoll_wait(kdpfd, events, curfds, -1);
if (nfds == -1)
{
perror("epoll_wait");
break;
}
/* 處理所有事件 */
for (n = 0; n < nfds; ++n)
if (events[n].data.fd == listener)
{
new_fd = accept(listener, (struct sockaddr *) &their_addr,&len);
if (new_fd < 0)
perror("accept");
continue;
}
printf("有連接配接來自于: %d:%d, 配置設定的 socket 為:%d/n",
inet_ntoa(their_addr.sin_addr), ntohs(their_addr.sin_port), new_fd);
setnonblocking(new_fd);
ev.events = EPOLLIN | EPOLLET;
ev.data.fd = new_fd;
if (epoll_ctl(kdpfd, EPOLL_CTL_ADD, new_fd, &ev) < 0)
fprintf(stderr, "把 socket '%d' 加入 epoll 失敗!%s/n",
new_fd, strerror(errno));
return -1;
}
curfds++;
}
else
ret = handle_message(events[n].data.fd);
if (ret < 1 && errno != 11)
epoll_ctl(kdpfd, EPOLL_CTL_DEL, events[n].data.fd,&ev);
curfds--;
}
close(listener);
epoll_wait運作的原理是 等侍注冊在epfd上的socket fd的事件的發生,如果發生則将發生的sokct fd和事件類型放入到events數組中。 并且将注冊在epfd上的socket fd的事件類型給清空,是以如果下一個循環你還要關注這個socket fd的話,則需要用epoll_ctl(epfd,EPOLL_CTL_MOD,listenfd,&ev)來重新設定socket fd的事件類型。這時不用EPOLL_CTL_ADD,因為socket fd并未清空,隻是事件類型清空。這一步非常重要。
二、第二個示例:
1. Epoll是何方神聖?
Epoll可是目前在Linux下開發大規模并發網絡程式的熱門人選,Epoll 在Linux2.6核心中正式引入,和select相似,其實都I/O多路複用技術而已,并沒有什麼神秘的。
其實在Linux下設計并發網絡程式,向來不缺少方法,比如典型的Apache模型(Process Per Connection,簡稱PPC),TPC(Thread PerConnection)模型,以及select模型和poll模型,那為何還要再引入Epoll這個東東呢?那還是有得說說的…
2. 常用模型的缺點
如果不擺出來其他模型的缺點,怎麼能對比出Epoll的優點呢。
2.1 PPC/TPC模型
這兩種模型思想類似,就是讓每一個到來的連接配接一邊自己做事去,别再來煩我。隻是PPC是為它開了一個程序,而TPC開了一個線程。可是别煩我是有代價的,它要時間和空間啊,連接配接多了之後,那麼多的程序/線程切換,這開銷就上來了;是以這類模型能接受的最大連接配接數都不會高,一般在幾百個左右。
2.2 select模型
1. 最大并發數限制,因為一個程序所打開的FD(檔案描述符)是有限制的,由FD_SETSIZE設定,預設值是1024/2048,是以Select模型的最大并發數就被相應限制了。自己改改這個FD_SETSIZE?想法雖好,可是先看看下面吧…
2. 效率問題,select每次調用都會線性掃描全部的FD集合,這樣效率就會呈現線性下降,把FD_SETSIZE改大的後果就是,大家都慢慢來,什麼?都逾時了??!!
3. 核心/使用者空間 記憶體拷貝問題,如何讓核心把FD消息通知給使用者空間呢?在這個問題上select采取了記憶體拷貝方法。
2.3 poll模型
基本上效率和select是相同的,select缺點的2和3它都沒有改掉。
3. Epoll的提升
把其他模型逐個批判了一下,再來看看Epoll的改進之處吧,其實把select的缺點反過來那就是Epoll的優點了。
3.1. Epoll沒有最大并發連接配接的限制,上限是最大可以打開檔案的數目,這個數字一般遠大于2048, 一般來說這個數目和系統記憶體關系很大,具體數目可以cat /proc/sys/fs/file-max察看。
3.2. 效率提升,Epoll最大的優點就在于它隻管你“活躍”的連接配接,而跟連接配接總數無關,是以在實際的網絡環境中,Epoll的效率就會遠遠高于select和poll。
3.3. 記憶體拷貝,Epoll在這點上使用了“共享記憶體”,這個記憶體拷貝也省略了。
4. Epoll為什麼高效
Epoll的高效和其資料結構的設計是密不可分的,這個下面就會提到。
首先回憶一下select模型,當有I/O事件到來時,select通知應用程式有事件到了快去處理,而應用程式必須輪詢所有的FD集合,測試每個FD是否有事件發生,并處理事件;代碼像下面這樣:
int res = select(maxfd+1, &readfds, NULL, NULL, 120);
if(res > 0)
{
for(int i = 0; i < MAX_CONNECTION; i++)
if(FD_ISSET(allConnection[i],&readfds))
{
handleEvent(allConnection[i]);
}
}
// if(res == 0) handle timeout, res < 0 handle error
Epoll不僅會告訴應用程式有I/0事件到來,還會告訴應用程式相關的資訊,這些資訊是應用程式填充的,是以根據這些資訊應用程式就能直接定位到事件,而不必周遊整個FD集合。
intres = epoll_wait(epfd, events, 20, 120);
for(int i = 0; i < res;i++)
handleEvent(events[n]);
5. Epoll關鍵資料結構
前面提到Epoll速度快和其資料結構密不可分,其關鍵資料結構就是:
structepoll_event {
__uint32_t events; // Epoll events
epoll_data_t data; // User datavariable
typedef union epoll_data {
void *ptr;
int fd;
__uint32_t u32;
__uint64_t u64;
} epoll_data_t;
可見epoll_data是一個union結構體,借助于它應用程式可以儲存很多類型的資訊:fd、指針等等。有了它,應用程式就可以直接定位目标了。
6. 使用Epoll
既然Epoll相比select這麼好,那麼用起來如何呢?會不會很繁瑣啊…先看看下面的三個函數吧,就知道Epoll的易用了。
intepoll_create(int size);
生成一個Epoll專用的檔案描述符,其實是申請一個核心空間,用來存放你想關注的socket fd上是否發生以及發生了什麼事件。size就是你在這個Epoll fd上能關注的最大socket fd數,大小自定,隻要記憶體足夠。
intepoll_ctl(int epfd, intop, int fd, structepoll_event *event);
控制某個Epoll檔案描述符上的事件:注冊、修改、删除。其中參數epfd是epoll_create()建立Epoll專用的檔案描述符。相對于select模型中的FD_SET和FD_CLR宏。
intepoll_wait(int epfd,structepoll_event * events,int maxevents,int timeout);
等待I/O事件的發生;參數說明:
epfd:由epoll_create() 生成的Epoll專用的檔案描述符;
timeout:等待I/O事件發生的逾時值;
相對于select模型中的select函數。
7. 例子程式
下面是一個簡單Echo Server的例子程式,麻雀雖小,五髒俱全,還包含了一個簡單的逾時檢查機制,簡潔起見沒有做錯誤處理。
//
// a simple echo server using epoll in linux
// 2009-11-05
// 2013-03-22:修改了幾個問題,1是/n格式問題,2是去掉了原代碼不小心加上的ET模式;
// 本來隻是簡單的示意程式,決定還是加上 recv/send時的buffer偏移
// by sparkling
#include <sys/socket.h>
#include <sys/epoll.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <errno.h>
#include <iostream>
using namespace std;
#define MAX_EVENTS 500
struct myevent_s
{
int fd;
void (*call_back)(int fd, int events, void *arg);
int events;
void *arg;
int status; // 1: in epoll wait list, 0 not in
char buff[128]; // recv data buffer
int len, s_offset;
long last_active; // last active time
};
// set event
void EventSet(myevent_s *ev, int fd, void (*call_back)(int, int, void*), void *arg)
ev->fd = fd;
ev->call_back = call_back;
ev->events = 0;
ev->arg = arg;
ev->status = 0;
bzero(ev->buff, sizeof(ev->buff));
ev->s_offset = 0;
ev->len = 0;
ev->last_active = time(NULL);
}
// add/mod an event to epoll
void EventAdd(int epollFd, int events, myevent_s *ev)
struct epoll_event epv = {0, {0}};
int op;
epv.data.ptr = ev;
epv.events = ev->events = events;
if(ev->status == 1){
op = EPOLL_CTL_MOD;
}
else{
op = EPOLL_CTL_ADD;
ev->status = 1;
if(epoll_ctl(epollFd, op, ev->fd, &epv) < 0)
printf("Event Add failed[fd=%d], evnets[%d]\n", ev->fd, events);
else
printf("Event Add OK[fd=%d], op=%d, evnets[%0X]\n", ev->fd, op, events);
// delete an event from epoll
void EventDel(int epollFd, myevent_s *ev)
if(ev->status != 1) return;
epoll_ctl(epollFd, EPOLL_CTL_DEL, ev->fd, &epv);
int g_epollFd;
myevent_s g_Events[MAX_EVENTS+1]; // g_Events[MAX_EVENTS] is used by listen fd
void RecvData(int fd, int events, void *arg);
void SendData(int fd, int events, void *arg);
// accept new connections from clients
void AcceptConn(int fd, int events, void *arg)
struct sockaddr_in sin;
socklen_t len = sizeof(struct sockaddr_in);
int nfd, i;
// accept
if((nfd = accept(fd, (struct sockaddr*)&sin, &len)) == -1)
{
if(errno != EAGAIN && errno != EINTR)
{
printf("%s: accept, %d", __func__, errno);
return;
do
for(i = 0; i < MAX_EVENTS; i++)
if(g_Events[i].status == 0)
{
break;
}
}
if(i == MAX_EVENTS)
printf("%s:max connection limit[%d].", __func__, MAX_EVENTS);
break;
// set nonblocking
int iret = 0;
if((iret = fcntl(nfd, F_SETFL, O_NONBLOCK)) < 0)
{
printf("%s: fcntl nonblocking failed:%d", __func__, iret);
// add a read event for receive data
EventSet(&g_Events[i], nfd, RecvData, &g_Events[i]);
EventAdd(g_epollFd, EPOLLIN, &g_Events[i]);
}while(0);
printf("new conn[%s:%d][time:%d], pos[%d]\n", inet_ntoa(sin.sin_addr),
ntohs(sin.sin_port), g_Events[i].last_active, i);
// receive data
void RecvData(int fd, int events, void *arg)
struct myevent_s *ev = (struct myevent_s*)arg;
int len;
// receive data
len = recv(fd, ev->buff+ev->len, sizeof(ev->buff)-1-ev->len, 0);
EventDel(g_epollFd, ev);
if(len > 0)
ev->len += len;
ev->buff[len] = '\0';
printf("C[%d]:%s\n", fd, ev->buff);
// change to send event
EventSet(ev, fd, SendData, ev);
EventAdd(g_epollFd, EPOLLOUT, ev);
else if(len == 0)
close(ev->fd);
printf("[fd=%d] pos[%d], closed gracefully.\n", fd, ev-g_Events);
printf("recv[fd=%d] error[%d]:%s\n", fd, errno, strerror(errno));
// send data
void SendData(int fd, int events, void *arg)
// send data
len = send(fd, ev->buff + ev->s_offset, ev->len - ev->s_offset, 0);
if(len > 0)
printf("send[fd=%d], [%d<->%d]%s\n", fd, len, ev->len, ev->buff);
ev->s_offset += len;
if(ev->s_offset == ev->len)
// change to receive event
EventDel(g_epollFd, ev);
EventSet(ev, fd, RecvData, ev);
EventAdd(g_epollFd, EPOLLIN, ev);
EventDel(g_epollFd, ev);
printf("send[fd=%d] error[%d]\n", fd, errno);
void InitListenSocket(int epollFd, short port)
int listenFd = socket(AF_INET, SOCK_STREAM, 0);
fcntl(listenFd, F_SETFL, O_NONBLOCK); // set non-blocking
printf("server listen fd=%d\n", listenFd);
EventSet(&g_Events[MAX_EVENTS], listenFd, AcceptConn, &g_Events[MAX_EVENTS]);
// add listen socket
EventAdd(epollFd, EPOLLIN, &g_Events[MAX_EVENTS]);
// bind & listen
sockaddr_in sin;
bzero(&sin, sizeof(sin));
sin.sin_family = AF_INET;
sin.sin_addr.s_addr = INADDR_ANY;
sin.sin_port = htons(port);
bind(listenFd, (const sockaddr*)&sin, sizeof(sin));
listen(listenFd, 5);
int main(int argc, char **argv)
unsigned short port = 12345; // default port
if(argc == 2){
port = atoi(argv[1]);
// create epoll
g_epollFd = epoll_create(MAX_EVENTS);
if(g_epollFd <= 0) printf("create epoll failed.%d\n", g_epollFd);
// create & bind listen socket, and add to epoll, set non-blocking
InitListenSocket(g_epollFd, port);
// event loop
struct epoll_event events[MAX_EVENTS];
printf("server running:port[%d]\n", port);
int checkPos = 0;
while(1){
// a simple timeout check here, every time 100, better to use a mini-heap, and add timer event
long now = time(NULL);
for(int i = 0; i < 100; i++, checkPos++) // doesn't check listen fd
if(checkPos == MAX_EVENTS) checkPos = 0; // recycle
if(g_Events[checkPos].status != 1) continue;
long duration = now - g_Events[checkPos].last_active;
if(duration >= 60) // 60s timeout
close(g_Events[checkPos].fd);
printf("[fd=%d] timeout[%d--%d].\n", g_Events[checkPos].fd, g_Events[checkPos].last_active, now);
EventDel(g_epollFd, &g_Events[checkPos]);
// wait for events to happen
int fds = epoll_wait(g_epollFd, events, MAX_EVENTS, 1000);
if(fds < 0){
printf("epoll_wait error, exit\n");
for(int i = 0; i < fds; i++){
myevent_s *ev = (struct myevent_s*)events[i].data.ptr;
if((events[i].events&EPOLLIN)&&(ev->events&EPOLLIN)) // read event
ev->call_back(ev->fd, events[i].events, ev->arg);
if((events[i].events&EPOLLOUT)&&(ev->events&EPOLLOUT)) // write event
// free resource
return 0;
}