epoll有两种触发模式,水平触发和边沿触发,但是高效率的epoll往往搭配边沿触发和非阻塞。为什么水平触发的效率不高呢?因为系统中一旦有大量不需要读的数据,剩余的数据都会使得epoll_wait函数都会返回,势必会影响效率。而在边沿触发模式下,缓冲区剩余未读尽的数据不会导致epoll_wait返回。
epoll反应堆的事件处理过程
1、准备工作,socket()、bind()、listen();
2、创建一颗监听红黑树,epoll_create();
3、通过函数epoll_ctl()把socket()返回的文件描述符挂在监听树上;
4、while(1);
5、一旦有客户端请求连接epoll_wait()返回满足监听条件的数组;
6、判断返回的是建立连接的请求还是通信数据请求;如果是建立连接请求调用accept(),返回用于通信的文件描述符,并将次文件描述符设置监听读事件挂在树上;如果是通信数据请求,read()读数据,接着处理数据,将文件描述符从树上取下,重新调用epoll_ctl()将文件描述符设置监听写事件,等待epoll_wait()返回,说明可写,将数据写给客户端,又将文件描述符从树上取下,设置监听的读事件,挂上树.......
代码分析
/* 描述就绪文件描述符相关信息 */ struct myevent_s { int fd; //要监听的文件描述符 int events; //对应的监听事件 void *arg; //泛型参数 void (*call_back)(int fd, int events, void *arg); //回调函数 int status; //监听状态,1表示在树上(监听), 0表示不在 char buf[BUFLEN]; int len; long last_active; //记录每次加入红黑树的时间 };
设置listenfd的相关信息
// MAX_EVENTS=1024 eventset(&g_events[MAX_EVENTS], lfd, acceptconn, &g_events[MAX_EVENTS]); void eventset(struct 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; memset(ev->buf, 0, sizeof(ev->buf)); ev->len = 0; ev->last_active = time(NULL); return; }
将listenfd挂在树上
//监听listenfd的读事件 eventadd(efd, EPOLLIN, &g_events[MAX_EVENTS]); void eventadd(int efd, int events, struct myevent_s *ev) { struct epoll_event epv ; int op; epv.data.ptr = ev; epv.events = ev->events = events; //设为EPOLLIN if (ev->status == 0) { //如果没有上树 op = EPOLL_CTL_ADD; //将其加入红黑树 , 并将status置1 ev->status = 1; } }
当有读事件满足,会调用acceptconn
int nfd = epoll_wait(g_efd, events, MAX_EVENTS+1, 1000); for (i = 0; i < nfd; i++) { struct myevent_s *ev = (struct myevent_s *)events[i].data.ptr; if ((events[i].events & EPOLLIN) && (ev->events & EPOLLIN)) //读就绪事件 ev->call_back(ev->fd, events[i].events, ev->arg); } //回调函数具体实现 void acceptconn(int lfd, int events, void *arg) { struct sockaddr_in cin; socklen_t len = sizeof(cin); int cfd, i; cfd = accept(lfd, (struct sockaddr *)&cin, &len) for (i = 0; i < MAX_EVENTS; i++) //从全局数组g_events中找一个空闲元素 if (g_events[i].status == 0) break; //跳出 for if (i == MAX_EVENTS) { //容错处理 } //设置为非阻塞 int flg=fcntl(cfd,F_GETFL); flg|O_NONBLOCK; fcntl(cfd,F_SETFL,flg); /* 给cfd设置一个 myevent_s 结构体, 回调函数 设置为 recvdata */ eventset(&g_events[i], cfd, recvdata, &g_events[i]); eventadd(g_efd, EPOLLIN, &g_events[i]); //将cfd添加到红黑树中,监听读事件 }
当epoll_wait()返回,cfd的读事件满足,调用recvdata()函数
int nfd = epoll_wait(g_efd, events, MAX_EVENTS+1, 1000); for (i = 0; i < nfd; i++) { struct myevent_s *ev = (struct myevent_s *)events[i].data.ptr; if ((events[i].events & EPOLLIN) && (ev->events & EPOLLIN)) //读就绪事件 ev->call_back(ev->fd, events[i].events, ev->arg); } void recvdata(int fd, int events, void *arg) { struct myevent_s *ev = (struct myevent_s *)arg; int len; len = recv(fd, ev->buf, sizeof(ev->buf), 0); //读文件描述符, 数据存入myevent_s成员buf中 eventdel(g_efd, ev); //将该节点从红黑树上摘除 if (len > 0) { ev->len = len; ev->buf[len] = '\0'; //手动添加字符串结束标记 eventset(ev, fd, senddata, ev); //设置该 fd 对应的回调函数为 senddata eventadd(g_efd, EPOLLOUT, ev); //将fd加入红黑树中,监听其写事件 } else if (len == 0) { close(ev->fd); } else { //出错 close(ev->fd); } return; }
当epoll_wait()返回,cfd的写事件满足,调用senddata()函数
int nfd = epoll_wait(g_efd, events, MAX_EVENTS+1, 1000); for (i = 0; i < nfd; i++) { struct myevent_s *ev = (struct myevent_s *)events[i].data.ptr; if ((events[i].events & EPOLLOUT) && (ev->events & EPOLLOUT)) //写就绪事件 ev->call_back(ev->fd, events[i].events, ev->arg); } void senddata(int fd, int events, void *arg) { struct myevent_s *ev = (struct myevent_s *)arg; int len; len = send(fd, “hello”, 6, 0); //直接将数据 回写给客户端。 eventdel(g_efd, ev); //从红黑树中移除 if (len > 0) { eventset(ev, fd, recvdata, ev); //将该fd的 回调函数改为 recvdata eventadd(g_efd, EPOLLIN, ev); //从新添加到红黑树上, 设为监听读事件 } else { close(ev->fd); //关闭链接 } return ; }
如此反复