Epoll介绍和使用

Epoll介绍

Epoll 可以使用一次等待监听多个描述符的可读\可写状态。等待返回时携带了可读的描述符或者自定义的数据,使用者据此读取所需的数据后可以再次进入等待。因此不需要为每个描述符创建独立的线程进行阻塞读取,避免了资源浪费的同时又可以获得较快的响应速度。


Epoll的接口

int epoll_create(int max_fds);

创建一个epoll对象的描述符,之后对epoll的操作均使用这个描述符完成.max_fds参数表示此epoll对象可以监听的描述符的最大量.

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

用于管理注册时间的函数.这个函数可以增加/删除/修改/删除事件的注册。

int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);

用于等待的事件到来。当此函数返回时,events数组参数中将会包含产生事件的描述符。

Epoll 用法

1 创建epoll对象
int epfd = epoll_create(MAX_FDS);
2 填充epoll_event对象

接着为每一个需要监控的描述符填充epoll_event结构体,以描述监控事件,并通过epoll_ctl函数将此描述符与epoll_event结构体注册进epoll对象。epoll_event结构体如下

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队列里

epoll_data_t联合体定义如下,当然,同一时间只能使用一个字段:

typedef union epoll_data {
    void *ptr;
    int fd;
    __uint32_t u32;
    __uint64_t u64;
} epoll_data_t;

data字段是一个联合体,他让使用者可以将一些自定义数据加入事件通知中,当此事件发生时,用户设置的data字段将会返回给使用者。在实际使用中经常设置epoll_data.data.fd为监听的文件描述符,事件发生时可以根据epoll_data.data.fd得知引发事件的描述符。当然,也可以设置epoll_data.data.fd为其他便于识别的数据。

填充epoll_event方法如下:

struct epoll_event eventItem;
memset(&eventItem,0,sizeof(eventItem));
eventItem.events = EPOLLIN | EPOLLERR | EPOLLHUP;
eventItem.data.fd = listeningFd;

接下来就可以使用epoll_ctl将事件注册进epoll对象了。

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

epfd:是由epoll_crate()函数所创建的epoll对象的描述符。
op  :EPOLL_CTL_ADD/DEL/MOD三种操作,增加/删除/修改
fd  :表示了需要监听的描述符
event:描述监听事件的详细信息的epoll_event结构体

注册方法如下:

result  = epoll_ctl(epfd,EPOLL_CTL_ADD,listeningFd,&eventItem);

重复这个步骤可以将多个文件描述符的多种事件监听注册到epoll对象中.完成监听的注册之后,便可以通过epoll_wait()函数等待事件到来。

3 使用epoll_wait()函数等待事件

epoll_wait()函数将会使调用者陷入等待状态,直到其注册的事件发生之后才会返回,并且携带刚刚发生的事件的详细信息。其签名如下:

int epoll_wait(int epfd, struct epoll_event *events, int maxEvents, int timeout);

epfd :是由epoll_crate()函数所创建的epoll对象描述符。
events:是一个epoll_events 数组,此函数返回时,事件的信息将被填充至此。
maxevents :表示此次调用最多可以调用的事件数,当然,events参数必须足够容纳这么多事件。
timeout:表示等待超市的时间

返回值,表示获取了多少个事件。
4 处理事件

epoll_wait 返回后,便可以根据events数组中保存的所有epoll_event结构体的events字段与data字段识别事件的类型与来源。

EPoll的使用步骤总结如下
  1. 通过EPoll_Create 创建epoll对象
  2. 为需要监听的描述符填充epoll_events结构体,并使用epoll_ctl注册到epoll对象中。
  3. 使用epoll_wait等待事件发生
  4. 根据epoll_wait返回的epoll_events结构体数组判断事件的类型与来源进行处理
  5. 继续使用epoll_wait等待事件发生

Epoll 使用示例

#include <iostream>
#include <sys/socket.h>
#include <sys/epoll.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <errno.h>

using namespace std;

#define MAXLINE 5
#define OPEN_MAX 100
#define LISTENQ 20
#define SERV_PORT 5000
#define INFTIM 1000

void setnonblocking(int sock)
{
    int opts;
    opts=fcntl(sock,F_GETFL);
    if(opts<0)
    {
        perror("fcntl(sock,GETFL)");
        exit(1);
    }
    opts = opts|O_NONBLOCK;
    if(fcntl(sock,F_SETFL,opts)<0)
    {
        perror("fcntl(sock,SETFL,opts)");
        exit(1);
    }
}

