结合redis设计与实现的redis源码学习-18-网络连接库(networking.c)

本文深入探讨Redis的网络连接库networking.c,介绍其在处理命令发送、响应接收、客户端管理及协议解析等方面的关键功能。通过分析结构体定义,揭示Redis内部通信机制。
摘要由CSDN通过智能技术生成

networking.cRedis 的网络连接库,负责发送命令回复和接受命令请求, 同时也负责创建/销毁客户端, 以及通信协议分析等工作.
下面是部分使用的结构体,定义在server.h中

//命令结构体
struct redisCommand {
    char *name;
    redisCommandProc *proc;//命令处理器
    int arity;//元数
    char *sflags; /* Flags as string representation, one char per flag. 用字符串表示标志,*/
    int flags;    /* The actual flags, obtained from the 'sflags' field. 实际标志,从sflags中获得*/
    /* Use a function to determine keys arguments in a command line. Used for Redis Cluster redirect. 使用函数来确定命令行中的参数,用作Redis集群重定向*/
    redisGetKeysProc *getkeys_proc;
    /* What keys should be loaded in background when calling this command? 调用此命令时应该在后台加载那些秘钥*/
    int firstkey; /* The first argument that's a key (0 = no keys) 第一个参数是键*/
    int lastkey;  /* The last argument that's a key 最后一个参数是键*/
    int keystep;  /* The step between first and last key 中间的步数*/
    long long microseconds, calls;
};

/* With multiplexing we need to take per-client state. Clients are taken in a linked list. 在多路复用下我们需要记录每个客户端的状态,放在列表中*/
typedef struct client {
    uint64_t id;            /* Client incremental unique ID. 唯一标识id*/
    int fd;                 /* Client socket. socket描述符*/
    redisDb *db;            /* Pointer to currently SELECTed DB. 选择/指向的数据库*/
    int dictid;             /* ID of the currently SELECTed DB. 当前选择的数据库字典的id*/
    robj *name;             /* As set by CLIENT SETNAME. 对象名称*/
    sds querybuf;           /* Buffer we use to accumulate client queries. 客户端的查询环城区*/
    size_t querybuf_peak;   /* Recent (100ms or more) peak of querybuf size. 最近100ms的缓冲区大小峰值*/
    int argc;               /* Num of arguments of current command. 当前命令参数的个数*/
    robj **argv;            /* Arguments of current command. 当前命令参数的数组*/
    struct redisCommand *cmd, *lastcmd;  /* Last command executed. 最后执行的命令*/
    int reqtype;            /* Request protocol type: PROTO_REQ_* 请求的协议类型*/
    int multibulklen;       /* Number of multi bulk arguments left to read. 尧都区多个批量参数的数量*/
    long bulklen;           /* Length of bulk argument in multi bulk request. 多批量请求中批量参数的长度*/
    list *reply;            /* List of reply objects to send to the client. 返回给客户端的reply对象列表*/
    unsigned long long reply_bytes; /* Tot bytes of objects in reply list. reply对象列表的字节数*/
    size_t sentlen;         /* Amount of bytes already sent in the current buffer or object being sent. 在当前正在发送的缓冲区或对象中已经发送的字节数*/
    time_t ctime;           /* Client creation time. 客户端创建时间*/
    time_t lastinteraction; /* Time of the last interaction, used for timeout 最后一次交互的时间,用于超时*/
    time_t obuf_soft_limit_reached_time;
    int flags;              /* Client flags: CLIENT_* macros. 客户端标志*/
    int authenticated;      /* When requirepass is non-NULL. 认证号*/
    int replstate;          /* Replication state if this is a slave. 复制状态,如果这是一个从服务器*/
    int repl_put_online_on_ack; /* Install slave write handler on ACK. 安装从机写句柄在ack*/
    int repldbfd;           /* Replication DB file descriptor. 复制数据库文件描述符*/
    off_t repldboff;        /* Replication DB file offset. db文件描述符偏移量*/
    off_t repldbsize;       /* Replication DB file size. 复制的大小*/
    sds replpreamble;       /* Replication DB preamble.复制db前言 */
    long long reploff;      /* Replication offset if this is our master. 复制抵消,如果这是主机的话*/
    long long repl_ack_off; /* Replication ack offset, if this is a slave. 复制请抵消,如果这是从机*/
    long long repl_ack_time;/* Replication ack time, if this is a slave. 复制请求时间,从机*/
    long long psync_initial_offset; /* FULLRESYNC reply offset other slaves copying this slave output buffer should use. 回复其他从机复制这个从机输出缓冲区使用*/
    char replrunid[CONFIG_RUN_ID_SIZE+1]; /* Master run id if is a master. 主机的运行id*/
    int slave_listening_port; /* As configured with: REPLCONF listening-port 从机端口*/
    char slave_ip[NET_IP_STR_LEN]; /* Optionally given by REPLCONF ip-address 从机可选ip*/
    int slave_capa;         /* Slave capabilities: SLAVE_CAPA_* bitwise OR. 从属功能*/
    multiState mstate;      /* MULTI/EXEC state 事务状态*/
    int btype;              /* Type of blocking op if CLIENT_BLOCKED. 阻塞类型*/
    blockingState bpop;     /* blocking state 阻塞状态*/
    long long woff;         /* Last write global replication offset. 最后写全局复制偏移量*/
    list *watched_keys;     /* Keys WATCHED for MULTI/EXEC CAS 在事务过程中观察的key链表*/
    dict *pubsub_channels;  /* channels a client is interested in (SUBSCRIBE) 客户端订阅的通道*/
    list *pubsub_patterns;  /* patterns a client is interested in (SUBSCRIBE) 客户端订阅的模式*/
    sds peerid;             /* Cached peer ID. 缓存对等id*/

    /* Response buffer 返回缓冲区*/
    int bufpos;
    char buf[PROTO_REPLY_CHUNK_BYTES];
} client;

