epoll介绍和使用

epoll是linux在2.6内核新增的系统调用,为了更高效地实现多路IO复用。与poll和select相比,它的高效体现在

1、 select和poll都是线性扫描FD(文件描述符)的集合,随着集合的增大,性能自然下降,且不能通过返回值得知那些文件描述符有I/O事件发生,还要再便利一边集合才能找出有I/O事件的文件描述符。而epool只是管理活跃的I/O的FD,不会因为集合增大而性能下降。

2、 select和poll是通过内存拷贝的方式,把FD消息从内核空间拷贝到用户空间,效率地下。epoll是通过共享内存方式。

epool的使用也比较简单。说明如下:

1、创建epoll句柄。

int epoll_create(int size);

参数size是监听句柄的最大数目,它的大小和机器内存有关。返回值是创建的epoll句柄。句柄使用完后调用close()关闭,因为它占用系统的FD。创建epoll句柄的本质是向内核申请空间,用来存放关注的FD集合以及事件。

2、epoll事件注册函数

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

注册想要监听的事件类型。参数epfd是epoll_create创建的句柄。

参数op表示动作,在使用时用三个宏来表示:
EPOLL_CTL_ADD表示注册新的fd到epfd中。
EPOLL_CTL_MOD表示修改已经注册过的fd的监听事件。
EPOLL_CTL_DEL表示从epf中删除fd。

参数fd是要监听的fd。

参数event是向内核注册的监听事件。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)

参数epfd是epoll_create创建的句柄。参数events是从内核得到的事件的集合。参数maxevents告诉内核events的大小,它可以大于epoll_create时指定的size的大小。参数timeout是超时事件,单位为毫秒。

函数返回需要处理的事件的个数,返回0则表示已超时。

epoll事件的触发有两种模式,边沿触发Edge Triggered(ET)和水平Level Triggered(LT)。

ET模式:边沿触发是指当监听的FD状态变换时获得通知。例如接收数据的FD缓存一次接收了2k数据,但是读取时只读取了1k,缓存还有1k数据,但是不会再获得通知。所以采用边沿触发时,如果读取的数据长度等于设定值的话就要继续读取,直到产生EAGAIN。ET模式只支持非阻塞的I/O,以防止一个句柄的读/写操作把处理多个文件描述符的任务饿死。

LT模式:水平触发是默认的模式,它支持阻塞和非阻塞I/O。当I/O就绪时,就会获得通知,如果不对I/O进行处理或没有处理完,内核就会一直通知。

下面结合网上例子,用epoll写一个Echo服务器。实现很简单,把accept、recv、send的I/O操作放到epoll中,有了I/O事件后调用相应的回调函数即可。具体步骤如下:

1、初始化监听FD,把accept事件作为回调函数 ‘AcceptConnection’,等待客户端connect后在服务的调用。

2、在回调函数’ AcceptConnection’中,把accept后的FD注册为等待EPOLLIN,即等待客户端send操作,回调函数为’RecvData‘。

3、客户端写操作(send)后,服务端调用了’RecvData’,在这个函数中,把对应的FD的I/O事件改为EPOLLOUT,回调函数为’SendData’,因为下一步就是向客户端写收到的数据了。

4、在’SendData’函数中,发送接收到的数据,并把对应FD等待事件改为EPOLLIN,重新等待客户端的send操作。

在实现时,用到了封装的数据结构myevent,目的是为了便于操作事件,把事件的回调函数、buffer、上一次激活事件等做了封装。并使用了全局数组集合来作为事件的集合。

代码如下:

//EchoEpoll.cpp

#include <iostream>
#include <sys/epoll.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <errno.h>
#include <string.h> //bzero Function
#include <stdlib.h>//atoi()

#define MAX_EVENTS 1000

struct myevent
{
public:
    int fd;
    void (*callBack)(int fd, int events, void* arg);
    int events;
    void* arg;
    bool status;//ture represent it has been added to epoll
    char buffer[128];
    int len;//use length
    int offset;//mark from where to send
    long  lastActiveTime;
};

