您的位置:首页 > 运维架构 > Linux

Linux Epoll介绍和程序实例

2011-04-26 11:29 281 查看
1. Epoll
是何方神圣?

Epoll
可是当前在
Linux
下开发大规模并发网络程序的热门人选,
Epoll

Linux2.6
内核中正式引入,和
select
相似,其实都
I/O

多路复用技术而已

,并没有什么神秘的。

其实在
Linux
下设计并发网络程序,向来不缺少方法,比如典型的
Apache
模型(
Process Per Connection
,简称
PPC
),
TPC

Thread Per
Connection
)模型,以及
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
集合。

int

res = epoll_wait(epfd, events, 20, 120);

for
(int
i = 0; i < res;i++)

{

handleEvent(events
);

}

5. Epoll
关键数据结构

前面提到
Epoll
速度快和其数据结构密不可分,其关键数据结构就是:

struct

epoll_event {

__uint32_t events;
// Epoll events

epoll_data_t data;
// User data
variable

};

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
的易用了。

int

epoll_create(int
size);

生成一个
Epoll
专用的文件描述符,其实是申请一个内核空间,用来存放你想关注的
socket fd
上是否发生以及发生了什么事件。
size
就是你在这个
Epoll fd
上能关注的最大
socket fd
数,大小自定,只要内存足够。

int

epoll_ctl(int
epfd, int
op, int
fd, struct
epoll_event *event
);

控制某个
Epoll
文件描述符上的事件:注册、修改、删除。其中参数
epfd

epoll_create()
创建
Epoll
专用的文件描述符。相对于
select
模型中的
FD_SET

FD_CLR
宏。

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
事件发生的超时值;

返回发生事件数。

相对于
select
模型中的
select
函数。

7.
epoll的两种工作模式LT/ET

EPOLLLT

完全靠kernel
epoll驱动,应用程序只需要处理从epoll_wait返回的fds,应用程序可以根据需要,执行读取/写入操作一次或多次

此模式下,系统默认所有的fds都是空闲的,只有epoll_wait通知的fds是忙碌的,所以应用系统只需要处理这些fds就可以了

EPOLLET

主要靠应用程序处理fds,应用程序从epoll_wait只能得到哪些fds是由空闲变为忙碌状态。此时应用程序需要自己维护一张fds的表格,把从
epoll_wait获得的状态变化信息登记到这张表格。然后应用程序可以选择遍历这张fds的表格,对处于忙碌状态的fds进行操作。

当读取/写入操作遇到EAGAIN的错误,就表示这个fd由忙碌状态变为空闲状态,在下一次epoll_wait调用之前如果有数据进来或者这个fd的写缓冲区又空闲了,那么epoll_wait会再次通知应用程序,这个fd从空闲状态变为忙碌状态。

此模式下,系统仅仅通知应用程序哪些fds变成了忙碌状态,一旦fd变成忙碌状态,epoll将不再关注这个fd的任何状态信息,直到应用程序通过读写操作触发EAGAIN状态,epoll认为这个fd又变为空闲状态,那么epoll又重新关注这个fd的状态变化。

因此EPOLLET比EPOLLLT对应用程序的要求更多,需要程序员设计的部分也更多,看上去EPOLLLT要简单的多。但是如果这里我们要求对fd有
超时控制,EPOLLLT需要有额外的fds遍历操作,而EPOLLET本来就需要不断遍历fds,如此看来使用EPOLLET是更好的选
择,EPOLLLT才是设计不够完善的小玩具。

而且由于epoll_wait每次返回的fds的数量有限,在大并发的模式下,EPOLLLT将非常的繁忙,所有的fds都要在它的队列中产生状态消息,而每次只有其中一部分fds被返回给应用程序。

相对于EPOLLET,只要epoll_wait返回一次fds之后,这些fds就从epoll队列中消除,只有应用程序遇到EAGAIN之后fd才会重
新添加到epoll队列,如此看来随着epoll_wait的返回,队列中的fds是在减少的,所以在大并发的系统中,EPOLLET更有优势。但是对程
序员的要求也更高。

8.
例子程序

下面是一个简单
Echo Server
的例子程序,麻雀虽小,五脏俱全,还包含了一个简单的超时检查机制,简洁起见没有做错误处理。