networking.c

#include "server.h"
#include <sys/uio.h>
#include <math.h>
static void setProtocolError(client *c, int pos);

/* Return the size consumed from the allocator, for the specified SDS string, including internal fragmentation. This function is used in order to compute the client output buffer size. 从指定的sds返回分配器消耗的大小,包括内部碎片,这个函数用来计算客户端输出缓冲区大小*/
size_t sdsZmallocSize(sds s) {
    void *sh = sdsAllocPtr(s);
    return zmalloc_size(sh);
}
/* Return the amount of memory used by the sds string at object->ptr for a string object. 返回字符串对象中sds使用的内存量*/
size_t getStringObjectSdsUsedMemory(robj *o) {
    serverAssertWithInfo(NULL,o,o->type == OBJ_STRING);
    switch(o->encoding) {
    case OBJ_ENCODING_RAW: return sdsZmallocSize(o->ptr);
    case OBJ_ENCODING_EMBSTR: return zmalloc_size(o)-sizeof(robj);
    default: return 0; /* Just integer encoding for now. */
    }
}
//客户端响应值引用计数+1
void *dupClientReplyValue(void *o) {
    incrRefCount((robj*)o);
    return o;
}
//链表比较对象
int listMatchObjects(void *a, void *b) {
    return equalStringObjects(a,b);
}
//创建客户端
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. 传递-1作为FD可以创建一个非连接的客户端,这是很有用的,因为所有的命令都需要在客户端的上下文中执行。挡在其他上下文中执行命令时,我们需要一个未连接的客户端*/
    if (fd != -1) {
        anetNonBlock(NULL,fd);//将文件描述符设置为非阻塞
        anetEnableTcpNoDelay(NULL,fd);//支持非延迟发送
        if (server.tcpkeepalive)
            anetKeepAlive(NULL,fd,server.tcpkeepalive);//让连接保持声明周期
        if (aeCreateFileEvent(server.el,fd,AE_READABLE,//创建文件事件
            readQueryFromClient, c) == AE_ERR)
        {
            close(fd);
            zfree(c);
            return NULL;
        }
    }

    selectDb(c,0);//选择数据库
    c->id = server.next_client_id++;
    c->fd = fd;
    c->name = NULL;
    c->bufpos = 0;
    c->querybuf = sdsempty();
    c->querybuf_peak = 0;
    c->reqtype = 0;
    c->argc = 0;
    c->argv = NULL;
    c->cmd = c->lastcmd = NULL;
    c->multibulklen = 0;
    c->bulklen = -1;
    c->sentlen = 0;
    c->flags = 0;
    c->ctime = c->lastinteraction = server.unixtime;
    c->authenticated = 0;
    c->replstate = REPL_STATE_NONE;
    c->repl_put_online_on_ack = 0;
    c->reploff = 0;
    c->repl_ack_off = 0;
    c->repl_ack_time = 0;
    c->slave_listening_port = 0;
    c->slave_ip[0] = '\0';
    c->slave_capa = SLAVE_CAPA_NONE;
    c->reply = listCreate();
    c->reply_bytes = 0;
    c->obuf_soft_limit_reached_time = 0;
    listSetFreeMethod(c->reply,decrRefCountVoid);//设置释放函数
    listSetDupMethod(c->reply,dupClientReplyValue);//设置复制函数
    c->btype = BLOCKED_NONE;
    c->bpop.timeout = 0;
    c->bpop.keys = dictCreate(&setDictType,NULL);//创建客户端键空间
    c->bpop.target = NULL;
    c->bpop.numreplicas = 0;
    c->bpop.reploffset = 0;
    c->woff = 0;
    c->watched_keys = listCreate();
    c->pubsub_channels = dictCreate(&setDictType,NULL);//创建
    c->pubsub_patterns = listCreate();
    c->peerid = NULL;
    listSetFreeMethod(c->pubsub_patterns,decrRefCountVoid);
    listSetMatchMethod(c->pubsub_patterns,listMatchObjects);
    if (fd != -1) listAddNodeTail(server.clients,c);//将客户端加入链表
    initClientMultiState(c);//初始化事务状态
    return c;
}

