IO复用之epoll

select和poll在使用上方式类似,缺点较明显。而epoll是Linux特有的IO复用函数,在使用上与前两者有很大不同。epoll的工作原理可以概括为:三个函数,两种模式。三个函数是epoll_create(), epoll_ctl(),epoll_wait()。两种模式是  ET和LT

epoll_create()

int  epoll_create(int size);
创建一个epoll的句柄,size用来告诉内核这个监听的数目一共有多大。
调用epoll_create()建立一个epoll对象(在epoll文件系统中为这个句柄对象分配资源)
当某一进程调用epoll_create方法时,Linux内核会创建一个eventpoll结构体

epoll_ctl()

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
epoll的事件注册函数,它不同于select()是在监听事件时(epoll使用epoll_wait监听)告诉内核要监听什么
类型的事件,而是在这里先注册要监听的事件类型。第一个参数是epoll_create()的返回值,第二个参数表示动
作,用三个宏来表示:
EPOLL_CTL_ADD:注册新的fd到epfd中;
EPOLL_CTL_MOD:修改已经注册的fd的监听事件;
EPOLL_CTL_DEL:从epfd中删除一个fd;
第三个参数是需要监听的fd,第四个参数是告诉内核需要监听什么事,对应结构体如下
struct epoll_event {
    __uint32_t events; /* Epoll events */
    epoll_data_t data; /* User data variable */
};
其中events成员描述事件类型。epoll支持的事件类型和poll基本相同。表示epoll事件类型的宏是在poll对应的
宏前加上“E",比如epoll的数据可读事件是EPOLLIN。但epoll有两个额外的事件类型--EPOLLET 
和EPOLLONESHOT.它们对于epoll的高效运作非常关键。data 成员用于存储用户数据,其类型epoll_data_t的定
义如下:
typedef union epoll_data {
    void *ptr;
    int fd;
    __uint32_t u32;
    __uint64_t u64;
} epoll_data_t;

epoll_wait()

int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
等待事件的产生,类似于select()调用。

第一个参数表示内核中事件表的文件描述符;

参数events用来从内核得到事件的集合,如果检测到事件,就将所有就绪的事件从内核事件表复制到它指向的数组中,与select和poll不同;

maxevents告之内核这个events有多大,最多监听多少个事件,这个 maxevents的值不能大于创建epoll_create()时的size;
参数timeout是超时时间(毫秒,0会立即返回,-1是阻塞)。
该函数返回需要处理的事件数目,如返回0表示超时。

LT

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

ET

ET (edge-triggered)是高速工作方式,只支持no-block socket。
在这种模式下当描述符从未就绪变为就绪时,内核通过epoll告诉你,并且不会再为那个文件描述符发送更多的就
绪通知,直到做了某些操作导致那个文件描述符不再为就绪状态了(比如发送,接收或者接收请求,或者发送接
收的数据少于一定量时导致了一个EWOULDBLOCK错误)。
如果一直不对这个fd作IO操作(从而导致它再次变成未就绪),内核不会发送更多的通知(only once)。

epoll特点

(1)支持一个进程打开大数目的socket描述符(FD),它所支持的FD上限是最大可以打开文件的数目,这个数字在1GB内存的机器上大约是10万左右,具体数目可以cat /proc/sys/fs/file-max查看,一般来说这个数目和系统内存关系很大。

(2)IO 效率不随FD数目增加而线性下降。传统的select/poll另一个致命弱点就是当你拥有一个很大的socket集合,任一时间只有部分的socket是"活跃"的, 但是select/poll每次调用都会线性扫描全部的集合,导致效率呈现线性下降。

但是epoll不存在这个问题,它只会对"活跃"的socket进行 操作---这是因为在内核实现中epoll是根据每个fd上面的callback函数实现的。只有"活跃"的socket才会主动的去调用 callback函数,其他idle状态socket则不会。

在某些情况下,如果所有的socket基本上都是活跃的---比如一个高速LAN环境,epoll并不比select/poll有什么效率;相反效率还可能稍微的下降。

(3)使用mmap加速内核与用户空间的消息传递。这点实际上涉及到epoll的具体实现了。无论是select,poll还是epoll都需要内核把FD消息通知给用户空间,如何避免不必要的内存拷贝就很重要,在这点上,epoll是通过内核与用户空间mmap同一块内存实现的。

使用示例

#include <sys/epoll.h>
#include <stdlib.h>
#include <error.h>
#include <iostream>
#include <unistd.h>
using namespace std;

int main()
{
    //step1: epoll开始工作之前 先把文件描述符纳入epoll监管
    struct epoll_event  listen_fd;
    listen_fd.data.fd=0;
    listen_fd.events=EPOLLIN;    
    int epoll_fd=epoll_create(10);
    epoll_ctl(epoll_fd,EPOLL_CTL_ADD,0,&listen_fd);

    //step2: epoll开始工作 阻塞的等待文件描述符就绪
    struct epoll_event ready_events[10];
    int ret=epoll_wait(epoll_fd,ready_events,10,-1);
    
    //step3:epoll完成工作  看自己感兴趣的套接字是否就绪
    if(ret<0)
    { 
            cout<<"epoll error"<<endl; return -1;
    }
    else if(ret==0)
        {cout<<"spoll time out";return -1;}
    else
    {
        for(int i=0;i<ret;i++)
        {
            if(ready_events[i].data.fd==0)
            {
                char  tmp[10];
                read(0,tmp,10);
                cout<<tmp<<endl;
            }
        }

    }
}

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值