1. //
2. // a simple echo server using epoll in linux
3. //
4. // 2009-11-05
5. // by sparkling
6. //
7. #include <sys/socket.h>
8. #include <sys/epoll.h>
9. #include <netinet/in.h>
10. #include <arpa/inet.h>
11. #include <fcntl.h>
12. #include <unistd.h>
13. #include <stdio.h>
14. #include <errno.h>
15. #include <iostream>
16. using namespace std;
17. #define MAX_EVENTS 500
18. struct myevent_s
19. {
20.     int fd;
21.     void (*call_back)(int fd, int events, void *arg);
22.     int events;
23.     void *arg;
24.     int status; // 1: in epoll wait list, 0 not in
25.     char buff[128]; // recv data buffer
26.     int len;
27.     long last_active; // last active time
28. };
29. // set event
30. void EventSet(myevent_s *ev, int fd, void (*call_back)(int, int, void*), void *arg)
31. {
32.     ev->fd = fd;
33.     ev->call_back = call_back;
34.     ev->events = 0;
35.     ev->arg = arg;
36.     ev->status = 0;
37.     ev->last_active = time(NULL);
38. }
39. // add/mod an event to epoll
40. void EventAdd(int epollFd, int events, myevent_s *ev)
41. {
42.     struct epoll_event epv = {0, {0}};
43.     int op;
44.     epv.data.ptr = ev;
45.     epv.events = ev->events = events;
46.     if(ev->status == 1){
47.         op = EPOLL_CTL_MOD;
48.     }
49.     else{
50.         op = EPOLL_CTL_ADD;
51.         ev->status = 1;
52.     }
53.     if(epoll_ctl(epollFd, op, ev->fd, &epv) < 0)
54.         printf("Event Add failed[fd=%d]/n", ev->fd);
55.     else
56.         printf("Event Add OK[fd=%d]/n", ev->fd);
57. }
58. // delete an event from epoll
59. void EventDel(int epollFd, myevent_s *ev)
60. {
61.     struct epoll_event epv = {0, {0}};
62.     if(ev->status != 1) return;
63.     epv.data.ptr = ev;
64.     ev->status = 0;
65.     epoll_ctl(epollFd, EPOLL_CTL_DEL, ev->fd, &epv);
66. }
67. int g_epollFd;
68. myevent_s g_Events[MAX_EVENTS+1]; // g_Events[MAX_EVENTS] is used by listen fd
69. void RecvData(int fd, int events, void *arg);
70. void SendData(int fd, int events, void *arg);
71. // accept new connections from clients
72. void AcceptConn(int fd, int events, void *arg)
73. {
74.     struct sockaddr_in sin;
75.     socklen_t len = sizeof(struct sockaddr_in);
76.     int nfd, i;
77.     // accept
78.     if((nfd = accept(fd, (struct sockaddr*)&sin, &len)) == -1)
79.     {
80.         if(errno != EAGAIN && errno != EINTR)
81.         {
82.             printf("%s: bad accept", __func__);
83.         }
84.         return;
85.     }
86.     do
87.     {
88.         for(i = 0; i < MAX_EVENTS; i++)
89.         {
90.             if(g_Events[i].status == 0)
91.             {
92.                 break;
93.             }
94.         }
95.         if(i == MAX_EVENTS)
96.         {
97.             printf("%s:max connection limit[%d].", __func__, MAX_EVENTS);
98.             break;
99.         }
100.         // set nonblocking
101.         if(fcntl(nfd, F_SETFL, O_NONBLOCK) < 0) break;
102.         // add a read event for receive data
103.         EventSet(&g_Events[i], nfd, RecvData, &g_Events[i]);
104.         EventAdd(g_epollFd, EPOLLIN|EPOLLET, &g_Events[i]);
105.         printf("new conn[%s:%d][time:%d]/n", inet_ntoa(sin.sin_addr), ntohs(sin.sin_port), g_Events[i].last_active);
106.     }while(0);
107. }
108. // receive data
109. void RecvData(int fd, int events, void *arg)
110. {
111.     struct myevent_s *ev = (struct myevent_s*)arg;
112.     int len;
113.     // receive data
114.     len = recv(fd, ev->buff, sizeof(ev->buff)-1, 0);
115.     EventDel(g_epollFd, ev);
116.     if(len > 0)
117.     {
118.         ev->len = len;
119.         ev->buff[len] = '/0';
120.         printf("C[%d]:%s/n", fd, ev->buff);
121.         // change to send event
122.         EventSet(ev, fd, SendData, ev);
123.         EventAdd(g_epollFd, EPOLLOUT|EPOLLET, ev);
124.     }
125.     else if(len == 0)
126.     {
127.         close(ev->fd);
128.         printf("[fd=%d] closed gracefully./n", fd);
129.     }
130.     else
131.     {
132.         close(ev->fd);
133.         printf("recv[fd=%d] error[%d]:%s/n", fd, errno, strerror(errno));
134.     }
135. }
136. // send data
137. void SendData(int fd, int events, void *arg)
138. {
139.     struct myevent_s *ev = (struct myevent_s*)arg;
140.     int len;
141.     // send data
142.     len = send(fd, ev->buff, ev->len, 0);
143.     ev->len = 0;
144.     EventDel(g_epollFd, ev);
145.     if(len > 0)
146.     {
147.         // change to receive event
148.         EventSet(ev, fd, RecvData, ev);
149.         EventAdd(g_epollFd, EPOLLIN|EPOLLET, ev);
150.     }
151.     else
152.     {
153.         close(ev->fd);
154.         printf("recv[fd=%d] error[%d]/n", fd, errno);
155.     }
156. }
157. void InitListenSocket(int epollFd, short port)
158. {
159.     int listenFd = socket(AF_INET, SOCK_STREAM, 0);
160.     fcntl(listenFd, F_SETFL, O_NONBLOCK); // set non-blocking
161.     printf("server listen fd=%d/n", listenFd);
162.     EventSet(&g_Events[MAX_EVENTS], listenFd, AcceptConn, &g_Events[MAX_EVENTS]);
163.     // add listen socket
164.     EventAdd(epollFd, EPOLLIN|EPOLLET, &g_Events[MAX_EVENTS]);
165.     // bind & listen
166.     sockaddr_in sin;
167.     bzero(&sin, sizeof(sin));
168.     sin.sin_family = AF_INET;
169.     sin.sin_addr.s_addr = INADDR_ANY;
170.     sin.sin_port = htons(port);
171.     bind(listenFd, (const sockaddr*)&sin, sizeof(sin));
172.     listen(listenFd, 5);
173. }
174. int main(int argc, char **argv)
175. {
176.     short port = 12345; // default port
177.     if(argc == 2){
178.         port = atoi(argv[1]);
179.     }
180.     // create epoll
181.     g_epollFd = epoll_create(MAX_EVENTS);
182.     if(g_epollFd <= 0) printf("create epoll failed.%d/n", g_epollFd);
183.     // create & bind listen socket, and add to epoll, set non-blocking
184.     InitListenSocket(g_epollFd, port);
185.     // event loop
186.     struct epoll_event events[MAX_EVENTS];
187.     printf("server running:port[%d]/n", port);
188.     int checkPos = 0;
189.     while(1){
190.         // a simple timeout check here, every time 100, better to use a mini-heap, and add timer event
191.         long now = time(NULL);
192.         for(int i = 0; i < 100; i++, checkPos++) // doesn't check listen fd
193.         {
194.             if(checkPos == MAX_EVENTS) checkPos = 0; // recycle
195.             if(g_Events[checkPos].status != 1) continue;
196.             long duration = now - g_Events[checkPos].last_active;
197.             if(duration >= 60) // 60s timeout
198.             {
199.                 close(g_Events[checkPos].fd);
200.                 printf("[fd=%d] timeout[%d--%d]./n", g_Events[checkPos].fd, g_Events[checkPos].last_active, now);
201.                 EventDel(g_epollFd, &g_Events[checkPos]);
202.             }
203.         }
204.         // wait for events to happen
205.         int fds = epoll_wait(g_epollFd, events, MAX_EVENTS, 1000);
206.         if(fds < 0){
207.             printf("epoll_wait error, exit/n");
208.             break;
209.         }
210.         for(int i = 0; i < fds; i++){
211.             myevent_s *ev = (struct myevent_s*)events[i].data.ptr;
212.             if((events[i].events&EPOLLIN)&&(ev->events&EPOLLIN)) // read event
213.             {
214.                 ev->call_back(ev->fd, events[i].events, ev->arg);
215.             }
216.             if((events[i].events&EPOLLOUT)&&(ev->events&EPOLLOUT)) // write event
217.             {
218.                 ev->call_back(ev->fd, events[i].events, ev->arg);
219.             }
220.         }
221.     }
222.     // free resource
223.     return 0;
224. }


转载自:

1、http://blog.csdn.net/sparkliang/archive/2009/11/05/4770655.aspx

2、http://blog.sina.com.cn/s/blog_544465b00100bkrj.html
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: