redis源码解析----epoll的使用

平时做项目,涉及到网络层的都是epoll,前几年发现redis的epoll实现起来非常的精简,好用。因为提供的接口简单,爱并实现的很高效。于是,我就提取出来,直接使用。

今天又打开该文件详细的看看他的实现细节。

首先简单介绍epoll,它是linux内核下的一个高效的处理大批量的文件操作符的一个实现。不仅限于socket fd。

他在超时时间内会唤醒有事件的操作符。其中有两种模式 1、水平触发(Level Triggered)2、边缘触发(Edge Triggered)

简单概括这两种,水平触发是是缺省的工作方式,并且同时支持block和no-block socket.在这种做法中,内核告诉你一个文件描述符是否就绪了,然后你可以对这个就绪的fd进行IO操作。如果你不作任何操作,内核还是会继续通知你的,所以,这种模式编程出错误可能性要小一点。传统的select/poll都是这种模型的代表。

而边缘模式是 有读写等事件,只会通知你一次,直到下一次事件再一次触发。所以,使用该模式的时候,一般情况下比较复杂,要对操作符读取数据到完全为空。才能保证数据不会丢失

epoll 提供了三个接口,

首先通过epoll_create(int maxfds)来创建一个epoll的句柄

之后在你的网络主循环里面,每一帧的调用epoll_wait(int epfd, epoll_event *events, int max events, int timeout)来查询所有的网络接口,看哪一个可以读,哪一个可以写了。

epoll_ctl用来添加/修改/删除需要侦听的文件描述符及其事件。

好了,当我们了解了如何使用这三个函数后,redis ae 做得就是如何友好的使用这三个函数了,并给我们提供方面的接口,让我们只关注 数据包的处理。

首先了解一下ae的结构体eventloop


/* State of an event based program */



typedef struct aeEventLoop {



int maxfd; /* highest file descriptor currently registered */



int setsize; /* max number of file descriptors tracked */



long long timeEventNextId;



time_t lastTime; /* Used to detect system clock skew */



aeFileEvent *events; /* Registered events */



aeFiredEvent *fired; /* Fired events */



aeTimeEvent *timeEventHead;



int stop;



void *apidata; /* This is used for polling API specific data */



aeBeforeSleepProc *beforesleep;



} aeEventLoop;

我们首先只关注epoll相关,maxfd,表示能够注册的最大操作符数,也就是aeFileEvent *events的最大数组,

int setsize; /* max number of file descriptors tracked */

同上,能够分配的最大数组的数量。events 成员保存了我们要注册到epoll里的操作符,以及对该操作符事件到来的时候进行的操作的相关函数,具体看一下起结构体我们就明白了。


/* File event structure */

typedef struct aeFileEvent {

    int mask; /* one of AE_(READABLE|WRITABLE) */

    aeFileProc *rfileProc;

    aeFileProc *wfileProc;

    void *clientData;

} aeFileEvent;

mask表示 我们对改操作符所要关心的时间,比如可读,可写时间的掩码。rfileProc为当我们有可读事件的时候,进行对其回调,wfileProc表示当有可写的事件的时候,进行回调。clientData为函数参数。

一般都是以fd作为aeFileEvent的数组下标,当有fd有事件时候,我们可以直接用fd定位到相应的位置,直接调用相应的函数。

redis 通过提供int aeCreateFileEvent(aeEventLoop *eventLoop, int fd, int mask,

        aeFileProc *proc, void *clientData)
该方法,将fd注册进入。

eventloop中的fired 用来临时保存epoll_wait中要有事件触发的操作符。

相应的结构体为


/* A fired event */

typedef struct aeFiredEvent {

    int fd;

    int mask;

} aeFiredEvent;

有了这个结构体,我们就可以根据fd 找到相应的struct aeFileEvent相对应的的数组元素了。

那么最终是什么时候被填充呢,下面我们就要看epoll_wait函数的调用了。


 retval = epoll_wait(state->epfd,state->events,eventLoop->setsize,

            tvp ? (tvp->tv_sec*1000 + tvp->tv_usec/1000) : -1);

