首页 > 代码库 > epoll编程
epoll编程
包含头文件:
#include <sys/epoll.h>
epoll的接口非常简单,一共就三个函数:
1. int epoll_create(int size);
创建一个epoll的句柄,size用来告诉内核这个监听的数目一共有多大。
2.int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
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);
等待事件的产生,类似于select()调用。
第一个参数:events用来从内核得到事件的集合,
第二个参数:maxevents告之内核这个events有多大,这个 maxevents的值不能大于创建epoll_create()时的size,
第三个参数timeout是超时时间(毫秒,0会立即返回,-1将不确定,也有说法说是永久阻塞)。
该函数返回需要处理的事件数目,如返回0表示已超时。
4.close(epollfd)
退出时记得释放创建的epoll句柄;
5、关于ET、LT两种工作模式:
可以得出这样的结论:
ET模式仅当状态发生变化的时候才获得通知,这里所谓的状态的变化并不包括缓冲区中还有未处理的数据,也就是说,如果要采用ET模式,需要一直read/write直到出错为止,很多人反映为什么采用ET模式只接收了一部分数据就再也得不到通知了,大多因为这样;
而LT模式是只要有数据没有处理就会一直通知下去的.
二者的差异在于:
level-trigger(LT)模式下只要某个socket处于readable/writable状态,无论什么时候进行epoll_wait都会返回该socket;
edge-trigger(ET)模式下只有某个socket从unreadable变为readable或从unwritable变为writable时,epoll_wait才会返回该socket。
所以,在epoll的ET模式下,正确的读写方式为:
读:只要可读,就一直读,直到返回0,或者 errno = EAGAIN
写:只要可写,就一直写,直到数据发送完,或者 errno = EAGAIN
PS:
epoll工作在ET模式的时候,必须使用非阻塞套接口
//设置socket连接为非阻塞模式void setnonblocking(int sockfd) { int opts; // 得到文件状态标志 opts = fcntl(sockfd, F_GETFL, 0); if(opts < 0) { perror("fcntl(F_GETFL)\n"); exit(1); }
// 设置文件状态标志 opts = (opts | O_NONBLOCK); if(fcntl(sockfd, F_SETFL, opts) < 0) { perror("fcntl(F_SETFL)\n"); exit(1); }}
6.
参考网址:
epoll详解
http://blog.chinaunix.net/uid-24517549-id-4051156.html
高并发的epoll+线程池,epoll在线程池内
http://blog.chinaunix.net/uid-311680-id-2439723.html
基于epoll实现socket编程完整实例
http://blog.chinaunix.net/uid-20771605-id-4596400.html
epoll使用详解(精髓)
http://blog.csdn.net/ljx0305/article/details/4065058
// 生成用于处理accept的epoll专用的文件描述符epfd = epoll_create(65535);memset(&event,0,sizeof(event));event.data.fd = listenfd;event.events = EPOLLIN | EPOLLET;// 注册epoll事件epoll_ctl(epfd,EPOLL_CTL_ADD,listenfd,&event);const int MAX_EVENTS = 1024; // 最大事件数void *epoll_loop(void* para){ int nfds; // 临时变量,存放返回值 int epfd; // 监听用的epoll句柄,epoll_create返回值 struct epoll_event events[MAX_EVENTS]; // 监听事件数组 struct epoll_event event; // struct sockaddr_in client_addr; int i; int connfd; // for(;;) { // 等待epoll事件的发生 nfds = epoll_wait(epfd,events,MAX_EVENTS,-1); // -1: timeout //printf("nfds = %d\n", nfds); // 处理所发生的所有事件 if(nfds > 0) { for(i=0; i<nfds; ++i) { if(events[i].data.fd == listenfd) // 如果新监测到一个SOCKET用户连接到了绑定的SOCKET端口,建立新的连接。 { //while(1) //{ // socklen_t cliaddrlen; connfd = accept(listenfd,(struct sockaddr *)&client_addr, &cliaddrlen); if(connfd > 0) { //cout << "AcceptThread, accept:" << connfd << ",errno:" << errno << ",connect:" << inet_ntoa(cliaddr.sin_addr) << ":" << ntohs(cliaddr.sin_port) << endl; event.data.fd=connfd; event.events = EPOLLIN | EPOLLET; // ET模式 epoll_ctl(epfd,EPOLL_CTL_ADD,connfd,&event); // //注册ev.将新的fd添加到epoll的监听队列中 //fd_Setnonblocking(event.data.fd); //event.events=EPOLLIN|EPOLLET; //epoll_ctl(epfd,EPOLL_CTL_ADD,event.data.fd,&event); } else { //cout << "AcceptThread, accept:" << connfd << ",errno:" << errno << endl; if (errno == EAGAIN) // 没有连接需要接收了 { break; } else if(errno == EINTR) // 可能被中断信号打断,,经过验证对非阻塞socket并未收到此错误,应该可以省掉该步判断 { ; } else // 其它情况可以认为该描述字出现错误,应该关闭后重新监听 { //... } } //} } 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);//修改标识符,等待下一个循环时发送数据,异步处理的精髓 // 或者如下处理: char recvBuf[1024] = {0}; int ret = 999; int rs = 1; while(rs) { ret = recv(events[i].data.fd, recvBuf, 1024, 0);// 接受客户端消息 if(ret < 0) { // 由于是非阻塞的模式,所以当errno为EAGAIN时,表示当前缓冲区已无数据可读,在这里就当作是该次事件已处理过。 if(errno == EAGAIN) { printf("EAGAIN\n"); break; } else { printf("recv error!\n"); epoll_ctl(epfd, EPOLL_CTL_DEL, events[i].data.fd, &event); // 其中 struct epoll_event event; // memset(&event,0,sizeof(event)); // event.data.fd=listenfd; // event.events=EPOLLIN|EPOLLET; close(events[i].data.fd); break; } } else if(ret == 0) { // 这里表示对端的socket已正常关闭. rs = 0; } if(ret == sizeof(recvBuf)) { rs = 1; // 需要再次读取 } else { rs = 0; } } } else if(events[i].events & EPOLLOUT) // 有数据待发送,写socket { //sprintf(buf,"HTTP/1.0 200 OK\r\nContent-type: text/plain\r\n\r\n%s","Hello world!\n"); send(events[i].data.fd,buf,strlen(buf),0); close(events[i].data.fd); } else { close(events[i].data.fd); } //else if (events[i].events & EPOLLERR || events[i].events & EPOLLHUP) // 有异常发生 //{ // //此时说明该描述字已经出错了,需要重新创建和监听 // //close(listenfd); // // epoll_ctl(epfd, EPOLL_CTL_DEL, listenfd, &event); // // 创建监听socket // //listenfd = socket(AF_INET, SOCK_STREAM, 0); // //... //} //epoll_ctl(epfd, EPOLL_CTL_DEL, events[i].data.fd, &event); //close(events[i].data.fd); } } }}
epoll编程