当前位置: 首页 > news >正文

纯div+css做网站简洁版/营销平台建设

纯div+css做网站简洁版,营销平台建设,互联网保险公司有哪些,衡水做阿里巴巴网站Redis 是cs架构(服务端-客户端),典型的一对多的服务器应用程序。多个客户通过网络与Redis服务器进行通信。那么在linux环境中是使用epoll(我们也 只讨论linux环境的,便于学习)。   通过使用I/O多路复用技术, redis 服务器使用单线程单进程的…

Redis 是cs架构(服务端-客户端),典型的一对多的服务器应用程序。多个客户通过网络与Redis服务器进行通信。那么在linux环境中是使用epoll(我们也 只讨论linux环境的,便于学习)。
  通过使用I/O多路复用技术, redis 服务器使用单线程单进程的方式处理命令请求,并与多个客户端连接进行网络通讯。

redis的网络框架和Muduo是很相似的,若是弄懂了Muduo后在来看Redis的网络部分,那是很轻松的。这里推荐下我写的cppServer程序,有很详细的章节解说。0.仿造muduo,实现linux服务器开发思路

 看懂这个后,很有助于看懂Muduo和Redis的网络部分。

1.Redis的main()函数-----建立监听的开始

//server.c
int main(int argc, char **argv) {//省略很多.......initServer();//省略很多.......//server.el 类型是struct aeEventLoop*aeMain(server.el);aeDeleteEventLoop(server.el);return 0;
}

目前,main()函数中关于网络的主要就这几个函数,其他的先省略了。

而要介绍这几个函数,需要先介绍一些数据结构才行。

2.网络相关的数据结构

这里就介绍aeEventLoop,aeFileEventaeTimeEventaeFiredEvent4个结构体(都在ae.h文件中)。

aeEventLoop是重中之重,带出其他三个结构体。

1.struct aeEventLoop

这个就类似我写的程序里面的EventLoop。

