redis——客户端

来看看客户端的数据结构:

typedef struct client {
    uint64_t id;            /* 客户端id */
    int fd;                 /* socket。fd=-1时表示伪客户端 */
    redisDb *db;            /* 当前的数据库 */
    robj *name;             /* 客户端的名字 */
    sds querybuf;           /* 查询缓冲区 */
    sds pending_querybuf;   
    size_t querybuf_peak;   /* querybuf的峰值大小 */
    int argc;               /* 当前命令的参数个数 */
    robj **argv;            /* 当前命令的参数数组 */
    struct redisCommand *cmd, *lastcmd;  /* 最后执行的一个命令 */
    int reqtype;            /* 请求协议类型,格式为: PROTO_REQ_* */
    int multibulklen;       /* 需要读取的大容量参数的数量。 */
    long bulklen;           /* 大容量参数的长度 */
    list *reply;            /* 要发送给客户端的应答对象的列表 */
    unsigned long long reply_bytes; /* 应答对象的字节数 */
    size_t sentlen;         /* 已经或正在发送到缓冲区的对象的字节数 */
    time_t ctime;           /* 客户端创建时间 */
    time_t lastinteraction; /* 最后一次交互的时间。用于判断是否超时 */
    time_t obuf_soft_limit_reached_time;
    int flags;              /* 标志,格式为: CLIENT_* macros. */
    /* 设置了权限时使用,判断是否通过权限验证,requirepass可在redis.conf中设置 */
    int authenticated;      
    int replstate;          /* 如果是从客户端,该字段记录它的复制状态 */
    int repl_put_online_on_ack; 
    int repldbfd;           /* 复制DB文件描述符 */
    off_t repldboff;        /* 复制DB文件偏移量 */
    off_t repldbsize;       /* 复制DB文件大小 */
    sds replpreamble;       /* 复制数据库序 */
    long long read_reploff; /* 如果是主客户端,此字段保存读取的DB文件偏移量 */
    long long reploff;      /* 如果是主客户端,则应用复制偏移量。 */
    long long repl_ack_off; /* 如果是从客户端,此字段保存复制的ACK偏移量 */
    long long repl_ack_time;/* 如果是从客户端,此字段保存复制的ACK时间 */
    long long psync_initial_offset;
    char replid[CONFIG_RUN_ID_SIZE+1]; /* 如果是主客户端,此字段保存主复制id */
    int slave_listening_port; /* 从客户端监听端口 */
    char slave_ip[NET_IP_STR_LEN]; 
    int slave_capa;         
    multiState mstate;      /* MULTI/EXEC 状态*/
    int btype;              /* 客户端阻塞类型 */
    blockingState bpop;     /* 阻塞状态 */
    long long woff;
    list *watched_keys;     /* MULTI/EXEC CAS 被监听的key*/
    dict *pubsub_channels;  /* 客户端订阅的频道 */
    list *pubsub_patterns;  /* 客户端订阅的模式 */
    sds peerid;             /* 缓存同级id */

    /* 输出缓冲区 */
    int bufpos;
    char buf[PROTO_REPLY_CHUNK_BYTES];
} client;

在服务器结构中,客户端属性是一个链表:

struct redisServer {
    //...
    list *clients;
    //...
};

可以通过以下命令来查看客户端状态:

id:客户端唯一标识;

addr:客户端ip与端口;

fd:套接字描述符,fd=9,说明这是普通客户端;

name:名称。可以通过命令来设置:

 age:记录从创建客户端开始,客户端与服务端连接的时间(秒);

db:当前数据库为0号数据库。


下面来看看客户端连接到服务端的过程:

在初始化配置之后,调用cliConnect方法:

static int cliConnect(int force) {
    if (context == NULL || force) {
        if (context != NULL) {
            redisFree(context);
        }
        
        if (config.hostsocket == NULL) {/* TCP连接 */
            context = redisConnect(config.hostip,config.hostport);
        } else {/* Unix连接 */
            context = redisConnectUnix(config.hostsocket);
        }

        if (context->err) {/* 记录错误信息,释放空间 */
            fprintf(stderr,"Could not connect to Redis at ");
            if (config.hostsocket == NULL)
                fprintf(stderr,"%s:%d: %s\n",config.hostip,config.hostport,context->errstr);
            else
                fprintf(stderr,"%s: %s\n",config.hostsocket,context->errstr);
            redisFree(context);
            context = NULL;
            return REDIS_ERR;
        }

        /* 场链接 */
        anetKeepAlive(NULL, context->fd, REDIS_CLI_KEEPALIVE_INTERVAL);

        /* 校验权限和选择数据库 */
        if (cliAuth() != REDIS_OK)
            return REDIS_ERR;
        if (cliSelect() != REDIS_OK)
            return REDIS_ERR;
    }
    return REDIS_OK;
}

 来看看调用redisConnect方法,通过tcp连接:

redisContext *redisConnect(const char *ip, int port) {
    redisContext *c;

    c = redisContextInit();
    if (c == NULL)
        return NULL;

    c->flags |= REDIS_BLOCK;
    redisContextConnectTcp(c,ip,port,NULL);
    return c;
}

关注redisContext数据结构:

typedef struct redisContext {
    int err; /* 错误标志。没有错误时err=0 */
    char errstr[128]; /* 错误描述 */
    int fd;
    int flags; 
    char *obuf; /* 写缓冲区 */
    redisReader *reader; 

    enum redisConnectionType connection_type; /* 连接类型:tcp、unix */
    struct timeval *timeout; /* 超时时间 */

    struct {/* tcp结构 */
        char *host;
        char *source_addr;
        int port;
    } tcp;

    struct { /* unix结构 */
        char *path;
    } unix_sock;

} redisContext;

其中包含了redisReader:

typedef struct redisReader {
    int err; 
    char errstr[128]; 

    char *buf; /* 读缓冲区 */
    size_t pos; /* 指针 */
    size_t len; /* 缓冲区长度 */
    size_t maxbuf; /* 未使用的缓冲区的最大长度 */

    redisReadTask rstack[9]; /* 读任务数组 */
    int ridx; /* 当前读任务的下标 */
    void *reply; /* 临时reply的指针 */

    redisReplyObjectFunctions *fn;
    void *privdata;
} redisReader;

回到redisConner方法主体,初始化redisContext:

static redisContext *redisContextInit(void) {
    redisContext *c;

    c = calloc(1,sizeof(redisContext));
    if (c == NULL)
        return NULL;

    c->err = 0;
    c->errstr[0] = '\0';
    c->obuf = sdsempty();
    c->reader = redisReaderCreate();
    c->tcp.host = NULL;
    c->tcp.source_addr = NULL;
    c->unix_sock.path = NULL;
    c->timeout = NULL;

    if (c->obuf == NULL || c->reader == NULL) {
        redisFree(c);
        return NULL;
    }

    return c;
}

最后调用redisContextConnectTcp方法,通过tcp连接。


在连接成功之后,设置长连接,校验权限和选择数据库。

这里来看看校验权限:其中redisCommand方法会阻塞客户端直到服务器回复

static int cliAuth(void) {
    redisReply *reply;
    /* 如果没有设置权限限制,那么不校验 */
    if (config.auth == NULL) return REDIS_OK;

    /* 将AUTH命令发送到服务端,等待服务端的回复 */
    reply = redisCommand(context,"AUTH %s",config.auth);
    if (reply != NULL) {
        freeReplyObject(reply);
        return REDIS_OK;
    }
    return REDIS_ERR;
}

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值