设为首页 收藏本站
查看: 855|回复: 0

[经验分享] Redis系列(三)---事件处理细节分析及epoll介绍

[复制链接]

尚未签到

发表于 2015-7-20 09:52:11 | 显示全部楼层 |阅读模式
    上两篇介绍了redis的启动流程接受客户端请求到调用请求处理函数,在这篇里,我将介绍redis事件触发细节,即epoll介绍。从redis源码可以看出,redis的io模型主要是基于epoll实现的,不过它也提供了 select和kqueue的实现,默认采用epoll。
  ae.c



#ifdef HAVE_EPOLL
#include "ae_epoll.c"
#else
#ifdef HAVE_KQUEUE
#include "ae_kqueue.c"
#else
#include "ae_select.c"
#endif
#endif
通过这么一个条件包含,就可以决定redis使用哪种i/o多路复用函数。同时redis通过ae.h的一系列声明为上层提供了一个统一的接口,以此隐藏底层io多路函数的具体实现。
有关unix io模型的类型,可参考(Unix 五种基本I/O模型的区别) .
  那么epoll到底是个什么东西呢? 其实只是众多i/o多路复用技术当中的一种而已,但是相比其他io多路复用技术(select, poll等等),epoll有诸多优点:
    1. epoll 没有最大并发连接的限制,上限是最大可以打开文件的数目,这个数字一般远大于 2048, 一般来说这个数目和系统内存关系很大 ,具体数目可以 cat /proc/sys/fs/file-max 察看。
    2. 效率提升, Epoll 最大的优点就在于它只管你“活跃”的连接 ,而跟连接总数无关,因此在实际的网络环境中, Epoll 的效率就会远远高于 select 和 poll 。
    3. 内存拷贝, Epoll 在这点上使用了“共享内存 ”,这个内存拷贝也省略了。
  那么在我们的系统中,到底应该如何使用epoll呢? 这里,epoll给我们提供了3个api: epoll_create, epoll_ctl, epoll_wait。
1: int epoll_create(int size);

生成一个 epoll 专用的文件描述符,其实是申请一个内核空间,用来存放你想关注的 socket fd 上是否发生以及发生了什么事件。 size 就是你在这个 epoll fd 上能关注的最大 socket fd 数,大小自定,只要内存足够。

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

  控制某个 epoll 文件描述符上的事件:注册、修改、删除。参数说明:
     epfd 是 epoll_create() 创建 epoll 专用的文件描述符。相对于 select 模型中的 FD_SET 和 FD_CLR 宏;
    op就是你要把当前这个套接口fd如何设置到epfd上边去,一般由epoll提供的三个宏指定:EPOLL_CTL_ADD,EPOLL_CTL_DEL,EPOLL_CTL_MOD。
    fd: 当事件发生时操作的目标套接口。
    event指针就是你要给这个套接口fd绑定什么事件。
  3: int epoll_wait(int epfd,struct epoll_event * events,int maxevents,int timeout);
  等待 I/O 事件的发生;参数说明:
  epfd: 由 epoll_create() 生成的 Epoll 专用的文件描述符;
  epoll_event: 用于回传代处理事件的数组;
  maxevents: 返回的最大事件数;
  timeout: 等待 I/O 事件发生的超时值(毫秒);
  epoll_wait返回触发的事件数。
  
  下面看一个例子:
  
    



kdpfd = epoll_create(1024);
epoll_event lev;
lev.events = EPOLLIN;
epoll_ctl(kdpfd, EPOLL_CTL_ADD, listener,  &lev);
struct epoll_event ev, *events;
for(;;) {
nfds = epoll_wait(kdpfd, events, maxevents, -1);
for(n = 0; n < nfds; ++n) {
if(events[n].data.fd == listener) {
client = accept(listener, (struct sockaddr *) &local,
&addrlen);
if(client < 0){
perror("accept");
continue;
}
setnonblocking(client);
ev.events = EPOLLIN | EPOLLET;
ev.data.fd = client;
if (epoll_ctl(kdpfd, EPOLL_CTL_ADD, client, &ev) < 0) {
fprintf(stderr, "epoll set insertion error: fd=%d0,
                        client);
return -1;
}
}
else
do_use_fd(events[n].data.fd);
}
}
  
    首先,通过epoll_create创建一个epoll实例, 然后声明一个epoll_event lev(这是一个struct,epoll用它来代表事件), 并将该lev的events赋值为EPOLLIN(这样当listener上有数据可读时,那么epoll_wait便会返回该fd), 最后再调用epoll_wait 等待 kdpfd这个epoll实例上事件的发生。当有事件发生(io读写事件)或者到达设定的超时值,那么epoll_wait就会返回,然后我们就可以通过 events拿到相应的socketfd并进行相应的处理。
    例子中是当给listener绑定的可读事件发生时(客户端连接到达),那么就调用accept函数,获取客户端与服务器段的套接字client , 然后给这个套接字绑定 ev.events = EPOLLIN | EPOLLET; 并调用 epoll_ctl函数将该套接字client 加入到epoll实例kdpfd,再次循环进行epoll_wait, 这样,当client有数据可读时(客户端请求数据到达),那么就可以进行下一步处理了,如调用recv/read接受客户端数据,等等。
    epoll_ctl(kdpfd, EPOLL_CTL_ADD, client, &ev)
    从上边的介绍中,我们知道了如何调用epoll提供的api, 生成epoll实例,如何给套接口设置相应事件,如何将套接口添加到epoll实例以及进行事件轮询(epoll_wait)等待相应事件的发生并处理, 再来看redis代码, 就可以对redis接受客户端请求并处理的过程一目了然了。
    
DSC0000.png
  
  如图所示,如果监听的端口有连接到来,那么epoll_wait返回,那么redis会把触发的套接口放到eventLoop.fired这个数组里:
  

1  retval = epoll_wait(state->epfd,state->events,AE_SETSIZE,
2             tvp ? (tvp->tv_sec*1000 + tvp->tv_usec/1000) : -1);
3     if (retval > 0) {
4         int j;
5
6         numevents = retval;
7         for (j = 0; j < numevents; j++) {
8             int mask = 0;
9             struct epoll_event *e = state->events+j;
10
11             if (e->events & EPOLLIN) mask |= AE_READABLE;
12             if (e->events & EPOLLOUT) mask |= AE_WRITABLE;
13             if (e->events & EPOLLERR) mask |= AE_WRITABLE;
14             if (e->events & EPOLLHUP) mask |= AE_WRITABLE;
15             eventLoop->fired[j].fd = e->data.fd;
16             eventLoop->fired[j].mask = mask;
17         }
18     }
  
  
  
    然后在aeProcessEvents这个函数里,会取出eventLoop.fired中的fd,并取出对应的事件:aeFileEvent *fe, 然后判断事件的类型,调用相应的处理函数:
    



if (fe->mask & mask & AE_READABLE) {
rfired = 1;
fe->rfileProc(eventLoop,fd,fe->clientData,mask);
}
if (fe->mask & mask & AE_WRITABLE) {
if (!rfired || fe->wfileProc != fe->rfileProc)
fe->wfileProc(eventLoop,fd,fe->clientData,mask);
}
  
  至此,redis的启动流程,接受客户端请求到调用请求处理函数,以及事件如何触发,如何调用处理函数,在三篇博客里都做了详细的分析。相信结合这三篇博客可以对redis内部的实现有一个较为深刻的理解。
  
  
  

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-88562-1-1.html 上篇帖子: Redis学习笔记(3) List类型值存取 下篇帖子: Redis VS Memcached 转载
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表