首页 > 代码库 > 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编程