Redis(六):Redis客户端初始化

1. 客户端状态:struct redisClient

    1.1 标志flags

    1.2  输入缓冲区querybuf

    1.3 命令argv和命令参数个数argc

    1.4 命令实现函数cmd

    1.5 输出缓冲区buf和reply

    1.6 身份验证authenticated

2. 客户端的创建

    2.1 客户端redis-cli创建流程

    2.2 相关数据结构

    2.3 主函数


    Redis是一个典型的一对多的服务程序,一个服务器可以和多个客户端建立网络连接,对于每个与服务器连接的客户端,服务器都为这些客户端连接了相应的redisClient结构,这个结构保存了客户端当前的状态信息,以及执行相关功能所需要的数据结构。

1. 客户端状态:struct redisClient

    Redis服务器的clients属性是一个链表,这个链表保存了所有与服务器连接的客户端的状态结构,查找某个指定的客户端可以通过遍历clients链表来完成。

struct redisServer {
    // ......
    
    list *clients; // 一个链表,保存了所有客户端状态

    // ......
};

  客户端状态redisClient的数据结构定义如下: 

typedef struct redisClient {
    
    int fd;                             // 套接字描述符
    redisDb *db;                        // 当前正在使用的数据库
    int dictid;                         // 当前正在使用的数据库的 id (号码)
    robj *name;                         // 客户端的名字,可以通过命令CLIENT SETNAME 进行设置
    sds querybuf;                       // 查询缓冲区
    size_t querybuf_peak;               // 查询缓冲区长度峰值
    int argc;                           // 参数数量
    robj **argv;                        // 参数对象数组
    struct redisCommand *cmd, *lastcmd; // 记录被客户端执行的命令
    int reqtype;                        // 请求的类型:内联命令还是多条命令
    int multibulklen;                   // 剩余未读取的命令内容数量
    long bulklen;                       // 命令内容的长度
    list *reply;                        // 回复链表
    unsigned long reply_bytes;          // 回复链表中对象的总大小
    int sentlen;                        // 已发送字节,处理 short write 用
    time_t ctime;                       // 创建客户端的时间
    time_t lastinteraction;             // 客户端最后一次和服务器互动的时间
    time_t obuf_soft_limit_reached_time; // 客户端的输出缓冲区超过软性限制的时间 
    int flags;                          // 客户端状态标志 REDIS_SLAVE | REDIS_MONITOR | REDIS_MULTI ...
    int authenticated;                  // 当 server.requirepass 不为 NULL 时,代表认证的状态,0 代表未认证, 1 代表已认证
    int replstate;                      // 复制状态
    int repldbfd;                       // 用于保存主服务器传来的 RDB 文件的文件描述符
    off_t repldboff;                    // 读取主服务器传来的 RDB 文件的偏移量
    off_t repldbsize;                   /* replication DB file size */
    sds replpreamble;                   /* replication DB preamble. */
    long long reploff;                  // 主服务器的复制偏移量
    long long repl_ack_off;             // 从服务器最后一次发送 REPLCONF ACK 时的偏移量
    long long repl_ack_time;            // 从服务器最后一次发送 REPLCONF ACK 的时间
    char replrunid[REDIS_RUN_ID_SIZE+1]; // 主服务器的 master run ID,保存在客户端,用于执行部分重同步
    int slave_listening_port;           // 从服务器的监听端口号
    multiState mstate;                  // 事务状态
    int btype;                          // 阻塞类型
    blockingState bpop;                 // 阻塞状态
    long long woff;                     // 最后被写入的全局复制偏移量
    list *watched_keys;                 // 被监视的键
    dict *pubsub_channels;              // 这个字典记录了客户端所有订阅的频道,键为频道名字,值为 NULL,也即是,一个频道的集合
    list *pubsub_patterns;              // 链表,包含多个 pubsubPattern 结构,记录了所有订阅频道的客户端的信息,新 pubsubPattern 结构总是被添加到表尾
    sds peerid;                         // 缓存的 peer ID
    int bufpos;                         // 回复偏移量
    char buf[REDIS_REPLY_CHUNK_BYTES];  // 回复缓冲区

} redisClient;

  1.1 标志flags

    客户端的标志属性flags记录了客户端的角色,以及客户端目前所处的状态。flags的属性值可以是单个标志,也可以是多个标志的二进制组成。每个标志使用一个常量表示,一个部分标志记录了客户端的角色,另一部分标志则记录了客户端目前所处的状态。flags = <flag1> | <flag2> | ...

    客户端的角色:

  • REDIS_MASTER:标志客户端代表的是一个主服务器
  • REDIS_SLAVE:标志客户端代表的是一个从服务器
  • REDIS_PER_PSYNC:标志客户端代表的是一个版本低于Redis2.8的从服务器
  • REDIS_LUA_CLIENT:标志客户端是专门用于处理Lua脚本里面包含的Redis命令的伪客户端
  • REDIS_MONITOR:标志客户端是一个slave monitor

    客户端所处的状态:

  • REDIS_UNIX_SOCKET:标志服务器使用UNIX套接字来连接客户端
  • REDIS_BLOCKED:标志客户端正在被BRPOP、BLPOP等命令阻塞
  • REDIS_UNBLOCKED:标志客户端已经从阻塞状态中脱离出来
  • REDIS_MULTI:标志客户端正在执行事务
  • REDIS_DIRTY_EXEC:标志事务在命令入队时出现错误
  • REDIS_DIRTY_CAS:表示事务使用WATCH命令监视的数据库键已经被修改
  • REDIS_CLOSE_ASAP:标志客户端的输入缓冲区超过了服务器允许的范围,服务器会在下一次执行serverCron函数时关闭这个客户端
  • REDIS_CLOSE_AFTER_REPLY:表示有用户对这个客户端执行了CLIENT KILL命令,或者客户端发送给服务器的命令请求中包含了错误的协议内容
  • REDIS_ASKING:标志客户端向集群节点发送了ASKING命令
  • REDIS_FORCE_AOF:标志表示强制服务器将当前的执行命令写入AOF文件里面
  • REDIS_FORCE_REPL:表示强制主服务器将当前执行命令复制给所有从服务器。执行SCRIPT LOAD命令会使得客户端打开以上这两个标志。

  1.2  输入缓冲区querybuf

    客户端的输入缓冲区用于保存客户端发送的命令请求,输入缓冲区会根据输入内容动态地缩小或者扩大,但是它的大小不能超过1GB,否则服务器将关闭这个客户端。

  1.3 命令argv和命令参数个数argc

    服务器将客户端发送的命令请求保存到客户端状态的querybuf属性之后,服务器将对命令请求的内容进行分析,并将得出的命令参数和命令参数个数分别保存到客户端状态的argv和argc属性中。argv是一个数组,数组中每一项保存了一个字符串对象,argv[0]是要执行的命令,之后则是传给命令的参数。argc则负责记录argv数组的长度。

  1.4 命令实现函数cmd

  解析的到argv[0]之后,服务器将在命令表中查找对应的命令实现函数。并将客户端状态的cmd指针指向这个结构。之后,服务器就可以使用cmd属性所指向的redisCommand结构,以及argv和argc属性中保存的命令参数信息,调用命令实现函数,执行客户端指定的指令。

  1.5 输出缓冲区buf和reply

    执行命令得到命令回复会被保存在客户端的输入缓冲区里面,每个客户端有两个输出缓冲区,一个缓冲区大小固定,buf默认大小为16KB,用于保存那些长度比较小的回复。另外一个缓冲区大小可变,reply是一个链表,用于保存那些长度比较大的回复。

  1.6 身份验证authenticated

    authenticated属性用于保存客户端是否通过了了身份验证,0表示验证通过,1表示验证未通过。