首先关注一下,第二个参数,struct epoll_event
* events

这个结构体是epoll的参数,它是什么样子呢?


//保存触发事件的某个文件描述符相关的数据(与具体使用方式有关)  

  

typedef union epoll_data {  

    void *ptr;  

    int fd;  

    __uint32_t u32;  

    __uint64_t u64;  

} epoll_data_t;  

 //感兴趣的事件和被触发的事件  

struct epoll_event {  

    __uint32_t events; /* Epoll events */  

    epoll_data_t data; /* User data variable */  

};

而redis将该结构体放到了,


typedef struct aeApiState {

    int epfd;

    struct epoll_event *events;

} aeApiState;

内,epfd是epoll_create的返回句柄,events用来保存epoll_wait的的第二个参数结果。能够保存的数目也就是我们之前提到的setSize大小了。

好,当我们调用epoll_wait后,就会有相应的epoll_event填充到state内,那么,我们就要对这些fd进行操作了。

请看代码。




static int aeApiPoll(aeEventLoop *eventLoop, struct timeval *tvp) {

    aeApiState *state = eventLoop->apidata;

    int retval, numevents = 0;



    retval = epoll_wait(state->epfd,state->events,eventLoop->setsize,

            tvp ? (tvp->tv_sec*1000 + tvp->tv_usec/1000) : -1);

    if (retval > 0) {

        int j;



        numevents = retval;

        for (j = 0; j < numevents; j++) {

            int mask = 0;

            struct epoll_event *e = state->events+j;



            if (e->events & EPOLLIN) mask |= AE_READABLE;

            if (e->events & EPOLLOUT) mask |= AE_WRITABLE;

            if (e->events & EPOLLERR) mask |= AE_WRITABLE;

            if (e->events & EPOLLHUP) mask |= AE_WRITABLE;

            eventLoop->fired[j].fd = e->data.fd;

            eventLoop->fired[j].mask = mask;

        }

    }

    return numevents;

}

我们可以清晰的认识到,epoll_wait返回值是本次触发的时间数量,然后将其便利,相应的事件放入到fired中,

紧接着,对fired进行遍历操作


 numevents = aeApiPoll(eventLoop, tvp);

        for (j = 0; j < numevents; j++) {

            aeFileEvent *fe = &eventLoop->events[eventLoop->fired[j].fd];

            int mask = eventLoop->fired[j].mask;

            int fd = eventLoop->fired[j].fd;

            int rfired = 0;



	    /* note the fe->mask & mask & ... code: maybe an already processed

             * event removed an element that fired and we still didn‘t

             * processed, so we check if the event is still valid. */

            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);

            }

            processed++;

        }

    }

这就完成了一次对操作符的操作实现

那么为什么中间还弄了一个fired的临时存储fd的成员呢,多了一次循环操作,我想应该是为了实现kqueue,select,epoll的提供共了一个通用的结构。

是不是很简单?

因此,我在项目中是直接拿来使用的。非常好用方便。

http://blog.chinaunix.net/uid-24517549-id-4051156.html 这篇文章对epoll的使用有很详细的讲解。

更多文章,欢迎访问

http://blog.csdn.net/wallwind

时间: 2024-08-27 17:48:12

redis源码解析----epoll的使用的相关文章

redis源码解析之事件驱动