aeEventLoop是Reactor模型的具体抽象,把网络读写事件和时间事件(定时器任务)可以统一到一起处理。

  • events实际是个大小为setsize的数组,管理着网络IO事件。events数组的下标表示的是fd,而对应的元素是该fd关注的IO事件(aeFileEvent结构体)。
  • fired数组是已被触发读写的网络事件数组。其下标仅仅是索引index,该索引位置上的元素是记录了被触发的fd及其对应的事件类型。
  • 在Redis中是使用链表存储定时器任务的。timeEventHead表示的是定时器链表的头结点。而由于每次定时器任务结点都是从链表头部插入的,所以timeEventHead记录的是最晚插入的结点。(因为是直接插入放在头结点,所有对链表进行排序,所以头结点不一定是最早超时的任务

  • beforesleep和aftersleep是每次事件循坏之前和之后需要执行的回调函数。(即是调用epoll_wait()前后)

  • apidata封装来具体的IO多路复用的系统调用。linux主要有select、poll、epoll,在Redis代码文件中分别对应ae.select.cc、ae_evport.cc、ae_epoll.cc。(这里我们只分析epoll)。

//ae.h
// 事件循环
typedef struct aeEventLoop {int maxfd;       //目前已注册的最大文件描述符fdint setsize;     //能注册的最大描述符数long long timeEventNextId;    //下一个要注册的时间事件idtime_t lastTime;      //最后一次执行时间事件的时间aeFileEvent *events;     //是数组,已注册的文件事件 (就是IO event)aeFiredEvent *fired;     //数组,已就绪的文件事件aeTimeEvent *timeEventHead;    //定时器链表的头结点int stop;    //eventLoop的开关void *apidata; //多路复用库的私有数据(epollfd相关的数据)aeBeforeSleepProc *beforesleep;//在处理事件前要执行的回调函数(即是在执行epoll_wait()之前)aeBeforeSleepProc *aftersleep;//在处理事件后要执行的回调函数(即是在执行epoll_wait()之后)int flags;          //设置的标识位
} aeEventLoop;//文件事件结构
typedef struct aeFileEvent {//监听事件类型,即是关注的事件int mask; /* one of AE_(READABLE|WRITABLE|BARRIER) */aeFileProc *rfileProc;      //读事件的回调函数aeFileProc *wfileProc;      //写事件的回调函数void *clientData;           
} aeFileEvent;//已就绪的事件
typedef struct aeFiredEvent {int fd;     //已就绪文件描述符int mask;   //事件类型
} aeFiredEvent;

2.aeFileEvent用来管理要注册的IO事件

  • mask是需要注册的事件类型。其就是这几个事件经过或运算后的掩码,包括AE_READABLE(可读事件)、AE_WRITABLE(可写事件)和AE_BARRIER(该事件可以实现读写事件处理顺序的反转)。(AE_BARRIER可以先不了解,没有影响的)
  • clientData是万能指针void*,是执行回调处理函数的参数数据。
  • rfileProc和wfileProc是回调函数,分别在AE_READABLE和AE_WRITABLE类型时进行回调使用的。

 要说说回调函数的类型。

//ae.h
//回调函数类型
//用c++11表示的话 using aeFileProc=std::function<void(aeEventLoop* ,int,void*,int)>;//IO读写事件回调函数
typedef void aeFileProc(struct aeEventLoop *eventLoop, int fd, void *clientData, int mask);
//定时器事件回调函数
typedef int aeTimeProc(struct aeEventLoop *eventLoop, long long id, void *clientData);
//删除定时事件的回调函数
typedef void aeEventFinalizerProc(struct aeEventLoop *eventLoop, void *clientData);
//进入循环等待之前的回调函数
typedef void aeBeforeSleepProc(struct aeEventLoop *eventLoop);

 3.aeFiredEvent用来管理已经发生的IO事件

  • fd是已事件就绪的文件描述符
  • mask是返回的已发生事件的事件类型(通过epoll_wait()返回已发生的事件)

 4.时间事件是aeTimeEvent,timeEventHead是注册的时间事件列表(链表)

  • 每个时间事件都有一个事件id,aeEventLoop中的timeEventNextId是下一个要注册的时间事件id。
  • when_sec、when_ms是时间事件(定时器任务)的发生时间
  • timeProc是时间事件的处理回调函数。说明:aeTimeProc 需要返回一个 int 值,代表下次该超时事件触发的时间间隔。如果返回 - 1,则说明超时时间不需要再触发了,标记为删除即可
  • finalizerProc是时间事件要删除时的处理函数
//ae.h
//时间事件结构
typedef struct aeTimeEvent {long long id; //时间事件的唯一标识符,自增//事件的到达时间(即是执行时间)long when_sec; /* seconds */long when_ms; /* milliseconds *///事件处理函数 (到期执行的回调函数)aeTimeProc *timeProc;//事件释放函数 (回调函数)aeEventFinalizerProc *finalizerProc;void *clientData;               //多路复用库的私有数据//双向链表struct aeTimeEvent *prev;   struct aeTimeEvent *next;int refcount;     //以防止计时器事件在递归时间事件调用中释放
} aeTimeEvent;

那么,我们接着看回main()函数中的关于网络的部分。

initServer()函数

void initServer(void) {//省略了很多不相关的代码.......//创建evEventLoop ,也可以说是创建epoll//server.el是aeEventLoop*类型server.el = aeCreateEventLoop(server.maxclients+CONFIG_FDSET_INCR);//ipfd是服务器端fd,是数组,因为有IPV4和IPV4,listenToPort(server.port,server.ipfd,&server.ipfd_count);//函数内部调用sokcet(),bind(),listen()...for (j = 0; j < server.ipfd_count; j++) {aeCreateFileEvent(server.el, server.ipfd[j], AE_READABLE,acceptTcpHandler,NULL);}
}

3.事件驱动初始化

1.aeCreateEventLoop()函数,创建EventLoop。

其主要4步:

1.分配内存给eventloop;

2.eventloop的成员进行初始化,

3.创建epollfd(调用epoll_create());

4.初始化要关注的事件类型,初始化阶段什么时间也没有关注。

aeEventLoop *aeCreateEventLoop(int setsize) {aeEventLoop *eventLoop;int i;//1.分配内存  zmalloc()函数是Redis封装的函数,可以理解为是malloc()if ((eventLoop = zmalloc(sizeof(*eventLoop))) == NULL) goto err;//创建数组,可见数组的长度就是setsizeeventLoop->events = zmalloc(sizeof(aeFileEvent)*setsize);eventLoop->fired = zmalloc(sizeof(aeFiredEvent)*setsize);if (eventLoop->events == NULL || eventLoop->fired == NULL) goto err;//2.初始化eventLoop->setsize = setsize;eventLoop->lastTime = time(NULL);eventLoop->timeEventHead = NULL;eventLoop->timeEventNextId = 0;eventLoop->stop = 0;eventLoop->maxfd = -1;eventLoop->beforesleep = NULL;eventLoop->aftersleep = NULL;eventLoop->flags = 0;//3.创建epollfdif (aeApiCreate(eventLoop) == -1) goto err;/* Events with mask == AE_NONE are not set. So let's initialize the* vector with it. *//** 可以看到数组长度就是setsize,同时创建之后将每一个event的mask属性置为AE_NONE(即是0),*对于eventLoop->events数组来说,fd就是这个数组的下标。*例如,当程序刚刚启动时候,创建监听套接字,按照标准规定,该fd的值为3。此时就直接在            eventLoop->events下标为3的元素中存放相应event数据。*不过也基于文件描述符的这些特点,意味着events数组的前三位一定不会有相应的fd赋值。*///4.初始化,什么事件也没有关注,mask=AE_NONEfor (i = 0; i < setsize; i++)eventLoop->events[i].mask = AE_NONE;return eventLoop;err:if (eventLoop) {zfree(eventLoop->events);zfree(eventLoop->fired);zfree(eventLoop);}return NULL;
}

2.IO多路复用,epoll的封装

 epoll的封装都在ae_epoll.c文件中。这个是封装了I/O多路复用。

主要是封装一些epoll的函数(epoll_create,epoll_ctl)。  

aeEventLoop中的apidata在epoll中表示为aeApiState。结构体aeApiState中的epfd为epoll的fd,events表示接受事件循环epoll_wait返回的触发读写的网络事件。

  • int aeApiCreate(aeEventLoop *eventLoop):   

 调用epoll_create()得到epfd,并将aeApiState数据赋值给eventLoop->apidata。 

  • int aeApiResize(aeEventLoop *eventLoop, int setsize):

         重置eventLoop->apidata的events的大小。

  • int aeApiAddEvent(aeEventLoop *eventLoop, int fd, int mask):

        调用epeoll_ctl(),根据mask对fd进行添加或修改。

  • void aeApiDelEvent(aeEventLoop *eventLoop, int fd, int delmask):

         调用epoll_ctl(),对fd进行删除。

  •  int aeApiPoll(aeEventLoop *eventLoop, struct timeval *tvp):

         调用epoll_wait(),得到已就绪的events,并将已就绪的events赋值给eventLoop->fired。

//ae_epoll.c
typedef struct aeApiState {int epfd;        //这个是epoll_create()返回来的fdstruct epoll_event *events; //用在epoll_wait(int epfd,epoll_event* events)函数内的参数
} aeApiState;//创建epollfd,调用epoll_create()
static int aeApiCreate(aeEventLoop *eventLoop) {aeApiState *state = zmalloc(sizeof(aeApiState));if (!state) return -1;state->events = zmalloc(sizeof(struct epoll_event)*eventLoop->setsize);if (!state->events) {zfree(state);return -1;}state->epfd = epoll_create(1024); /* 1024 is just a hint for the kernel */if (state->epfd == -1) {zfree(state->events);zfree(state);return -1;}eventLoop->apidata = state;//epoll的数据aeApiStatereturn 0;
}//重置events的大小
static int aeApiResize(aeEventLoop *eventLoop, int setsize) {aeApiState *state = eventLoop->apidata;state->events = zrealloc(state->events, sizeof(struct epoll_event)*setsize);return 0;
}//添加fd到epoll上,调用epoll_ctl()
static int aeApiAddEvent(aeEventLoop *eventLoop, int fd, int mask) {aeApiState *state = eventLoop->apidata;struct epoll_event ee = {0}; /* avoid valgrind warning *//* If the fd was already monitored for some event, we need a MOD* operation. Otherwise we need an ADD operation. */int op = eventLoop->events[fd].mask == AE_NONE ?EPOLL_CTL_ADD : EPOLL_CTL_MOD;ee.events = 0;mask |= eventLoop->events[fd].mask; /* Merge old events */if (mask & AE_READABLE) ee.events |= EPOLLIN;if (mask & AE_WRITABLE) ee.events |= EPOLLOUT;ee.data.fd = fd;if (epoll_ctl(state->epfd,op,fd,&ee) == -1) return -1;return 0;
}static void aeApiDelEvent(aeEventLoop *eventLoop, int fd, int delmask) {aeApiState *state = eventLoop->apidata;struct epoll_event ee = {0}; /* avoid valgrind warning */int mask = eventLoop->events[fd].mask & (~delmask);ee.events = 0;if (mask & AE_READABLE) ee.events |= EPOLLIN;if (mask & AE_WRITABLE) ee.events |= EPOLLOUT;ee.data.fd = fd;if (mask != AE_NONE) {epoll_ctl(state->epfd,EPOLL_CTL_MOD,fd,&ee);} else {/* Note, Kernel < 2.6.9 requires a non null event pointer even for* EPOLL_CTL_DEL. */epoll_ctl(state->epfd,EPOLL_CTL_DEL,fd,&ee);}
}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|AE_READABLE;if (e->events & EPOLLHUP) mask |= AE_WRITABLE|AE_READABLE;//赋给已就绪的fired事件eventLoop->fired[j].fd = e->data.fd;eventLoop->fired[j].mask = mask;}}return numevents;
}

