欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

IO多路转接之epoll

程序员文章站 2022-06-14 11:18:31
...

I/O多路转接之epoll

epoll是在2.6内核中提出的,是之前的select和poll的增强版本。相对于select和poll来说,epoll更加灵活,没有描述符限制。epoll使用一个文件描述符管理多个描述符,将用户关系的文件描述符的事件存放到内核的一个事件表中,这样在用户空间和内核空间的copy只需一次。

epoll接口

#include <sys/epoll.h>
int epoll_create(int size);
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);

(1) int epoll_create(int size);

  创建一个epoll的句柄,size用来告诉内核这个监听的数目一共有多大。这个参数不同于select()中的第一个参数,给出最大监听的fd+1的值。需要注意的是,当创建好epoll句柄后,它就是会占用一个fd值,在linux下如果查看/proc/进程id/fd/,是能够看到这个fd的,所以在使用完epoll后,必须调用close()关闭,否则可能导致fd被耗尽。

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

函数是对指定描述符fd执行op操作。

- epfd:是epoll_create()的返回值。

- op:表示op操作,用三个宏来表示:添加EPOLL_CTL_ADD,删除EPOLL_CTL_DEL,修改EPOLL_CTL_MOD。分别添加、删除和修改对fd的监听事件。

- fd:是需要监听的fd(文件描述符)

- epoll_event:是告诉内核需要监听什么事,struct epoll_event结构如下:

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

epoll_data是一个联合体,其中fd使用的最多,表示指定事件的文件描述符。ptr可以用来指定与fd相关的用户数据。如果要将文件描述符和用户数据关联起来,可以让ptr指向的用户数据这种包含fd。

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);

功能:该函数成功时返回就绪的文件描述符的个数,失败返回-1并设置errno

等待epfd上的io事件,最多返回maxevents个事件。

参数events用来从内核得到事件的集合,maxevents告之内核这个events有多大,这个maxevents的值不能大于创建epoll_create()时的size,参数timeout是超时时间(毫秒,0会立即返回,-1将不确定,也有说法说是永久阻塞)。该函数返回需要处理的事件数目,如返回0表示已超时。

工作模式:

LT(level triggered)是缺省的工作方式,并且同时支持block和no-block socket.在这种做法中,内核告诉你一个文件描述符是否就绪了,然后你可以对这个就绪的fd进行IO操作。如果你不作任何操作,内核还是会继续通知你的,所以,这种模式编程出错误可能性要小一点。传统的select/poll都是这种模型的代表.LT同时支持阻塞和非阻塞方式。

ET(edge-triggered)是高速工作方式,只支持no-block socket。在这种模式下,当描述符从未就绪变为就绪时,内核通过epoll告诉你。然后它会假设你知道文件描述符已经就绪,并且不会再为那个文件描述符发送更多的就绪通知,直到你做了某些操作导致那个文件描述符不再为就绪状态了(比如,你在发送,接收或者接收请求,或者发送接收的数据少于一定量时导致了一个EWOULDBLOCK 错误)。但是请注意,如果一直不对这个fd作IO操作(从而导致它再次变成未就绪),内核不会发送更多的通知(only once),ET模式在很大程度上降低了同一个epoll事件被重复触发的次数,所以ET要更高效。使用ET模式的文件描述符都应该是非阻塞的,以避免由于一个文件句柄的阻塞读/阻塞写操作把处理多个文件描述符的任务都饿死。ET只支持非阻塞方式。

工作原理:

1、当调用epoll_wait获得就绪文件描述符时,返回的不是实际的描述符,而是一个代表就绪描述符数量的值,你只需要去epoll指定一个数组依次取得相应数量的文件描述符即可,这里使用了内存映射(mmap)技术,会节省一些文件描述符在系统调用时的复制开销。

2、一但文件描述符就绪,内核会采用类似callback的回调机制,迅速**这个文件描述符,当进程调用epoll_wait时便得到通知。

3、epoll与select和poll不同,epoll将事件表完全交给内核去管理,用户只需要将要监测的文件描述符添加进入内核表中即可,等到事件就绪后内核会自动将就绪事件的文件描述符**

IO多路转接之epoll

epoll优点:

1、底层采用红黑树给内核管理,增删改查效率高

2、采用回调机制.

3、一旦就绪,放入就绪队列,上层直接从就绪队列读取,算法复杂度为O(1)

