redis 服务端处理客户端命令行的epoll事件执行过程分析

简易流程图如下:

redis 服务端处理客户端命令行的epoll事件执行过程分析_第1张图片

redis 服务端处理客户端命令行的epoll事件执行过程分析_第2张图片

其中,fd10,为epoll的首地址

fd11,为server监听的fd0

fd12-fd14,为测试时的3个客户端接入时,生成的fd socket.

1、客户端链接

redis服务端启动时,在_anetTcpServer进行socket,bind,listen步骤时,建立一个监听的sock fd0。并把这个fd0 添加到epoll监听事件。设置的回调函数中,fe->rfileProc=acceptTcpHandler

    if (mask & AE_READABLE) fe->rfileProc = proc;
    if (mask & AE_WRITABLE) fe->wfileProc = proc;

当客户端有链接请求接入时,epoll事件触发,在

aeProcessEvents==> aeApiPoll, 

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

此时有了IN事件,当多个客户端接入时,都会触发同一个fd0

            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;

aeProcessEvents==>fe->rfileProc,此时回调函数acceptTcpHandler得以执行。

2、acceptTcpHandler中,准备接收客户端数据

accpet建立时,生成当前服务端-客户端四元组(srcip,srcport,dstip,dstport)的一个fd,

然后将此fd1再次添加到epoll队列中,同时,此时的回调函数,设置成readQueryFromClient,准备读客户端数据

client *createClient(int fd) {
    client *c = zmalloc(sizeof(client));

    /* passing -1 as fd it is possible to create a non connected client.
     * This is useful since all the commands needs to be executed
     * in the context of a client. When commands are executed in other
     * contexts (for instance a Lua script) we need a non connected client. */
    if (fd != -1) {
        anetNonBlock(NULL,fd);
        anetEnableTcpNoDelay(NULL,fd);
        if (server.tcpkeepalive)
            anetKeepAlive(NULL,fd,server.tcpkeepalive);
        if (aeCreateFileEvent(server.el,fd,AE_READABLE,//此处的fd,是accept后,生成的fd1
            readQueryFromClient, c) == AE_ERR) 

3、读入客户端数据

在2中的epoll_ctl 添加事件时,主动设置了一个mask为IN事件,此时,epoll_wait会得到触发,产生一个新的IN事件。

回调函数readQueryFromClient继而被调用。在此函数中,

###
nread = read(fd, c->querybuf+qblen, readlen);
###
void processInputBuffer(client *c) {
    server.current_client = c;

    /* Keep processing while there is something in the input buffer */
    while(c->qb_pos < sdslen(c->querybuf)) {
        /* Return if clients are paused. */
  

这里的c->querybuf就是客户端执行的命令,比如这里的 get name

(gdb) p c->qb_pos
$35 = 23
(gdb) p c->querybuf
$36 = (sds) 0x7d6ca5 "*2\r\n$3\r\nget\r\n$4\r\nname\r\n"

4、接下来就是调用命令

 call(c,CMD_CALL_FULL);

     c->cmd->proc(c);  此处进行gdb,发现调用的函数,是getCommand

(gdb) p c->cmd->proc
$19 = (redisCommandProc *) 0x44bd50
(gdb) p c->argv[1]
$20 = (robj *) 0x7cb9a0
(gdb) p c->cmd->name
$21 = 0x4c80dd "get"

int getGenericCommand(client *c) {
    robj *o;

    if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.nullbulk)) == NULL)
        return C_OK;

    if (o->type != OBJ_STRING) {
        addReply(c,shared.wrongtypeerr);
        return C_ERR;
    } else {
        addReplyBulk(c,o);
        return C_OK;
    }
}

此时经过key查找,找到name对应的内容是“huawei”

(gdb) p c->buf
$9 = "$6\r\nhuawei\r\n", '\000' , "@nJ\367\377\177\000\000!\000\000\000\000\000\000\000\270\247J\367\377\177\000\000\270\247J\367\377\177\000\000`\002\000\000\000\000\000\000P", '\000' , " 13 15:24:40 redis[26368]: Accepted 192.168.27.128:53068\000\000\000\000\000\000\000\000\321\356", '\000' ...
(gdb) n

5、给客户端响应内容  c->buf

 void aeMain(aeEventLoop *eventLoop) {
    eventLoop->stop = 0;
    while (!eventLoop->stop) {
        if (eventLoop->beforesleep != NULL)
            eventLoop->beforesleep(eventLoop);

int writeToClient 
###
 nwritten = write(fd,c->buf+c->sentlen,c->bufpos-c->sentlen);

(gdb) p c->buf
$6 = "$6\r\nhuawei\r\n\r\nlrange\r\n:4\r\n\000\000@nJ\367\377\177\000\000!\000\000\000\000\000\000\000\270\247J\367\377\177\000\000\270\247J\367\377\177\000\000`\002\000\000\000\000\000\000P", '\000' , " 13 21:55:04 redis[31690]: Accepted 192.168.27.128:53186\000\000\000\000\000\000\000\000\321\356", '\000' ...
(gdb) n
986                nwritten = write(fd,c->buf+c->sentlen,c->bufpos-c->sentlen);
 

你可能感兴趣的:(后端,epoll,redis)