//Global variables
int g_epollFd;//epoll fd
myevent g_Events[MAX_EVENTS+1];//event set. The last one is used to accept
//Set event 
void SetEvent(myevent* ev, int fd, void(*callBack)(int, int , void*), void* arg)
{
    ev->fd=fd;
    ev->callBack=callBack;
    ev->events=0;
    ev->arg=arg;
    ev->status=false;
    bzero(ev->buffer,sizeof ev->buffer);
    ev->offset=0;
    ev->len=0;
    ev->lastActiveTime=time(NULL);
}
//only set event's callback function, don't set it's buffer
void SetEventCallback(myevent* ev, void(*callBack)(int, int ,void*))
{
    ev->callBack=callBack;
}

void AddEvent(int epfd, int events, myevent* ev)
{
    struct epoll_event epv={0,{0}};

    epv.data.ptr=ev;
    epv.events=ev->events=events;

    int op;
    if(ev->status)// have been added
        op=EPOLL_CTL_MOD;
    else
    {
        op=EPOLL_CTL_ADD;
        ev->status=true;
    }
    if(epoll_ctl(epfd,op,ev->fd,&epv)<0)
        printf("Event add failed  FD=%d,events=%d\n",ev->fd,events);
    else
        printf("Event add successfully,FD=%d,op=%d,events=%0x\n",ev->fd,op,events);

}

void DelEvent(int epfd, myevent* ev)
{
    struct epoll_event epv={0,{0}};
    if(!ev->status)//have not been added
        return;
    epv.data.ptr=ev;
    ev->status=false;
    epoll_ctl(epfd,EPOLL_CTL_DEL,ev->fd,&epv);
}

//forward declation
void RecvData(int fd, int events, void* arg);
void SendData(int fd, int events, void* arg);
void AcceptConnection(int fd, int events, void* arg)
{
    struct sockaddr_in sin;
    socklen_t len=sizeof(struct sockaddr_in);

    int nfd=accept(fd,(struct sockaddr*)&sin,&len) ;
    if(nfd==-1)
    {
        if(errno!= EAGAIN&&errno!=EINTR)
        {}
        printf("%s, accept, %d",__func__,errno);

    }
    int i;
    do
    {
        for( i=0; i<MAX_EVENTS;++i)//find the first unused Event
        {
            if(g_Events[i].status==false)
                break;
        }
        if(i==MAX_EVENTS)
        {
            printf("%s:max connection limit %d\n",__func__,MAX_EVENTS );
            break;//break do-while

        }
        int iret=fcntl(nfd,F_SETFL, O_NONBLOCK);
        if(iret<0)
        {
            printf("%s, fctl nonblocking failed:%d",__func__, iret);
            break;//break do-while
        }
        //set nfd, wait to receive data
        SetEvent(&g_Events[i], nfd, RecvData,&g_Events[i]);
        AddEvent(g_epollFd,EPOLLIN,&g_Events[i]);
    }while(0);
    printf("new connection[%s:%d] [time:%d], pos[%d]\n",inet_ntoa(sin.sin_addr),ntohs(sin.sin_port),g_Events[i].lastActiveTime,i);

}

void RecvData(int fd, int events, void* arg)
{
    struct  myevent* ev=(struct myevent*)arg;
    int len=recv(fd,ev->buffer+ev->len,sizeof(ev->buffer)-1-ev->len,0);
    //has been received data, delete recv event
    DelEvent(g_epollFd,ev);
    if(len>0)
    {
        ev->len+=len;
        ev->buffer[len]='\0';
        printf("C[%d]:%s\n",fd,ev->buffer);
        SetEventCallback(ev,SendData);
        //ev->callBack=SendData;
        AddEvent(g_epollFd,EPOLLOUT,ev);
    }
    else if(len==0)
    {
        close(ev->fd);
        printf("[fd=%d] pos[%d], closed gracefully.\n",fd, ev->events);
    }
    else
    {
        close(ev->fd);
        printf("recv[fd=%d] errno[%d]:%s\n",fd,errno,strerror(errno));
    }
}

