I/O多路转接之epoll

56 篇文章 0 订阅

一、epoll函数
epoll是linux特有的I/O复用函数,它比select和poll要高效的多。epoll用一个事件表来保存用户关心的文件描述符,但是这个事件表需要一个文件描述符来标识。

1、内核事件表

#include<sys/epoll.h>
int epoll_create(int size);

功能:创建一个事件表,并返回这个事件表的文件描述符。这个文件描述符就代表一个epoll模型,用作其他epoll系统调用的第一个参数,以指定要访问的内核事件表。
参数:size参数并不起作用,只是给内核一个提示,告诉它事件表需要多大。

2、操作内核事件表

#include<sys/epoll.h>
int epoll_ctl(int epfd,int op,int fd,struct epoll_event *event);

参数:
epfd:表示要操作的内核事件表的文件描述符。
op:指定操作的类型。操作类型有三种。
EPOLL_CTL_ADD:向事件表中注册fd上的事件。
EPOLL_CTL_MOD:修改fd上的注册事件。
EPOLL_CTL_DEL:删除fd上的注册事件。
fd:是要操作的文件描述符。
event:用来指定事件,它是epoll_event结构指针类型。

struct epoll_event
{
     _uint32_t events;   //epoll事件
     epoll_data_t data;  //用户数据
}

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

typedef union epoll_data
{
     void* ptr;     
     int fd;            
     uint32_t u32;
     uint64_t u64;
}
epoll_ctl成功时返回0,失败时返回-1并设置errno。

3、等待事件发生

#include<sys/epoll.h>
int epoll_wait(int epfd,struct epoll_event* events,int maxevents,int timeout);

功能:该函数成功时返回就绪的文件描述符的个数,失败返回-1并设置errno。
参数:
epfd:要监测的epoll模型,也就是要监测的内核事件表。
events:它表示一个结构体数组,是一个输出型参数,用来获取已经就绪的事件的相关信息。events不可以是空指针,内核只负责把数据复制到这个events数组中,而不会去帮助我们在用户态中分配内存。
maxevents:指明events的大小。这个值不能大于epoll_create的参数size。
timeout:设置超时时间的。

4、LT和ET模式
LT(水平触发)模式是默认的模式,在这种模式下epoll相当于一个效率较高poll。采用LT的工作模式时:当epoll_wait检测到其上有事件发生并将此事件通知应用程序后,应用程序可以不立即处理该事件。这样,当应用程序下一次调用epoll_wait时,epoll_wait还会再次向应用程序通告此事件,直到事件被处理。LT同时支持阻塞和非阻塞方式。
ET:当向epoll内核事件表中注册一个文件描述符上的EPOLLET事件的时,epoll将通过ET(边沿模式)来操作该文件描述符,ET模式是epoll的高效工作模式。对于在ET模式工作的文件描述符来说:当epoll_wait检测到其上有事件发生并将此事件通知应用程序后,应用程序应该立即处理该事件,因为后续的epoll_wait调用将不再向应用程序通知这一事件。ET模式在很大程度上降低了同一个epoll事件被重复触发的次数,所以ET要更高效。使用ET模式的文件描述符都应该是非阻塞的,以避免由于一个文件句柄的阻塞读/阻塞写操作把处理多个文件描述符的任务都饿死。ET只支持非阻塞方式。

二、EPOLL工作原理
1、当调用epoll_wait获得就绪文件描述符时,返回的不是实际的描述符,而是一个代表就绪描述符数量的值,你只需要去epoll指定一个数组依次取得相应数量的文件描述符即可,这里使用了内存映射(mmap)技术,会节省一些文件描述符在系统调用时的复制开销。
2、一但文件描述符就绪,内核会采用类似callback的回调机制,迅速激活这个文件描述符,当进程调用epoll_wait时便得到通知。
3、epoll与select和poll不同,epoll将事件表完全交给内核去管理,用户只需要将要监测的文件描述符添加进入内核表中即可,等到事件就绪后内核会自动将就绪事件的文件描述符激活。
那么在内核中是怎样对这些文件描述符进行监测的呢?又是怎样处理就绪事件的呢?
这里写图片描述

三、epoll的优点
1、内核通过一个事件表直接管理用户感兴趣的所有事件。因此每次调用epoll_wait时,无需反复传入用户感兴趣的事件。
2、epoll采用回调方式来监测就绪事件,算法复杂度为O(1)。epoll_wait返回值是就绪事件的个数。
3、epoll将事件表交给内核去管理,底层用红黑树,效率高。
4、epoll支持ET模式,非常高效。
5、epoll支持的文件描述符最大值一般是65535,比select要优。

例:用epoll方式实现一个服务器,要求用浏览器访问这个服务器后能够简单的在浏览器上输出一句“hello wrold”。
#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;
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值