I/O多路转接之epoll

由于select/poll存在着高并发下效率低,监视数目受限的缺点,基于poll改进的epoll得以出现。

epoll是Linux内核为处理大批量文件描述符而作了改进的poll,是Linux下多路复用IO接口select/poll的增强版本,它能显著提高程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率。无须遍历整个被侦听的描述符集,只须遍历那些被内核IO事件异步唤醒而加入Ready队列的描述符集合。epoll除了提供select/poll那种IO事件的水平触发(Level Triggered)外,还提供了边缘触发(Edge Triggered),这就使得用户空间程序有可能缓存IO状态,减少epoll_wait/epoll_pwait的调用,提高应用程序效率。


epoll句柄创建函数:
int epfd=int epoll_create(int size);


szie为监听的数目,自从linux2.6.8之后,size参数是被忽略的,一旦创建,便会占用一个fd值,记得在用完epoll之后close。


epoll事件注册函数:
int epoll_ctl(int epfd,int op,int fd,struct epoll_event *event);

epfd为创建的句柄


op表示动作,用三个宏表示:
EPOLL_CTL_ADD:注册新的fd到epfd中。
EPOLL_CTL_MOD:修改已经注册的fd的监听事件。
EPOLL_CTL_DEL:从epfd中删除一个fd。


fd为op动作的对象。


*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_data_t data;  





events可以是以下几个宏的集合:
EPOLLIN :表示对应的文件描述符可以读(包括对端SOCKET正常关闭);
EPOLLOUT:表示对应的文件描述符可以写;
EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);
EPOLLERR:表示对应的文件描述符发生错误;
EPOLLHUP:表示对应的文件描述符被挂断;
EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的。
EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里




等待fd状态:
int epoll_wait(int epfd,struct epoll_event *events,int maxevents,int timeout);

epfd为创建的句柄


*events里存储的是已准备好的fd及其状态


maxevents告诉内核events有多大,不能大于size,如果maxevents=3,就有events[0],events[1],events[2],若maxevents小于同一时间要处理的事件数,则多出来的事件会在下一次wait时处理


timeout为超时时间,为0立即返回,为-1不确定返回时间(也说是永久阻塞)



工作原理:
epoll同样只告知那些就绪的文件描述符,当我们调用epoll_wait()获得就绪文件描述符时,返回的不是实际的描述符,而是一个代表就绪描述符数量的值。我们需要去wait中指定的数组中找准备好的描述符。

工作方式:

epoll分为水平触发(LT)和边缘触发(ET),水平触发是epoll默认的工作方式。

如果我们将描述符准备就绪设置为1,没有准备就绪设置为0。当从0变为1时,内核会告诉你描述符准备好,此时可进行操作,此后只要描述符状态不从1变为0,内核就会一直告诉你描述符准备好,可一直操作描述符,这就是水平触发。而从0变为1时,内核会告诉你描述符准备好,此时可操作,此后直到新的事件到来(0变1),内核不会告诉你描述符是否准备好,这便是边缘触发。如果边缘触发时,没有完全处理完缓冲区的数据,会导致用户的请求得不到相应(毕竟只有一次处理的机会)。

水平触发出错的可能性要小些,但速度也要慢些,支持所有描述符。边缘触发速度快,但易出错,且只支持非阻塞的描述符。

与select/poll不同之处:
select/poll需要将各描述符的状态传入内核中调用相关的函数(select或poll),再从内核中传出来给程序。而epoll在调用内核函数epoll_wait时,直接从之前创建的句柄epfd中拿便是,不需要进内核又出内核。因为各文件描述符的信息都注册在epfd中,可直接用。


server大体框架:

struct epoll_event               event;
struct epoll_event             *events;


epfd=epoll_create(MAX_SIZE);//创建句柄,设置监听的最大数目(受进程能打开的最大的描述符数目影响)出错返回-1

event.events=...;//设置关心的描述符的哪个事件
        event.data.fd=listenfd(监听描述符);//设置关心的描述符

epoll_ctl(epfd,EPOLL_CTL_ADD,listenfd,&event);//出错返回-1

events = calloc (size, sizeof (events));//为events分配内存空间,用于存放已准备好的fd及其状态,不分配会报错

for( ; ; )
{
readynum=epoll_wait(epfd,events,MAXEVENTS,TIMEOUT);//等待描述符状态(此处的MAXEVENTS,TIMEOUT为设置的宏
for(i=0;i<readynum;i++)
{
if ((events[i].events & EPOLLERR)||(events[i].events & EPOLLHUP)||(!(events[i].events & EPOLLIN)))//当发生错误或挂断或不可读时,提示错误
{
......//此处为发生错误要进行的操作
}
else if(listenfd==events[i].data.fd)//当监听描述符准备好,进行以下操作
{

event.events=...;
        event.data.fd=connfd(连接描述符);

epoll_ctl(epfd,EPOLL_CTL_ADD,connfd,&event);//将connfd加入到epfd中


continue;//跳出for(i=0;i<n;i++)当前循环
}
else if(connfd==events[i].data.fd)//由于当listenfd相应时,添加了connfd,故下一次的for循环中有了connfd的状态选择
{
.......//cs数据传输操作
}
}
}

......

close(epfd);//记得关闭epfd

free(events);//记得释放events所占的内存空间

......//其他该关闭的fd及操作

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值