4、就绪队列连续存储(遍历快)

5、采用内核映射技术

6、可以采用ET模式.

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<string.h>
#include<sys/epoll.h>
#define _SIZE_ 128
typedef struct epoll_msg    //创建一个结构体类型,里面保存文件描述符,和一个缓冲区 
{
    int fd;
    char buf[_SIZE_];
}epoll_t,*epoll_p,**epoll_pp;

static void* allocator(int fd)
{
     epoll_p buf=(epoll_p)malloc(sizeof(epoll_t));
     if(NULL==buf)
     {
         perror("malloc");
         exit(6);
     }
    buf->fd=fd;
    return buf;
}


void delalloc(void* ptr)
{    
    if(NULL!=ptr)
    {
        free(ptr);
    }
}

int startup(const char* ip,int port)
{
    int sock=socket(AF_INET,SOCK_STREAM,0);
    if(sock<0)
    {
        perror("socket");
        exit(1);
    }

    int opt=1;
    if(setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt))<0)
    {
        perror("setsockopt");
        exit(2);
    }

    struct sockaddr_in local;
    local.sin_family=AF_INET;
    local.sin_port=htons(port);
    local.sin_addr.s_addr=inet_addr(ip);
    if(bind(sock,(struct sockaddr*)&local,sizeof(local))<0)
    {

        perror("bind");
        exit(3);
    }

    if(listen(sock,5)<0)
    {
        perror("listen");
        exit(4);
    }

    return sock;
}



int main(int argc,char* argv[])
{
    if(argc!=3)
    {
        printf("usage:%s ip_local port_local\n",argv[0]);
        return 1;
    }
    int listen_sock=startup(argv[1],atoi(argv[2]));

    int epfd=epoll_create(256);
    if(epfd<0)
    {
        perror("epoll_create");
        exit(5);
    }

    struct epoll_event envs;
    envs.events=EPOLLIN|EPOLLET;
    envs.data.ptr=allocator(listen_sock);
    epoll_ctl(epfd,EPOLL_CTL_ADD,listen_sock,&envs);

    while(1)
    {
        int num=0;
        int timeout=-1;
        struct epoll_event evs[32];
        int max=32;
        switch((num=epoll_wait(epfd,evs,max,timeout)))
        {
        case 0:
            printf("timeout...");
            break;
        case -1:
            perror("epoll_wait");
            break;
        default:
            {
                int i=0;
                for(i=0;i<num;i++)
                {
                    int fd=((epoll_p)(evs[i].data.ptr))->fd;
                    if(fd==listen_sock&&evs[i].events&EPOLLIN)
                    {
                        struct sockaddr_in peer;
                        socklen_t len=sizeof(peer);
                        int connfd=accept(listen_sock,(struct sockaddr*)&peer,&len);
                        if(connfd<0)
                        {
                            perror("accept");
                            continue;
                        }

                        envs.events=EPOLLIN;
                        envs.data.ptr=allocator(connfd);
                        epoll_ctl(epfd,EPOLL_CTL_ADD,connfd,&envs);
                    }  //fi
                    else if(fd!=listen_sock&&evs[i].events&EPOLLIN)
                    {
                        int s=read(fd,((epoll_p)(evs[i].data.ptr))->buf,_SIZE_-1);
                        if(s>0)
                        {
                            char* buf=((epoll_p)(evs[i].data.ptr))->buf;
                            buf[s]=0;
                            printf("client# %s\n",buf);

                            evs[i].events=EPOLLOUT;
                            epoll_ctl(epfd,EPOLL_CTL_MOD,fd,&evs[i]);
                        }
                        else if(s==0)
                        {
                            epoll_ctl(epfd,EPOLL_CTL_DEL,fd,NULL);
                            delalloc(evs[i].data.ptr);
                            evs[i].data.ptr=NULL;
                            close(fd);
                        }
                        else
                        {
                            perror("read");
                        }
                    } //fi
                    else if(fd!=listen_sock&&evs[i].events&EPOLLOUT)
                    {
                        char *msg="http/1.0 200 ok\r\n\r\n<html><h1>hello wrold</h1></html>";
                        write(fd,msg,strlen(msg));
                        delalloc(evs[i].data.ptr);
                        epoll_ctl(epfd,EPOLL_CTL_DEL,fd,&evs[i]);
                    //    close(fd);
                    }
                }//for
            }
        }
    }
    return 0;