void SendData(int fd, int events, void* arg)
{
    struct myevent* ev=(struct myevent*)arg;
    int len=send(fd, ev->buffer+ev->offset, ev->len-ev->offset, 0);
    if(len>0)
    {
        printf("send [fd=%d],[%d<->%d]%s\n",fd,len,ev->len,ev->buffer);
        ev->offset+=len;
        if(ev->offset==ev->len)// all data has been send
        {
            DelEvent(g_epollFd,ev);
            SetEvent(ev,fd,RecvData,ev);//reset event
            AddEvent(g_epollFd,EPOLLIN,ev);
        }
    }
    else
    {
        close(ev->fd);
        DelEvent(g_epollFd,ev);
        printf("send[fd=%d] errno[%d]\n",fd,errno);
    }
}

void InitListenSocket(int epfd, short port)
{
    int listenFd=socket(AF_INET, SOCK_STREAM,0);
    fcntl(listenFd, F_SETFL, O_NONBLOCK);//Set listen socket to Non-block
    printf("server listen fd=%d\n",listenFd);
    SetEvent(&g_Events[MAX_EVENTS], listenFd, AcceptConnection, &g_Events[MAX_EVENTS]);
    AddEvent(epfd, EPOLLIN,&g_Events[MAX_EVENTS]);

    sockaddr_in sin;
    bzero(&sin, sizeof(sin));
    sin.sin_family=AF_INET;
    sin.sin_addr.s_addr=INADDR_ANY;
    sin.sin_port=htons(port);
    bind(listenFd,(const sockaddr*)&sin, sizeof(sin));
    listen(listenFd,MAX_EVENTS);
}
int main(int argc, char* argv[])
{
    unsigned short port=12345;
    if(argc==2)
    {
        port=atoi(argv[1]);
    }

    g_epollFd=epoll_create(MAX_EVENTS);
    if(g_Events<=0)
        printf("Create epoll failed.\n");
    InitListenSocket(g_epollFd,port);
    printf("Server running:port %d\n",port);

    struct epoll_event events[MAX_EVENTS];
    int checkPos=0;
    while(true)
    {
        long now=time(NULL);
        for(int i=0; i< 100; ++i, checkPos++)
        {
            if(checkPos==MAX_EVENTS)
                checkPos=0;
            if(g_Events[checkPos].status==false)
                continue;
            long duration=now - g_Events[checkPos].lastActiveTime;
            //close time out fd
            if(duration>=60)//timeout
            {
                close(g_Events[checkPos].fd);
                printf("fd=%d timeout:%d--%d\n",g_Events[checkPos].fd, g_Events[checkPos].lastActiveTime , now);
                DelEvent(g_epollFd,&g_Events[checkPos]);

            }
        }

        int fds=epoll_wait(g_epollFd,events,MAX_EVENTS,1000);
        if(fds<0)
        {
            printf("epoll_wait error\n");
            break;
        }

        for(int i=0; i<fds;++i)
        {
            myevent* ev=(struct myevent*)events[i].data.ptr;
            if((events[i].events&EPOLLIN)&&(ev->events&EPOLLIN))
            {
                ev->callBack(ev->fd, events[i].events, ev->arg);
            }
            if((events[i].events&EPOLLOUT)&&(ev->events&EPOLLOUT))
            {
                ev->callBack(ev->fd, events[i].events, ev->arg);
            }
        }
    }

    return 0;
}

测试客户端如下:

//EchoClient.cpp
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <string.h>

int main(int argc, char* argv[])
{
    int fd=socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
    if(fd<0)
    {
        perror("Create Scoket Error\n");
        exit(EXIT_FAILURE);
    }

    struct sockaddr_in serverAddr;
    bzero(&serverAddr, sizeof serverAddr);
    serverAddr.sin_family=AF_INET;
    serverAddr.sin_port=htons(12345);
    serverAddr.sin_addr.s_addr=inet_addr("127.0.0.1");
    if(connect(fd, (struct sockaddr*)&serverAddr, sizeof serverAddr)<0)
    {
        perror("Connect Error\n");
        exit(EXIT_FAILURE);
    }

    char sendBuf[128]={0};
    char recvBuf[128]={0};

    while(fgets(sendBuf, sizeof sendBuf, stdin) != NULL)
    {
        write(fd, sendBuf, strlen(sendBuf));
        read(fd, recvBuf, sizeof recvBuf);

        fputs(recvBuf, stdout);

        bzero(sendBuf, sizeof sendBuf);
        bzero(recvBuf, sizeof recvBuf);
    }

    close(fd);

    return 0;

}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值