epoll服务器

来源:互联网 发布:贵州大数据培训 编辑:程序博客网 时间:2024/05/21 13:13

相比select、poll,epoll是I/O多路转接最高效的手段,它几乎具备了之前select、poll的一切优点,被公认为Linux2.6下性能最好的多路I/O就绪通知方法。epoll实现只有epoll_create()、epoll_ctl()、epoll_wait()三个系统调用函数。

1、epoll_create()函数:

int epoll_create(int size);
  • 含义:创建一个epoll句柄,占用一个fd,使用完成epoll需要关闭此fd;
  • 参数:size:从linux2.6.8之后,size参数是被忽略的;
  • 返回值:返回一个epfd.

2.epoll_ctl()函数:

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
  • 含义:事件注册函数,向epoll模型中增加、修改、删除需要监听的fd,并指定对应关心的事件;
  • 参数:
  • epfd:epoll_create返回值;
  • op:表示动作,用三个宏来表示:(1)EPOLL_CTL_ADD:添加新的fd到epfd中;(2)EPOLL_CTL_MOD:修改以及注册fd监听事件;(3)EPOLL_CTL_DEL:从epfd中删除一个fd;
  • fd:需要监听的fd;
  • event:需要监听的事件:struct epoll_event结构如下:
struct epoll_event{    _unint32_t events;//关心的事件    epoll_data_t data;};typedef union epoll_data{    void* ptr;    int fd;    _uint32_t u32;    _uint64_t u64;}epoll_data_t;
  • events可以是以下几个宏的集合:
    EPOLLIN :表示对应的文件描述符可以读(包括对端SOCKET正常关闭);
    EPOLLOUT:表示对应的文件描述符可写;
    EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);
    EPOLLERR:表示对应的文件描述符发生错误;
    EPOLLHUP:表示对应的文件描述符被挂断;
    EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的;
    EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个
    socket的话,需要再次把这个socket加入到EPOLL队列里;

3.epoll_wait()函数:

int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
  • 含义:等待epoll模型中哪些fd的事件就绪;
  • 参数:
  • epfd:epoll句柄;
  • events:结构体数组,输出型参数,epoll将会把发生的事件赋值到events数组中;
  • maxevents:告之内核这个events数组有多大,这个 maxevents的值不能大于创建epoll_create()时的size值;
  • timeout:超时时间(毫秒,0会立即返回,-1将不确定,也有说法说是永久阻塞);
  • 返回值:函数调用成功,返回对应I/O上已准备好的文件描述符数目,如返回0表示已超时。

以上即为epoll最基本的操作,其中其最基本的原理为:

  1. epoll_create()在底层创建一个红黑树与就绪队列,返回一个epoll模型;
  2. epoll_ctl()即在红黑树插入新的结点,即关心的fd,当其事件就绪,采用回调机制激活;
  3. epll_wait()只关心就绪队列中的就绪fd,直接对对应fd进行相应事件处理;
  4. 当我们调用epoll_wait()获得就绪文件描述符时,返回的不是实际的描述符,而是一个代表就绪描述符数量的值,只需要去epoll指定的数组中依次取得相应数量的文件描述符即可,其使用了内存映射(mmap)技术,彻底省掉了这些文件描述符在系统调用时复制的开销。
  5. 于epoll采用基于事件的就绪通知方式。在select/poll中,进程只有在调用一定的方法后,内核才对所有监视的文件描述符进行扫描,而epoll事先通过epoll_ctl()来注册一个文件描述符,一旦基于某个文件描述符就绪时,内核会采用类似callback的回调机制,迅速激活这个文件描述符,当进程调用epoll_wait()时便得到通知。
  6. Epoll的2种工作方式-水平触发(LT)和边缘触发(ET):LT:是epoll缺省的工作方式,并且同时支持block和no-block socket.在这种做法中,内核告诉你一个文件描述符是否就绪了,然后你可以对这个就绪的fd进行IO操作。如果你不作任何操作,内核还是会继续通知,所以,这种模式编程出错误可能性要小一点,传统的select/poll都是这种模型的代表. ET:是高速工作方式,只支持no-block socket,它效率要比LT更高。ET与LT的区别在于,当一个新的事件到来时,ET模式下当然可以从epoll_wait调用中获取到这个事件,可是如果这次没有把这个事件对应的套接字缓冲区处理完,在这个套接字中没有新的事件再次到来时,在ET模式下是无法再次从epoll_wait调用中获取这个事件的。而LT模式正好相反,只要一个事件对应的套接字缓冲区还有数据,就总能从epoll_wait中获取这个事件。因此,LT模式下开发基于epoll的应用要简单些,不太容易出错。而在ET模式下事件发生时,如果没有彻底地将缓冲区数据处理完,则会导致缓冲区中的用户请求得不到响应。

