epoll介绍和使用

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

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

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

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;

}

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-10-02 08:30:09

epoll介绍和使用的相关文章

Linux Epoll介绍和程序实例

1. Epoll是何方神圣? Epoll但是当前在Linux下开发大规模并发网络程序的热门人选,Epoll 在Linux2.6内核中正式引入,和select类似,事实上都I/O多路复用技术而已,并没有什么神奇的. 事实上在Linux下设计并发网络程序,向来不缺少方法,比方典型的Apache模型(Process Per Connection,简称PPC),TPC(Thread PerConnection)模型,以及select模型和poll模型,那为何还要再引入Epoll这个东东呢?那还是有得说说

Linux的I/O多路复用机制之--epoll

什么是epoll 按照man手册的说法:是为处理大批量句柄而作了改进的poll.它几乎具备了之前所说的一切优点,被公认为Linux2.6下性能最好的多路I/O就绪通知方法. epoll的相关系统调用 int epoll_create(int size); 创建一个epoll的句柄.自从linux2.6.8之后,size参数是被忽略的.需要注意的是,当创建好epoll句柄后,它就是会占用一个fd值,在linux下如果查看/proc/进程id/fd/,是能够看到这个fd的,所以在使用完epoll后,

基本I/O模型与Epoll简介

5种基本的I/O模型:1)阻塞I/O ;2)非阻塞I/O; 3)I/O复用(select和poll);4)信号驱动I/O(SIGIO);5)异步I/O(POSIX.1的aio_系列函数). 操作系统中一个输入操作一般有两个不同的阶段: 第一:等待数据准备好.第二:从内核到进程拷贝数据.对于一个sockt上的输入操作,第一步一般是等待数据到达网络,当分组到达时,它被拷贝到内核中的某个缓冲区,第二步是将数据从内核缓冲区拷贝到应用程序缓冲区. 一.           阻塞I/O模型 请求无法立即完成

Epoll模型详解

1. 内核中提高I/O性能的新方法epoll epoll是什么?按照man手册的说法:是为处理大批量句柄而作了改进的poll.要使用epoll只需要这三个系统调 用:epoll_create(2), epoll_ctl(2), epoll_wait(2).当然,这不是2.6内核才有的,它是在 2.5.44内核中被引进的(epoll(4) is a new API introduced in Linux kernel 2.5.44)Linux2.6 内核epoll介绍:    先介绍2本书<The

SylixOS 之epoll异常分析

1. SylixOS epoll介绍 SylixOS为了兼容Linux的epoll,创建了epoll的兼容子系统,并支持了epoll的部分功能.SylixOS epoll兼容子系统是由select子系统模拟出来的,所以效率没有select高. 2. epoll异常分析 2.1epoll异常场景 在使用线程A创建AF_UNIX匿名套接字发送数据:线程B把套接字加入epoll监听,且设置属性为一次有效:线程C等待epoll事件产生,并读取套接字中的数据.如程序清单 2-1所示.           

Windows完成端口与Linux epoll技术简介

收藏自:http://www.cnblogs.com/cr0-3/archive/2011/09/09/2172280.html WINDOWS完成端口编程1.基本概念2.WINDOWS完成端口的特点3.完成端口(Completion Ports )相关数据结构和创建4.完成端口线程的工作原理5.Windows完成端口的实例代码Linux的EPoll模型1.为什么select落后2.内核中提高I/O性能的新方法epoll3.epoll的优点4.epoll的工作模式 5.epoll的使用方法6.L

linux epoll 学习

一.epoll介绍 epoll是linux内核为处理大批量句柄而作的改进的poll,是linux下IO多路复用select.poll的增强版,它能显著减少程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率. epoll有两种工作方式:LT(水平触发).ET(边缘触发) LT(level triggered,水平触发)是缺省的工作方式,并且同时支持block和non-block socket,在这种方式中,内核告诉你一个文件描述符是否就绪了,然后你可以对这个就绪的fd进行IO操作.如果你不

用C写一个web服务器(二) I/O多路复用之epoll

.container { margin-right: auto; margin-left: auto; padding-left: 15px; padding-right: 15px } .container::before,.container::after { content: " "; display: table } .container::after { clear: both } .container::before,.container::after { content:

Linux Epoll模型(1) --理论与实践

引言: 相比于select,Epoll最大的好处在于它不会随着监听fd数目的增长而降低效率.因为在内核中的select实现中,它是采用轮询来处理的,轮询的fd数目越多,自然耗时越多.并且,在linux/posix_types.h头文件有这样的声明: #define __FD_SETSIZE    1024 表示select最多同时监听1024个fd,当然,可以通过修改头文件再重编译内核来扩大这个数目,但这似乎治标不治本. 常用模型的特点 Linux 下设计并发网络程序,有典型的Apache模型(