/* This function is called every time we are going to transmit new data to the client. The behavior is the following: If the client should receive new data (normal clients will) the function returns C_OK, and make sure to install the write handler in our event loop so that when the socket is writable new data gets written. If the client should not receive new data, because it is a fake client (used to load AOF in memory), a master or because the setup of the write handler failed, the function returns C_ERR. The function may return C_OK without actually installing the write event handler in the following cases:每当我们要出书新的数据给客户端时,都会调用这个函数,其行为如下:如果客户端应该接受新数据,函数返回C_OK,并确保在我们的事件循环中安装写入处理程序,一遍在套接字可写入时写入新的数据。如果客户端并应该接受新数据,一位它是假客户端,主服务器或由于写处理程序的设置失败,函数返回C_ERR。在以下情况,该函数可能会返回C_OK而不是时间安装写入事件处理程序:
 * 1) The event handler should already be installed since the output buffer already contained something.事件处理程序已经安装,因为输出缓冲区已经包含了一些东西。
 * 2) The client is a slave but not yet online, so we want to just accumulate writes in the buffer but not actually sending them yet. Typically gets called every time a reply is built, before adding more data to the clients output buffers. If the function returns C_ERR no data should be appended to the output buffers. 客户端是一个奴隶,但还没有在线,所以我们只想累计写入缓冲区,但实际上并没有发送他们,通常在每次构建回复时都会调用,然后再向客户端输出缓冲区添加更多数据。如果返回C_ERR,则不应将数据附加到输出缓冲区*/
