前面博客讲了select和poll,但是每次调用时都要将fd集合从用户空间拷贝到内核,并且为了检查哪些fd满足事件,需要遍历所有的fd,其效率是低下的。这样就有了epoll,epoll只需要注意我们所关心的那些fd。
epoll的系统调用有三个函数:
1.创建epoll fd函数
int epoll_create(int size);
创建一个epoll的文件描述符并在内核创建epoll的数据结构,其包含一颗红黑树(需要监听的文件描述符的数据结构)和一个就绪双向链表,size提示内核红黑树的节点个数。自从Linux2.6.8版本以后,size值其实是没什么用的,不过要大于0,因为内核可以动态的分配大小,所以不需要size这个提示了。
epoll中的所有事件,都与网卡驱动程序建立回调关系,当相应的事件发生的时候,会通过这个回调函数,将发生的事件添加到就绪链表当中
2.注册事件函数
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
epoll_ctl函数用于对epoll的红黑树进行操作。
epfd为epoll的文件描述符。
op为所作的操作EPOLL_CTL_ADD,EPOLL_CTL_DEL,EPOLL_CTL_MOD分别是添加,删除,修改。
fd为需要监听的文件描述符。
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,EPOLLOUT,EPOLLERR等表示对应描述符可读,对应描述符可写,对应描述符发生错误。
3.监听事件函数
int epoll_wait(int epfd, struct epoll_event *events,
int maxevents, int timeout);
检查是否有事件发生,只需要检测内核中的就绪双向链表是否有数据,若链表不为空,则将发生的事件复制到用户空间(即events数组),并返回事件的个数。
epfd:epoll的文件描述符
events:传出参数,内核会把发生的事件复制到events数组中
maxevents:告诉内核events数组的元素总个数
timeout: 超时时间(毫秒) ,-1为阻塞,0为非阻塞,>0 设置超时时间
下面通过代码说明epoll最基本的用法:
1 #include<sys/epoll.h>
2 #include<unistd.h>
3 #include<errno.h>
4 #include<ctype.h>
5 #include<arpa/inet.h>
6 #include<stdio.h>
7 #include"wrap.h"
8 #define SERVER_PORT 4000
9 int main(void)
10 {
11 int lfd,cfd,root_fd,ret;
12 struct sockaddr_in ser_addr,clt_addr;
13 socklen_t clt_addr_len = sizeof(clt_addr);
14 struct epoll_event tep,ep[1024];
15 char buf[BUFSIZ];
16
17 lfd = Socket(AF_INET,SOCK_STREAM,0);
18
19 ser_addr.sin_family = AF_INET;
20 ser_addr.sin_port = htons(SERVER_PORT);
21 ser_addr.sin_addr.s_addr = htonl(INADDR_ANY);
22
23 Bind(lfd,(struct sockaddr* )&ser_addr,sizeof(ser_addr));
24
25 Listen(lfd,128);
26
27 root_fd = epoll_create(1024);
28 tep.events = EPOLLIN;
29 tep.data.fd = lfd;
30 epoll_ctl(root_fd,EPOLL_CTL_ADD,lfd,&tep);
31 while(1)
32 {
33 ret = epoll_wait(root_fd,ep,1024,-1);
34 for(int i = 0;i < ret ;i++)
35 {
36 if(ep[i].data.fd == lfd)
37 {
38 cfd = Accept(lfd,(struct sockaddr*)&clt_addr,&clt_addr_len);
39 printf("port = %d go in\n",ntohs(clt_addr.sin_port));
40
41 tep.events = EPOLLIN;
42 tep.data.fd = cfd;
43 epoll_ctl(root_fd,EPOLL_CTL_ADD,cfd,&tep);
44 }
45 else{
46 int n = read(ep[i].data.fd,buf,sizeof(buf));
47 if(n < 0)
48 {
49 if(errno == ECONNRESET)
50 {
51 Close(ep[i].data.fd);
52 epoll_ctl(root_fd,EPOLL_CTL_DEL,ep[i].data.fd,NULL);
53 }
54 else perr_exit("read error");
55 }
56 else if(n == 0)
57 {
58 Close(ep[i].data.fd);
59 epoll_ctl(root_fd,EPOLL_CTL_DEL,ep[i].data.fd,NULL);
60 }
61 else{
62 for(int i = 0; i < n;i++)
63 buf[i] = toupper(buf[i]);
64 Write(ep[i].data.fd,buf,n);
65 }
66 }
67 }
68 }
69 return 0;
70 }
其中wrap.h在这篇博客中有说明