创建好EventLoop后,需要进行服务器的初始化,调用socket(),bind()等等操作。

4.listenToPort函数,进行服务器的常规初始化操作(socket,bind,listen)

bindaddr是个元素是char*的数组,元素是服务器需要绑定的ip。

为了便于分析,假定server.bindaddr_count是0,那么IPv4和IPv6都需要绑定。

//in server.c
//调用例子 listenToPort(server.port,server.ipfd,&server.ipfd_count)
int listenToPort(int port, int *fds, int *count) {int j;if (server.bindaddr_count == 0) server.bindaddr[0] = NULL;for (j = 0; j < server.bindaddr_count || j == 0; j++) {if (server.bindaddr[j] == NULL) {/* Bind * for both IPv6 and IPv4, we enter here only if* server.bindaddr_count == 0. */fds[*count] = anetTcp6Server(server.neterr,port,NULL,server.tcp_backlog);if (fds[*count] != ANET_ERR) {anetNonBlock(NULL,fds[*count]);    //设置非阻塞(*count)++;} if (*count == 1) {/* Bind the IPv4 address as well. */fds[*count] = anetTcpServer(server.neterr,port,NULL,server.tcp_backlog);if (fds[*count] != ANET_ERR) {anetNonBlock(NULL,fds[*count]);(*count)++;} }} if (fds[*count] == ANET_ERR) {//省略一些错误处理和打印日志return C_ERR;}anetNonBlock(NULL,fds[*count]);(*count)++;}return C_OK;
}

 这里需要调用anetTcpServer()函数和anetTcp6Server()函数。

//in anet.c
int anetTcpServer(char *err, int port, char *bindaddr, int backlog)
{return _anetTcpServer(err, port, bindaddr, AF_INET, backlog);
}int anetTcp6Server(char *err, int port, char *bindaddr, int backlog)
{return _anetTcpServer(err, port, bindaddr, AF_INET6, backlog);
}static int _anetTcpServer(char *err, int port, char *bindaddr, int af, int backlog)
{int s = -1, rv;char _port[6];  /* strlen("65535") */struct addrinfo hints, *servinfo, *p;snprintf(_port,6,"%d",port);memset(&hints,0,sizeof(hints));hints.ai_family = af;hints.ai_socktype = SOCK_STREAM;hints.ai_flags = AI_PASSIVE;    /* No effect if bindaddr != NULL */if ((rv = getaddrinfo(bindaddr,_port,&hints,&servinfo)) != 0) {anetSetError(err, "%s", gai_strerror(rv));return ANET_ERR;}/* getaddrinfo() returns a list of address structures.Try each address until we successfully bind(2).If socket(2) (or bind(2)) fails, we (close the socketand) try the next address. *///其中有绑定一个成功就会跳出for循环的for (p = servinfo; p != NULL; p = p->ai_next) {if ((s = socket(p->ai_family,p->ai_socktype,p->ai_protocol)) == -1) //创建sockdfdcontinue;if (af == AF_INET6 && anetV6Only(err,s) == ANET_ERR) goto error;if (anetSetReuseAddr(err,s) == ANET_ERR) goto error;    //设置端口服用if (anetListen(err,s,p->ai_addr,p->ai_addrlen,backlog) == ANET_ERR) s = ANET_ERR;   //绑定并开始监听goto end;}if (p == NULL) {anetSetError(err, "unable to bind socket, errno: %d", errno);goto error;}error:if (s != -1) close(s);s = ANET_ERR;
end:freeaddrinfo(servinfo);return s;
}

5.创建服务器的FileEvent,并注册到eventLoop中

绑定并进行listenn()监听后,就到了aeCreateFileEvent(),进行事件注册,添加FileEvent到eventLoop中。

事件一共有两类:

  • IO事件注册与删除:aeCreateFileEvent,aeDeleteFileEvent
  • 时间事件注册与删除:aeCreateTimeEvent,aeDeleteTimeEvent

 IO事件的注册通过aeApiAddEvent函数将套接字及其事件处理函数注册到epoll中。

//in ae.c
//根据mask参数的值, 创建文件事件
// 监听fd 文件的状态,
//当fd可用时,执行proc函数(即是回调函数)
int aeCreateFileEvent(aeEventLoop *eventLoop, int fd, int mask,aeFileProc *proc, void *clientData)
{if (fd >= eventLoop->setsize) {errno = ERANGE;return AE_ERR;}aeFileEvent *fe = &eventLoop->events[fd];if (aeApiAddEvent(eventLoop, fd, mask) == -1)//使用epoll的话即是调用epoll_ctlreturn AE_ERR;//设置文件事件类型,以及事件的处理器(即是设置回调函数)fe->mask |= mask;if (mask & AE_READABLE) fe->rfileProc = proc;if (mask & AE_WRITABLE) fe->wfileProc = proc;fe->clientData = clientData;if (fd > eventLoop->maxfd)  //若符合,则更新maxfdeventLoop->maxfd = fd;return AE_OK;
}void aeDeleteFileEvent(aeEventLoop *eventLoop, int fd, int mask)
{if (fd >= eventLoop->setsize) return;aeFileEvent *fe = &eventLoop->events[fd];if (fe->mask == AE_NONE) return;/* We want to always remove AE_BARRIER if set when AE_WRITABLE* is removed. */if (mask & AE_WRITABLE) mask |= AE_BARRIER;aeApiDelEvent(eventLoop, fd, mask); //调用epoll_ctlr(del)fe->mask = fe->mask & (~mask);if (fd == eventLoop->maxfd && fe->mask == AE_NONE) {/* Update the max fd */int j;for (j = eventLoop->maxfd-1; j >= 0; j--)   //判断最大的fd的maskif (eventLoop->events[j].mask != AE_NONE) break;eventLoop->maxfd = j;}
}//创建时间事件
long long aeCreateTimeEvent(aeEventLoop *eventLoop, long long milliseconds,aeTimeProc *proc, void *clientData,aeEventFinalizerProc *finalizerProc)
{long long id = eventLoop->timeEventNextId++;    //更新时间计数器,这个id是时间事件的唯一标识符aeTimeEvent *te;te = zmalloc(sizeof(*te));if (te == NULL) return AE_ERR;te->id = id;aeAddMillisecondsToNow(milliseconds,&te->when_sec,&te->when_ms);    //设置到期时间//设置事件处理器,即是设置回调函数te->timeProc = proc;te->finalizerProc = finalizerProc;//设置私有数据te->clientData = clientData;te->prev = NULL;te->next = eventLoop->timeEventHead;    //将新事件放入表头te->refcount = 0;if (te->next)te->next->prev = te;eventLoop->timeEventHead = te;return id;
}int aeDeleteTimeEvent(aeEventLoop *eventLoop, long long id)
{aeTimeEvent *te = eventLoop->timeEventHead; //这是个链表while(te) { //需要从头找到尾if (te->id == id) {te->id = AE_DELETED_EVENT_ID;return AE_OK;}te = te->next;}return AE_ERR; /* NO event with the specified ID found */
}

那么现在,通过 aeCreateFileEvent(server.el,server.ipfd[j],AE_READABLE,acceptTcpHandler,NULL);将服务器的监听fd和其事件回调函数acceptTcpHandler注册到epoll中,并监听读事件。(acceptTcpHandler函数先不细讲,这个函数肯定是有调用accept()的)

6.进行事件循环

启动aeMain函数阻塞等待事件发生,并处理。

//文件事件 1
#define AE_FILE_EVENTS (1<<0)
//时间事件 2
#define AE_TIME_EVENTS (1<<1)
//文件事件和时间事件 4
#define AE_ALL_EVENTS (AE_FILE_EVENTS|AE_TIME_EVENTS)
//不阻塞等待标识 8
#define AE_DONT_WAIT (1<<2)#define AE_CALL_BEFORE_SLEEP (1<<3)
#define AE_CALL_AFTER_SLEEP (1<<4)void aeMain(aeEventLoop *eventLoop) {eventLoop->stop = 0;while (!eventLoop->stop) {aeProcessEvents(eventLoop, AE_ALL_EVENTS|AE_CALL_BEFORE_SLEEP|AE_CALL_AFTER_SLEEP);}
}

该函数就是一个while()循环,循环内部是aeProcessEvents函数。

下面注重来看下aeProcessEvents,第一个参数是要处理的事件驱动框架,第二个参数是要处理的事件类型,在aeMain中表示处理包括IO事件和时间事件在内的所有事件以及回调前置函数beforesleep和回调后置函数aftersleep

aeProcessEvents的主要步骤:

回调函数beforsleep和aftersleep可以先省略不关注的。因为目前其还没有用途,先知道有这两个回调函数就行。

1.计算epoll_wait()需要的阻塞时间。

        如果是设置了AF_DONT_WAIT,那就是不阻塞,epoll_wait()的超时时间就设置为0。如有定时器任务,那么其阻塞时间即是定时器的最早超时时间,这样可以防止定时器任务等待过久。

若是没有定时器任务,那就永远等待下去,直到有事件被触发。

2.执行beforsleep

        在epoll_wait()阻塞之前执行一些任务,防止因为阻塞时间过长而无法执行,或者执行一些准备工作。

3.epoll_wait等待事件发生。

4.执行aftersleep。

5.处理发生的IO事件。

根据发生的事件类型来调用对应的回调函数。若是AE_READABLE类型调用rfileProc,若是AE_WRITABLE类型调用wfileProc。一般来说,先处理AE_READABLE类型事件,该类事件一般为客户端连接或者命令,然后处理AE_WRITABLE类型事件向客户端发送响应对于客户端的回复,一般在beforesleep中就会执行完成)

6.处理时间事件。若该时间事件时周期性的,执行完后会再添加到时间事件链表的。

int aeProcessEvents(aeEventLoop *eventLoop, int flags)
{int processed = 0, numevents;//没有时间事件和文件事件,退出if (!(flags & AE_TIME_EVENTS) && !(flags & AE_FILE_EVENTS)) return 0;//1.计算epoll_wait()需要的阻塞时间if (eventLoop->maxfd != -1 ||((flags & AE_TIME_EVENTS) && !(flags & AE_DONT_WAIT))) {int j;aeTimeEvent *shortest = NULL;struct timeval tv, *tvp;if (flags & AE_TIME_EVENTS && !(flags & AE_DONT_WAIT)) //表示有时间事件且需要阻塞,epoll_wait()设置的时间就不是0shortest = aeSearchNearestTimer(eventLoop); //找到最近的定时器超时时间if (shortest) {long now_sec, now_ms;aeGetTime(&now_sec, &now_ms);tvp = &tv;//定时器的到期时间,毫秒数long long ms =(shortest->when_sec - now_sec)*1000 +shortest->when_ms - now_ms;if (ms > 0) {tvp->tv_sec = ms/1000;tvp->tv_usec = (ms % 1000)*1000;} else {tvp->tv_sec = 0;tvp->tv_usec = 0;}} else {//不需要阻塞if (flags & AE_DONT_WAIT) {tv.tv_sec = tv.tv_usec = 0;tvp = &tv;} else {/* Otherwise we can block */tvp = NULL; /* wait forever */}}//再次确认if (eventLoop->flags & AE_DONT_WAIT) {tv.tv_sec = tv.tv_usec = 0;tvp = &tv;}//2.执行beforesleepif (eventLoop->beforesleep != NULL && flags & AE_CALL_BEFORE_SLEEP)eventLoop->beforesleep(eventLoop);  //在休眠前执行(即是在epoll_wait()前)/* Call the multiplexing API, will return only on timeout or when* some event fires. *///3.执行epoll_waitnumevents = aeApiPoll(eventLoop, tvp);//4.执行aftersleep/* After sleep callback. */if (eventLoop->aftersleep != NULL && flags & AE_CALL_AFTER_SLEEP)eventLoop->aftersleep(eventLoop);//在休眠后执行(即是在epoll_wait()之后)//5.逐个处理触发的事件for (j = 0; j < numevents; j++) {aeFileEvent *fe = &eventLoop->events[eventLoop->fired[j].fd];int mask = eventLoop->fired[j].mask;        //这个是epoll_wait()返回的触发事件类型int fd = eventLoop->fired[j].fd;int fired = 0; /* Number of events fired for current fd. */int invert = fe->mask & AE_BARRIER;//触发可读事件if (!invert && fe->mask & mask & AE_READABLE) {fe->rfileProc(eventLoop,fd,fe->clientData,mask);fired++;fe = &eventLoop->events[fd]; /* Refresh in case of resize. */}//可写事件if (fe->mask & mask & AE_WRITABLE) {if (!fired || fe->wfileProc != fe->rfileProc) {fe->wfileProc(eventLoop,fd,fe->clientData,mask);fired++;}}/* If we have to invert the call, fire the readable event now* after the writable one. */if (invert) {fe = &eventLoop->events[fd]; /* Refresh in case of resize. */if ((fe->mask & mask & AE_READABLE) &&(!fired || fe->wfileProc != fe->rfileProc)){fe->rfileProc(eventLoop,fd,fe->clientData,mask);fired++;}}processed++;}}//6.处理时间事件if (flags & AE_TIME_EVENTS)processed += processTimeEvents(eventLoop);  return processed; /* return the number of processed file/time events */
}


 

相关文章:

Redis源码(1) 建立监听服务和开启事件循环

Redis 是cs架构(服务端-客户端)&#xff0c;典型的一对多的服务器应用程序。多个客户通过网络与Redis服务器进行通信。那么在linux环境中是使用epoll(我们也 只讨论linux环境的&#xff0c;便于学习)。   通过使用I/O多路复用技术&#xff0c; redis 服务器使用单线程单进程的…...

c++基础概念,const与指针、引用的关系,auto,decltype关键字能干啥总得了解吧。总得按照需求自定义创建实体类,自己编写头文件吧

const限定符 有时我们希望定义这样一种变量&#xff0c;它的值不能被改变。例如&#xff0c;用一个变量来表示缓冲区的大小。使用变量的好处是当我们觉得缓冲区大小不再合适时&#xff0c;很容易对其进行调整。另一方面&#xff0c;也应随时警惕防止程序一不小心改变了这个值。…...

【数据结构】---几分钟简单几步学会手撕链式二叉树(下)

文章目录 前言&#x1f31f;一、二叉树链式结构的实现&#x1f30f;1.1 二叉树叶子节点个数&#x1f4ab;代码&#xff1a;&#x1f4ab;流程图&#xff1a; &#x1f30f;1.2 二叉树的高度&#x1f4ab;第一种写法(不支持)&#xff1a;&#x1f4d2;代码&#xff1a;&#x1f…...

用户验证FTP实验

用户FTP实验 目录 匿名用户验证&#xff1a; 本地用户验证&#xff1a; 本地用户访问控制&#xff1a; 匿名用户验证&#xff1a; 例&#xff1a;&#xff08;前提配置&#xff0c;防火墙关闭&#xff0c;yum安装&#xff0c;同模式vmware11&#xff09; 现有一台计算机huy…...

App 软件开发《单选4》试卷答案及解析

App 软件开发《单选4》试卷答案及解析 注&#xff1a;本文章所有答案的解析来自 ChatGPT 的回答&#xff08;给出正确答案让其解释原因&#xff09;&#xff0c;正确性请自行甄辨。 文章目录 App 软件开发《单选4》试卷答案及解析单选题&#xff08;共计0分&#xff09;1&#…...

代码随想录算法训练营第三十七天 | 力扣 738.单调递增的数字, 968.监控二叉树

738.单调递增的数字 题目 738. 单调递增的数字 当且仅当每个相邻位数上的数字 x 和 y 满足 x < y 时&#xff0c;我们称这个整数是单调递增的。 给定一个整数 n &#xff0c;返回 小于或等于 n 的最大数字&#xff0c;且数字呈 单调递增 。 解析 从后向前遍历&#xf…...

C++内存总结

1.2 C内存 参考 https://www.nowcoder.com/issue/tutorial?tutorialId93&uuid8f38bec08f974de192275e5366d8ae24 1.2.1 简述一下堆和栈的区别 参考回答 区别&#xff1a; 堆栈空间分配不同。栈由操作系统自动分配释放 &#xff0c;存放函数的参数值&#xff0c;局部变…...

开发移动端官网总结_Vue2.x

目录 1、自定义加载中效果 2、HTML页面注入环境变量 / 加载CDN和本地库 3、在 Vue 中使用 wow.js 4、过渡路由 5、全局监听当前设备&#xff0c;切换PC端和移动端 6、移动端常用初始化样式 7、官网默认入口文件 8、回到顶部滑动过渡效果&#xff08;显示与隐藏、滑动置…...

Zookeeper+消息队列Kafka

一、Zookeeper 概述 官方下载地址&#xff1a;Index of /dist/zookeeper 1.1 Zookeeper 定义 Zookeeper是一个开源的分布式的&#xff0c;为分布式框架提供协调服务的Apache项目。 1.2 Zookeeper 工作机制 Zookeeper从设计模式角度来理解&#xff1a;是一个基于观察者模式设…...

【滤波】设计卡尔曼滤波器

本文主要翻译自rlabbe/Kalman-and-Bayesian-Filters-in-Python的第8章节08-Designing-Kalman-Filters&#xff08;设计卡尔曼滤波器&#xff09;。 %matplotlib inline#format the book import book_format book_format.set_style()简介 在上一章节中&#xff0c;我们讨论了教…...

redis主备切换,哨兵模式,缓存穿透、缓存击穿、缓存雪崩问题

主备切换 主从复制指的是把一台Redis服务器的数据复制到其他Redis服务器上&#xff0c;前者称为主节点Master&#xff0c;后者称为从节点Slave&#xff0c;只能从Master单向复制到Slave&#xff0c;一般Master以写操作为主&#xff0c;Slave以读操作为主&#xff0c;实现读写分…...

2023山东icpc省赛总结

距离比赛结束已经一天多了&#xff0c;现在的感觉就是三个字&#xff1a;意难平。 这是我们第一次打现场赛&#xff0c;去之前真的是很激动。因为我们比赛前做了很多其他省的省赛模拟&#xff0c;也做了几套今年别的省的题目&#xff0c;做完会去搜题解&#xff0c;会看到别人写…...

linux0.12-12-fs

[606页] 第12章 文件系统 606–12-1-总体功能 607–12-1-1-MINIX文件系统 611–12-1-2-文件类型、属性和目录项 615–12-1-3-高速缓冲区 616–12-1-4-文件系统底层函数 616–12-1-5-文件中数据的访问操作 618–12-1-6-文件和目录管理系统调用 619–12-1-7-360KB软盘中文件系统…...

快速入门SpringMVC 学习

目录 SpringMVC 定义 MVC定义 创建SpringMVC项目 SpringMVC掌握功能 一、连接功能 RequestMapping(请求映射) GetMapping 和 PostMapping 二、获取参数功能 传递单个参数/多个参数 注意点&#xff1a; RequestParam(前后端参数映射) 前后端参数映射 RequestParam特…...

leetcode96--不同的二叉搜索树[java]

不同的二叉搜索树 leetcode 96 题 不同的二叉搜索树题目描述暴力递归解题思路代码演示执行效率 递归 缓存解题思路代码演示执行效率 动态规划专题 leetcode 96 题 不同的二叉搜索树 原题链接: 难度—中等 https://leetcode.cn/problems/unique-binary-search-trees/ 题目描述 …...

【Spring 项目的创建和使用】

&#x1f389;&#x1f389;&#x1f389;点进来你就是我的人了博主主页&#xff1a;&#x1f648;&#x1f648;&#x1f648;戳一戳,欢迎大佬指点! 欢迎志同道合的朋友一起加油喔&#x1f93a;&#x1f93a;&#x1f93a; 目录 1. 创建 Spring 项目 2. 创建一个 普通 Maven…...

数据类型.

数据类型 数据类型分类 数值类型 tinyint类型 数值越界测试&#xff1a; mysql> create table tt1(num tinyint); Query OK, 0 rows affected (0.02 sec)mysql> insert into tt1 values(1); Query OK, 1 row affected (0.00 sec)mysql> insert into tt1 values(128…...

深入了解JavaScript中的Promise

在JavaScript中&#xff0c;异步编程是必不可少的。过去&#xff0c;我们通常使用回调函数来处理异步操作&#xff0c;但回调地狱&#xff08;callback hell&#xff09;和复杂的错误处理使得代码难以维护。为了解决这些问题&#xff0c;ES6引入了Promise&#xff0c;它是一种更…...

Solidity基础六

生活本来就是平凡琐碎的&#xff0c;哪有那么多惊天动地的大事&#xff0c;快乐的秘诀就是不管对大事小事都要保持热情 目录 一、Solidity的特殊变量(全局) 二、Solidity的不可变量 immutable的赋值方式 三、Solidity的事件与日志 事件和日志加深理解 四、Solidity的异常…...

自学网络安全解决问题方法

自学网络安全很容易学着学着就迷茫了&#xff0c;找到源头问题&#xff0c;解决它就可以了&#xff0c;所以首先咱们聊聊&#xff0c;学习网络安全方向通常会有哪些问题&#xff0c;看到后面有惊喜哦 1、打基础时间太长 学基础花费很长时间&#xff0c;光语言都有几门&#xf…...

Java之旅(七)

Java 异常 Java异常&#xff08;Exception&#xff09;是在程序运行过程中出现错误或异常情况时&#xff0c;由程序自动抛出&#xff0c;导致程序无法正常运行&#xff0c;用于向上层调用程序传递错误信息或中断程序执行的一种机制。 异常与错误不同&#xff0c;错误是由于程…...

测试报告模板二

项目名称 系统测试报告 平台测试小组 2023年x月xx日 文档信息 文档名称: 作者:...

C语句概述

1 、 C 语句分类&#xff1a; ①控制语句&#xff1a;二个分支语句&#xff08; if-else 、 switch &#xff09;&#xff0c;三个循环语句&#xff08; for 、 while 、 do - while &#xff09;&#xff0c;四个转移语句&#xff08; continue 、 break 、 goto 、 return…...

C++ [STL之vector模拟实现]

本文已收录至《C语言和高级数据结构》专栏&#xff01; 作者&#xff1a;ARMCSKGT STL之vector模拟实现 前言正文空间结构默认成员函数构造函数拷贝构造函数赋值重载析构函数关于数据拷贝问题 迭代器容量操作查询容量容量操作 数据访问下标访问头尾数据访问 数据增删尾插尾删重…...

【算法竞赛进阶指南】141.周期 题解 KMP 最小循环节

题目描述 一个字符串的前缀是从第一个字符开始的连续若干个字符&#xff0c;例如 abaab 共有 5 5 5 个前缀&#xff0c;分别是 a&#xff0c;ab&#xff0c;aba&#xff0c;abaa&#xff0c;abaab。 我们希望知道一个 N N N 位字符串 S S S 的前缀是否具有循环节。 换言之…...

【Springboot 入门培训 】#19 Spring Boot 组件扫描与bean生命周期

目录 1 什么是组件扫描2 何时使用组件扫描3 扫描整个包basePackages与 includeFilters4 Spring boot 的 Bean 生命周期4.1 生命周期4.2 Bean 生命周期4.3 周期各个阶段 首先&#xff0c;我想先为你介绍一下“Spring”&#xff0c;这是一个开放源代码的设计模式解决方案和轻量级…...

Linux printf 函数输出问题

printf 函数并不会直接将数据输出到屏幕&#xff0c;而是先放到缓冲区中&#xff0c;只有一下三种情况满足&#xff0c;才会输出到屏幕。 1&#xff09; 缓冲区满 2&#xff09; 强制刷新缓冲区 fflush 3&#xff09; 程序结束时 1 #include<stdio.h>2 #include<st…...

皮卡丘Unsafe Fileupload

1.不安全的文件上传漏洞概述 文件上传功能在web应用系统很常见&#xff0c;比如很多网站注册的时候需要上传头像、上传附件等等。当用户点击上传按钮后&#xff0c;后台会对上传的文件进行判断 比如是否是指定的类型、后缀名、大小等等&#xff0c;然后将其按照设计的格式进行…...

最优化简明版(上)

引言 本文简单地介绍一些凸优化(Convex Optimization)的基础知识&#xff0c;可能不会有很多证明推导&#xff0c;目的是能快速应用到机器学习问题上。 凸集 直线与线段 设 x 1 ≠ x 2 x_1 \neq x_2 x1​x2​为 R n \Bbb R^n Rn空间中的两个点&#xff0c;那么具有下列形…...

MySQL的一些介绍

1. SQL的select语句完整的执行顺序 SQL Select语句完整的执行顺序&#xff1a; 1、from子句组装来自不同数据源的数据&#xff1b; 2、where子句基于指定的条件对记录行进行筛选&#xff1b; 3、group by子句将数据划分为多个分组&#xff1b; 4、使用聚集函数进行计算&am…...