2. 客户端的创建

  2.1 客户端redis-cli创建流程

        

  • 填写配置信息到config中:填写hostip、port、dbnum等信息到struct config结构体中
  • 解析命令行参数到config中:解析redis client启动时的命令行参数,解析参数并保存到struct config结构体中
  • 创建socket、连接server:在cliConnect()函数中创建套接字,并且连接服务器
  • 设置Nagle和TCP的keepalive:客户端禁用Nagle算法,这样可以避免出现粘包问题;开启TCP的keepalive字段,用于在长连接时进行心跳检测
  • 客户端认证auth:如果config结构体中auth字段保存了密码,则需要进行认证,发送命令:auth passwd
  • 选择server数据库:根据config结构体中配置的dbnum,选择服务器的数据库,发送命令:select dbnum
  • 进入交互界面:进入一个无限循环,不断从命令行中读取命令,解析参数,发送命令,并且打印出命令的执行结果

  2.2 相关数据结构

/* 连接redis的上下文信息 */
typedef struct redisContext {
    int err;              /* Error flags, 0 when there is no error */
    char errstr[128];     /* String representation of error when applicable */
    int fd;               // socket文件描述符
    int flags;
    char *obuf;           /* Write buffer */
    redisReader *reader;  /* Protocol reader */
} redisContext;

static redisContext *context; // 如果context为NULL,则表示需要重新连接

/* 保存连接redis服务器所需要的一些配置信息 */
static struct config {
    char *hostip;          // IP地址
    int hostport;          // 端口号
    char *hostsocket;
    long repeat;
    long interval;
    int dbnum;             // 选择使用哪个数据库
    int interactive;
    
    ... ...

} config;

  2.3 主函数

int main(int argc, char **argv) {
    int firstarg;

    config.hostip = sdsnew("127.0.0.1");
    config.hostport = 6379;
    config.hostsocket = NULL;
    config.repeat = 1;
    config.interval = 0;
    config.dbnum = 0;
    
    ... ...    

    if (!isatty(fileno(stdout)) && (getenv("FAKETTY") == NULL))
        config.output = OUTPUT_RAW;
    else
        config.output = OUTPUT_STANDARD;
    config.mb_delim = sdsnew("\n");
    cliInitHelp();

    firstarg = parseOptions(argc,argv); // 解析命令行参数到config中
    argc -= firstarg;
    argv += firstarg;

    ... ...

    /* 如果命令行没有任何参数,直接进入交互模式 */
    if (argc == 0 && !config.eval) {
        cliConnect(0); // 连接服务器
        repl();        // 进入交互界面
    }
    
    ... ... 
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值