redis源码阅读之ae_select

9790阅读 0评论2019-06-02 stolennnxb
分类:NOSQL

redis是单线程,要做到快速呢,就需要用到I/O多路复用技术了,今天闲来讲讲最为普遍的select

点击(此处)折叠或打开

  1. #include <sys/select.h>
  2. int select(int maxfdp1, fd_set *readfds, fd_set *writefds, fd_set *errorfds, const struct timeval *timeout);
其中各个参数的含义如下:

点击(此处)折叠或打开

  1. maxfdp1: 待测试的文件描述字的个数,其值是待测试的最大描述符加1(由于描述符是从0开始索引)
  2. readfds: 指向对可读感兴趣的描述符集合的指针
  3. writefds: 指向对可写感兴趣的描述符集合的指针
  4. errorfds: 指向对处于异常条件感兴趣的描述符集合的指针
  5. timeout: 指定愿意等待的时间长度。若为NULL,则堵塞;如果其结构体内元素均为0,则直接非堵塞;如果以上两种均不满足,则等待固定时间后返回
其中fd_set可以理解为一个描述符集合,一般情况是用如下几个宏来对其进行操作

点击(此处)折叠或打开

  1. FD_ZERO(fd_set *fdset); /* clear all bits in fdset */
  2. FD_SET(int fd, fd_set *fdset); /* turn on the bit for fd in fd_set */
  3. FD_CLR(int fd, fd_set *fdset); /* turn off the bit for fd in fd_set */
  4. int FD_ISSET(int fd, fd_set *fdset); /* is the bit for fd on in fdset? */
函数的返回值由三种情况

点击(此处)折叠或打开

  1. -1:出错
  2. 0:没有描述符准备好
  3. 正数:3个描述符集合当中已经准备好的描述符的个数之和
其劣势也就显而易见了,如果maxfd比较大,那么当返回之后,我们就得,全部轮询各个集合来进行后续操作,这就比较浪费 时间了,所以,select是redis当中I/O多路复用的下下之选,
在redis当中,封装结构如下:

点击(此处)折叠或打开

  1. typedef struct aeApiState {
  2.     fd_set rfds, wfds;
  3.     /* We need to have a copy of the fd sets as it's not safe to reuse
  4.      * FD sets after select(). */
  5.     fd_set _rfds, _wfds;
  6. } aeApiState;
其实现常规的api函数如下:

点击(此处)折叠或打开

  1. static int aeApiCreate(aeEventLoop *eventLoop) {
  2.     aeApiState *state = zmalloc(sizeof(aeApiState));

  3.     if (!state) return -1;
  4.     FD_ZERO(&state->rfds);
  5.     FD_ZERO(&state->wfds);
  6.     eventLoop->apidata = state;
  7.     return 0;
  8. }

  9. static int aeApiResize(aeEventLoop *eventLoop, int setsize) {
  10.     /* Just ensure we have enough room in the fd_set type. */
  11.     if (setsize >= FD_SETSIZE) return -1;
  12.     return 0;
  13. }

  14. static void aeApiFree(aeEventLoop *eventLoop) {
  15.     zfree(eventLoop->apidata);
  16. }

  17. static int aeApiAddEvent(aeEventLoop *eventLoop, int fd, int mask) {
  18.     aeApiState *state = eventLoop->apidata;

  19.     if (mask & AE_READABLE) FD_SET(fd,&state->rfds);
  20.     if (mask & AE_WRITABLE) FD_SET(fd,&state->wfds);
  21.     return 0;
  22. }

  23. static void aeApiDelEvent(aeEventLoop *eventLoop, int fd, int mask) {
  24.     aeApiState *state = eventLoop->apidata;

  25.     if (mask & AE_READABLE) FD_CLR(fd,&state->rfds);
  26.     if (mask & AE_WRITABLE) FD_CLR(fd,&state->wfds);
  27. }
  28. static char *aeApiName(void) {
  29.     return "select";
  30. }
最主要的实现函数为

点击(此处)折叠或打开

  1. static int aeApiPoll(aeEventLoop *eventLoop, struct timeval *tvp) {
  2.     aeApiState *state = eventLoop->apidata;
  3.     int retval, j, numevents = 0;

  4.     memcpy(&state->_rfds,&state->rfds,sizeof(fd_set));
  5.     memcpy(&state->_wfds,&state->wfds,sizeof(fd_set));

  6.     retval = select(eventLoop->maxfd+1,
  7.                 &state->_rfds,&state->_wfds,NULL,tvp);
  8.     if (retval > 0) {
  9.         for (j = 0; j <= eventLoop->maxfd; j++) {
  10.             int mask = 0;
  11.             aeFileEvent *fe = &eventLoop->events[j];

  12.             if (fe->mask == AE_NONE) continue;
  13.             if (fe->mask & AE_READABLE && FD_ISSET(j,&state->_rfds))
  14.                 mask |= AE_READABLE;
  15.             if (fe->mask & AE_WRITABLE && FD_ISSET(j,&state->_wfds))
  16.                 mask |= AE_WRITABLE;
  17.             eventLoop->fired[numevents].fd = j;
  18.             eventLoop->fired[numevents].mask = mask;
  19.             numevents++;
  20.         }
  21.     }
  22.     return numevents;
  23. }

由以上函数我们可以知晓,在ae_select设置的时候,使用的是rfds和wfds,在调用select的时候,是将上面两个拷贝到_rfds和_wfds当中,这里是为了避免重用描述符时的安全问题~
还望各位看官多提宝贵意见
上一篇:redis源码阅读之aof
下一篇:redis源码阅读之ae_epoll