int prepareClientToWrite(client *c) {
    /* If it's the Lua client we always return ok without installing any handler since there is no socket at all. 如果是lua客户端,总是返回ok并且不安装任何处理程序,因为没有套接字*/
    if (c->flags & CLIENT_LUA) return C_OK;

    /* CLIENT REPLY OFF / SKIP handling: don't send replies. 客户端回复关闭/跳过处理:不发送回复*/
    if (c->flags & (CLIENT_REPLY_OFF|CLIENT_REPLY_SKIP)) return C_ERR;

    /* Masters don't receive replies, unless CLIENT_MASTER_FORCE_REPLY flag is set. 主机没有收到回复,flag设置*/
    if ((c->flags & CLIENT_MASTER) &&
        !(c->flags & CLIENT_MASTER_FORCE_REPLY)) return C_ERR;

    if (c->fd <= 0) return C_ERR; /* Fake client for AOF loading.假装AOF加载客户端 */

    /* Schedule the client to write the output buffers to the socket only if not already done (there were no pending writes already and the client was yet not flagged), and, for slaves, if the slave can actually receive writes at this stage. 安排客户端将输出缓冲区写入套接字,只有在尚未完成的情况下,以及对于从服务器,如果从服务器在此阶段能够世界接收写入数据请求*/
    if (!clientHasPendingReplies(c) &&
        !(c->flags & CLIENT_PENDING_WRITE) &&
        (c->replstate == REPL_STATE_NONE ||
         (c->replstate == SLAVE_STATE_ONLINE && !c->repl_put_online_on_ack)))
    {
        /* Here instead of installing the write handler, we just flag the client and put it into a list of clients that have something to write to the socket. This way before re-entering the event loop, we can try to directly write to the client sockets avoiding a system call. We'll only really install the write handler if we'll not be able to write the whole reply at once. 在这里不是安装写处理程序,而是将客户端标记,并将其放入要写入套接字的客户端列表中,重新进入时间循环之前,我们可以尝试直接写入客户端套接字,避免系统调用,如果我们不能一次写完整个回复,我们只会真正安装写处理程序*/
        c->flags |= CLIENT_PENDING_WRITE;
        listAddNodeHead(server.clients_pending_write,c);
    }

    /* Authorize the caller to queue in the output buffer of this client. 授权调用者在此客户端的输出缓冲区中排队*/
    return C_OK;
}
/* Create a duplicate of the last object in the reply list when it is not exclusively owned by the reply list. 如果答复列表中的最后一个对象不属于答复列表的所有者,则创建最后一个对象的副本*/
robj *dupLastObjectIfNeeded(list *reply) {
    robj *new, *cur;
    listNode *ln;
    serverAssert(listLength(reply) > 0);
    ln = listLast(reply);
    cur = listNodeValue(ln);
    if (cur->refcount > 1) {
        new = dupStringObject(cur);
        decrRefCount(cur);
        listNodeValue(ln) = new;
    }
    return listNodeValue(ln);
}
/* -----------------------------------------------------------------------------
 * Low level functions to add more data to output buffers.
 * -------------------------------------------------------------------------- */