int main(int argc, char* argv[])
{
    int i, maxi, listenfd, connfd, sockfd,epfd,nfds, portnumber;
    ssize_t n;
    char line[MAXLINE];
    socklen_t clilen;


    if ( 2 == argc )
    {
        if( (portnumber = atoi(argv[1])) < 0 )
        {
            fprintf(stderr,"Usage:%s portnumber/a/n",argv[0]);
            return 1;
        }
    }
    else
    {
        fprintf(stderr,"Usage:%s portnumber/a/n",argv[0]);
        return 1;
    }



    //声明epoll_event结构体的变量,ev用于注册事件,数组用于回传要处理的事件

    struct epoll_event ev,events[20];
    //生成用于处理accept的epoll专用的文件描述符

    epfd=epoll_create(256);
    struct sockaddr_in clientaddr;
    struct sockaddr_in serveraddr;
    listenfd = socket(AF_INET, SOCK_STREAM, 0);
    //把socket设置为非阻塞方式

    //setnonblocking(listenfd);

    //设置与要处理的事件相关的文件描述符

    ev.data.fd=listenfd;
    //设置要处理的事件类型

    ev.events=EPOLLIN|EPOLLET;
    //ev.events=EPOLLIN;

    //注册epoll事件

    epoll_ctl(epfd,EPOLL_CTL_ADD,listenfd,&ev);
    bzero(&serveraddr, sizeof(serveraddr));
    serveraddr.sin_family = AF_INET;
    char *local_addr="127.0.0.1";
    inet_aton(local_addr,&(serveraddr.sin_addr));//htons(portnumber);

    serveraddr.sin_port=htons(portnumber);
    bind(listenfd,(sockaddr *)&serveraddr, sizeof(serveraddr));
    listen(listenfd, LISTENQ);
    maxi = 0;
    for ( ; ; ) {
        //等待epoll事件的发生

        nfds=epoll_wait(epfd,events,20,500);
        //处理所发生的所有事件

        for(i=0;i<nfds;++i)
        {
            if(events[i].data.fd==listenfd)//如果新监测到一个SOCKET用户连接到了绑定的SOCKET端口,建立新的连接。

            {
                connfd = accept(listenfd,(sockaddr *)&clientaddr, &clilen);
                if(connfd<0){
                    perror("connfd<0");
                    exit(1);
                }
                //setnonblocking(connfd);

                char *str = inet_ntoa(clientaddr.sin_addr);
                cout << "accapt a connection from " << str << endl;
                //设置用于读操作的文件描述符

                ev.data.fd=connfd;
                //设置用于注测的读操作事件

                ev.events=EPOLLIN|EPOLLET;
                //ev.events=EPOLLIN;

                //注册ev

                epoll_ctl(epfd,EPOLL_CTL_ADD,connfd,&ev);
            }
            else if(events[i].events&EPOLLIN)//如果是已经连接的用户,并且收到数据,那么进行读入。

            {
                cout << "EPOLLIN" << endl;
                if ( (sockfd = events[i].data.fd) < 0)
                    continue;
                if ( (n = read(sockfd, line, MAXLINE)) < 0) {
                    if (errno == ECONNRESET) {
                        close(sockfd);
                        events[i].data.fd = -1;
                    } else
                        std::cout<<"readline error"<<std::endl;
                } else if (n == 0) {
                    close(sockfd);
                    events[i].data.fd = -1;
                }
                line[n] = '/0';
                cout << "read " << line << endl;
                //设置用于写操作的文件描述符

                ev.data.fd=sockfd;
                //设置用于注测的写操作事件

                ev.events=EPOLLOUT|EPOLLET;
                //修改sockfd上要处理的事件为EPOLLOUT

                //epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev);

            }
            else if(events[i].events&EPOLLOUT) // 如果有数据发送

            {
                sockfd = events[i].data.fd;
                write(sockfd, line, n);
                //设置用于读操作的文件描述符

                ev.data.fd=sockfd;
                //设置用于注测的读操作事件

                ev.events=EPOLLIN|EPOLLET;
                //修改sockfd上要处理的事件为EPOLIN

                epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev);
            }
        }
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值