epoll模型的优点高效性体现:

  1. select维护数组,epoll维护红黑树,其增删查改效率较高;
  2. select遍历数组,epoll只遍历就绪队列,时间复杂度为o(1),并且队列只存储就绪结点;
  3. epoll监视的fd数目无上限,由于其由红黑树描述的,可以一直创建;
  4. 其用户与内核采用内存映射机制;
  5. 就绪队列从0开始连续放置就绪的fd;
  6. IO效率不随FD数目增加而线性下降;
  7. 内核微调.

以下实现LT模式下的epoll服务器:

#include <stdio.h>#include <sys/socket.h>#include <netinet/in.h>#include <sys/epoll.h>#include <sys/types.h>#include <string.h>#include <stdlib.h>#include <arpa/inet.h>static void usage(const char* proc){    printf("%s [local_ip] [local_port]\n",proc);}int startup(const char* ip,int port){    int sock=socket(AF_INET,SOCK_STREAM,0);    if(sock < 0)    {        perror("socket");        exit(3);    }    int opt=1;    setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));    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");        close(sock);        exit(4);    }    if(listen(sock,10)<0)    {        perror("listen");        close(sock);        exit(5);    }    return sock;}int main(int argc,char* argv[]){    if(argc != 3)    {        usage(argv[0]);        return 1;    }    int listen_sock=startup(argv[1],atoi(argv[2]));    //创建监听套接字     int efds=epoll_create(1024);             //创建epoll模型     struct epoll_event ev;    ev.events=EPOLLIN;    ev.data.fd=listen_sock;    epoll_ctl(efds,EPOLL_CTL_ADD,listen_sock,&ev);     //将监听套接字加入epoll模型中,并关心其读事件,采用激活回调机制     struct epoll_event fds[1024];    //定义共享内存就绪队列大小     int maxnum=1024;    //定义最多可以监听多少个事件     int timeout=500;    while(1)    {        int nums=epoll_wait(efds,fds,maxnum,timeout);    //一段时间内等待一组文件描述符的事件(实际则直接遍历就绪队列)         switch(nums)        {            case -1:                perror("epoll_wait");                break;            case 0:                printf("timeout\n");                break;            default:                {                    int i=0;                    for( ;i<nums;++i)                    {                        int sock=fds[i].data.fd;                        if(sock==listen_sock && fds[i].events&EPOLLIN)   //监听套接字就绪                         {                            struct sockaddr_in client;                            socklen_t len=sizeof(client);                            int new_sock=accept(sock,(struct sockaddr*)&client,&len);  //建立连接                             if(new_sock<0)                            {                                perror("accept");                                continue;                            }                            ev.events=EPOLLIN;                            ev.data.fd=new_sock;                            epoll_ctl(efds,EPOLL_CTL_ADD,new_sock,&ev);     //将新套接字加入efds模型关心其读事件                         }                        else if(fds[i].events & EPOLLIN)   //nomal sock ready    //读事件就绪                         {                            char buf[1024];                            ssize_t s=read(sock,buf,sizeof(buf)-1);                            if(s>0)                            {                                buf[s]=0;                                printf("client# %s\n");                                ev.events=EPOLLOUT;                                ev.data.fd=sock;                                epoll_ctl(efds,EPOLL_CTL_MOD,sock,&ev);     //读成功关心其写事件                             }                            else if(s==0)                            {                                printf("client quit!!!\n");                                close(sock);                                epoll_ctl(efds,EPOLL_CTL_DEL,sock,NULL);                            }                            else                            {                                perror("read");                                continue;                            }                        }                        else if(fds[i].events & EPOLLOUT)   //写事件就绪                         {                            char* msg="I am server!!!\n";                            ssize_t s=write(sock,msg,strlen(msg));                            if(s<0)                            {                                perror("write");                                continue;                            }                            ev.events=EPOLLIN;                            ev.data.fd=sock;                            epoll_ctl(efds,EPOLL_CTL_MOD,sock,&ev);   //写成功关心其读事件                         }                    }                }                break;        }    }    close(listen_sock);    return 0;}
原创粉丝点击