Redis 内部有个小型的事件驱动,它主要处理两项任务: 文件事件:使用I/O多路复用技术处理多个客户端请求,并返回执行结果. 时间事件:维护服务器的资源管理,状态检查. 主要的数据结构包括文件事件结构体,时间事件结构体,触发事件结构体,事件循环结构体 /* File event structure */ typedef struct aeFileEvent { int mask; /* one of AE_(READABLE|WRITABLE) */ aeFileProc *rfileProc

Redis源码解析——双向链表

相对于之前介绍的字典和SDS字符串库,Redis的双向链表库则是非常标准的.教科书般简单的库.但是作为Redis源码的一部分,我决定还是要讲一讲的.(转载请指明出于breaksoftware的csdn博客) 基本结构 首先我们看链表元素的结构.因为是双向链表,所以其基本元素应该有一个指向前一个节点的指针和一个指向后一个节点的指针,还有一个记录节点值的空间 typedef struct listNode { struct listNode *prev; struct listNode *next;

redis源码解析之内存管理

zmalloc.h的内容如下: 1 void *zmalloc(size_t size); 2 void *zcalloc(size_t size); 3 void *zrealloc(void *ptr, size_t size); 4 void zfree(void *ptr); 5 char *zstrdup(const char *s); 6 size_t zmalloc_used_memory(void); 7 void zmalloc_enable_thread_safeness(v

Redis源码解析之ziplist

Ziplist是用字符串来实现的双向链表,对于容量较小的键值对,为其创建一个结构复杂的哈希表太浪费内存,所以redis 创建了ziplist来存放这些键值对,这可以减少存放节点指针的空间,因此它被用来作为哈希表初始化时的底层实现.下图即ziplist 的内部结构. Zlbytes是整个ziplist 所占用的空间,必要时需要重新分配. Zltail便于快速的访问到表尾节点,不需要遍历整个ziplist. Zllen表示包含的节点数. Entries表示用户增加上去的节点. Zlend是一个255

redis源码解析之dict数据结构

dict 是redis中最重要的数据结构,存放结构体redisDb中. typedef struct dict { dictType *type; void *privdata; dictht ht[2]; int rehashidx; /* rehashing not in progress if rehashidx == -1 */ int iterators; /* number of iterators currently running */ } dict; 其中type是特定结构的处

Redis源码解析:15Resis主从复制之从节点流程

Redis的主从复制功能,可以实现Redis实例的高可用,避免单个Redis 服务器的单点故障,并且可以实现负载均衡. 一:主从复制过程 Redis的复制功能分为同步(sync)和命令传播(commandpropagate)两个操作: 同步操作用于将从节点的数据库状态更新至主节点当前所处的数据库状态: 命令传播操作则用于在主节点的数据库状态被修改,导致主从节点的数据库状态不一致时,让主从节点的数据库重新回到一致状态: 1:同步 当客户端向从节点发送SLAYEOF命令,或者从节点的配置文件中配置了

Redis源码解析:13Redis中的事件驱动机制

Redis中,处理网络IO时,采用的是事件驱动机制.但它没有使用libevent或者libev这样的库,而是自己实现了一个非常简单明了的事件驱动库ae_event,主要代码仅仅400行左右. 没有选择libevent或libev的原因大概在于,这些库为了迎合通用性造成代码庞大,而且其中的很多功能,比如监控子进程,复杂的定时器等,这些都不是Redis所需要的. Redis中的事件驱动库只关注网络IO,以及定时器.该事件库处理下面两类事件: a:文件事件(file  event):用于处理Redis

Redis源码解析(十五)--- aof-append only file解析

继续学习redis源码下的Data数据相关文件的代码分析,今天我看的是一个叫aof的文件,这个字母是append ONLY file的简称,意味只进行追加文件操作.这里的文件追加记录时为了记录数据操作的改变记录,用以异常情况的数据恢复的.类似于之前我说的redo,undo日志的作用.我们都知道,redis作为一个内存数据库,数据的每次操作改变是先放在内存中,等到内存数据满了,在刷新到磁盘文件中,达到持久化的目的.所以aof的操作模式,也是采用了这样的方式.这里引入了一个block块的概念,其实就

Redis源码解析——字符串map

本文介绍的是Redis中Zipmap的原理和实现.(转载请指明出于breaksoftware的csdn博客) 基础结构 Zipmap是为了实现保存Pair(String,String)数据的结构,该结构包含一个头信息.一系列字符串对(之后把一个"字符串对"称为一个"元素"(ELE))和一个尾标记.用图形表示该结构就是: Redis源码中并没有使用结构体来表达该结构.因为这个结构在内存中是连续的,而除了HEAD和红色背景的尾标记END(恒定是0xFF)是固定的8位,其