//将reply数据写入缓冲区
int _addReplyToBuffer(client *c, const char *s, size_t len) {
    size_t available = sizeof(c->buf)-c->bufpos;

    if (c->flags & CLIENT_CLOSE_AFTER_REPLY) return C_OK;

    /* If there already are entries in the reply list, we cannot add anything more to the static buffer. 如果条目已经在链表中,不会再增加任何东西到buffer中*/
    if (listLength(c->reply) > 0) return C_ERR;

    /* Check that the buffer has enough space available for this string. 检查buffer是否有足够空间放这个string*/
    if (len > available) return C_ERR;

    memcpy(c->buf+c->bufpos,s,len);
    c->bufpos+=len;
    return C_OK;
}
//将对象放入链表
void _addReplyObjectToList(client *c, robj *o) {
    robj *tail;

    if (c->flags & CLIENT_CLOSE_AFTER_REPLY) return;

    if (listLength(c->reply) == 0) {
        incrRefCount(o);
        listAddNodeTail(c->reply,o);//将对象放入链表
        c->reply_bytes += getStringObjectSdsUsedMemory(o);
    } else {
        tail = listNodeValue(listLast(c->reply));//获取链表尾节点

        /* Append to this object when possible. 可以的话追加到这个对象*/
        if (tail->ptr != NULL &&
            tail->encoding == OBJ_ENCODING_RAW &&
            sdslen(tail->ptr)+sdslen(o->ptr) <= PROTO_REPLY_CHUNK_BYTES)
        {
            c->reply_bytes -= sdsZmallocSize(tail->ptr);
            tail = dupLastObjectIfNeeded(c->reply);
            tail->ptr = sdscatlen(tail->ptr,o->ptr,sdslen(o->ptr));
            c->reply_bytes += sdsZmallocSize(tail->ptr);
        } else {
  //不行就在家一个节点
            incrRefCount(o);
            listAddNodeTail(c->reply,o);
            c->reply_bytes += getStringObjectSdsUsedMemory(o);
        }
    }
    asyncCloseClientOnOutputBufferLimitReached(c);
}
/* This method takes responsibility over the sds. When it is no longer needed it will be free'd, otherwise it ends up in a robj. 这个方法对sds负责,当它不再需要时,它将被释放,否自将以robj结束*/
void _addReplySdsToList(client *c, sds s) {
    robj *tail;

    if (c->flags & CLIENT_CLOSE_AFTER_REPLY) {
        sdsfree(s);
        return;
    }

    if (listLength(c->reply) == 0) {
  //链表为空,直接加
        listAddNodeTail(c->reply,createObject(OBJ_STRING,s));
        c->reply_bytes += sdsZmallocSize(s);
    } else {
        tail = listNodeValue(listLast(c->reply));

        /* Append to this object when possible. 尝试追加到尾部节点*/
        if (tail->ptr != NULL && tail->encoding == OBJ_ENCODING_RAW &&
            sdslen(tail->ptr)+sdslen(s) <= PROTO_REPLY_CHUNK_BYTES)
        {
            c->reply_bytes -= sdsZmallocSize(tail->ptr);
            tail = dupLastObjectIfNeeded(c->reply);
            tail->ptr = sdscatlen(tail->ptr,s,sdslen(s));
            c->reply_bytes += sdsZmallocSize(tail->ptr);
            sdsfree(s);
        } else {
  //新加入一个节点
            listAddNodeTail(c->reply,createObject(OBJ_STRING,s));
            c->reply_bytes += sdsZmallocSize(s);
        }
    }
    asyncCloseClientOnOutputBufferLimitReached(c);
}
//将字符串追加到链表
void _addReplyStringToList(client *c, const char *s, size_t len) {
    robj *tail;

    if (c->flags & CLIENT_CLOSE_AFTER_REPLY) return;

    if (listLength(c->reply) == 0) {
        robj *o = createStringObject(s,len);

        listAddNodeTail(c->reply,o);
        c->reply_bytes += getStringObjectSdsUsedMemory(o);
    } else {
        tail = listNodeValue(listLast(c->reply));

        /* Append to this object when possible. */
        if (tail->ptr != NULL && tail->encoding == OBJ_ENCODING_RAW &&
            sdslen(tail->ptr)+len <= PROTO_REPLY_CHUNK_BYTES)
        {
            c->reply_bytes -= sdsZmallocSize(tail->ptr);
            tail = dupLastObjectIfNeeded(c->reply);
            tail->ptr = sdscatlen(tail->ptr,s,len);
            c->reply_bytes += sdsZmallocSize(tail->ptr);
        } else {
            robj *o = createStringObject(s,len);

            listAddNodeTail(c->reply,o);
            c->reply_bytes += getStringObjectSdsUsedMemory(o);
        }
    }
    asyncCloseClientOnOutputBufferLimitReached(c);
}
/* -----------------------------------------------------------------------------
 * Higher level functions to queue data on the client output buffer. The following functions are the ones that commands implementations will call.
 * -------------------------------------------------------------------------- */
//增加回复对象
void addReply(client *c, robj *obj) {
    if (prepareClientToWrite(c) != C_OK) return;

    /* This is an important place where we can avoid copy-on-write when there is a saving child running, avoiding touching the refcount field of the object if it's not needed. If the encoding is RAW and there is room in the static buffer we'll be able to send the object to the client without messing with its page.这是一个重要的地方,我们可以避免在保存子运行时写入复制,避免触摸对象的引用次数字段,如果编码是raw,并且在静态缓冲区中有空间,我们就可以把对象发送给客户端,而不会弄乱页面 */
    if (sdsEncodedObject(obj)) {
        if (_addReplyToBuffer(c,obj->ptr,sdslen(obj->ptr)) != C_OK)
            _addReplyObj
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值