“This is the 41st day of my participation in the First Challenge 2022. For details: First Challenge 2022.”

Redis Communication Protocol (RESP)

Redis implements client and server communication based on Redis Serialization Protocal (RESP). RESP is a text protocol that is simple to implement and parse. Features and description are shown in the following table:

type Protocol description
The network layer The client and server communicate over a TCP/stream socket. To prevent sticky packets, commands or data are terminated with \r\ N (CRLF)
request *< parameter number >CR LF


< parameter 1 Number of bytes > C R L F < b r / > < parameter 1 The data of > C R L F < b r / > . . . < b r > < number of bytes for parameter 1 >CR LF<br/>< data for parameter 1 >CR LF<br/> <br>
< number of bytes of parameter N >CR LF

< data of parameter N > CR LF

* 2 \ r \ n3 \ $13 nget \ r \ n \ r \ nusername: 1234 \ r \ n. See callSendCommond -> Redis AppendConnadnArgv -> redisFromatCommandArgv
Simple String reply The first byte + +ok\r\n
Error response The first byte – -ERR unknown command ‘sa’ \r\n
Integer reply First byte: :0\r\n
Batch reply The first byte $ 6\r\nfoobar\r\n, empty reply– 1
Multiple batch reply The first byte * * 5 \ r \ n: 1 \ r \ n: 2: \ r \ n \ r \ n: 3, 4 $6 \ r \ n \ r \ nfoobar \ r \ n, empty reply * 0 \ r \ n

Special note: if the client and server are on the same machine. Then the communication protocol will be optimized, go directly to the local loop

We use the tcpdump tool to help us with network packet capture.

# linux
tcpdump -i lo part 6379 -Ann

# mac 
tcpdump -i lo0 port 6379 -Ann
Copy the code

Test it (I am running a MAC environment) :

Client A127.0. 01.:6379> set msg100 1
OK
  
Copy the code

The packet capture result of the server is as follows:

➜ ~ sudo tcpdump -i lo0 port 6379 -ann Password: tcpdump: verbose output suppressed, use -v or -vv for full protocol decode listening on lo0, link-type NULL (BSD loopback), Capture size 262144 bytes 21:52:53.447885 IP 127.0.0.1.51645 > 127.0.0.1.6379: Flags [P.], seq 1564111974:1564112006, ack 169183468, win 6272, options [nop,nop,TS val 774447713 ecr 772455554], length 32: RESP "set" "msg100" "1" E.. T.. @. @... ] :tf ........ H..... .). "a. .. * 3$3
set
$6
msg100
$11 21:52:53.447912 IP 127.0.0.1.6379 > 127.0.0.1.51645: Flags [.], ack 32, win 6376, options [nop,nop,TS val 774447713 ecr 774447713], length 0 E.. 4.. @. @... . ] :t...... (... .). "A.)"a 21:52:53.528935 IP 127.0.0.1.6379 > 127.0.0.1.51645: Flags [P.], seq 1:6, ack 32, win 6376, options [nop,nop,TS val 774447793 ecr 774447713], length 5: RESP "OK" E.. 9.. @. @... . ] :t...... -... .). "..) "A +OK 21:52:53.528966 IP 127.0.0.1.51645 > 127.0.0.1.6379: Flags [.], ack 6, win 6272, options [nop,nop,TS val 774447793 ecr 774447793], length 0 E.. 4.. @. @... ] :t. ........ (... .). "..) ".Copy the code

Client-side effects

C /cliFormatReplyTTY in redis-cli.c/cliFormatReplyTTY

static sds cliFormatReplyTTY(redisReply *r, char *prefix) {
    sds out = sdsempty();
    switch (r->type) {
    case REDIS_REPLY_ERROR:
        out = sdscatprintf(out,"(error) %s\n", r->str);
    break;
    case REDIS_REPLY_STATUS:
        out = sdscat(out,r->str);
        out = sdscat(out,"\n");
    break;
    case REDIS_REPLY_INTEGER:
        out = sdscatprintf(out,"(integer) %lld\n",r->integer);
    break;
    case REDIS_REPLY_DOUBLE:
        out = sdscatprintf(out,"(double) %s\n",r->str);
    break;
    case REDIS_REPLY_STRING:
    case REDIS_REPLY_VERB:
        /* If you are producing output for the standard output we want * a more interesting output with quoted characters and so  forth, * unless it's a verbatim string type. */
        if (r->type == REDIS_REPLY_STRING) {
            out = sdscatrepr(out,r->str,r->len);
            out = sdscat(out,"\n");
        } else {
            out = sdscatlen(out,r->str,r->len);
            out = sdscat(out,"\n");
        }
    break;
    case REDIS_REPLY_NIL:
        out = sdscat(out,"(nil)\n");
    break;
    case REDIS_REPLY_BOOL:
        out = sdscat(out,r->integer ? "(true)\n" : "(false)\n");
    break;
    case REDIS_REPLY_ARRAY:
    case REDIS_REPLY_MAP:
    case REDIS_REPLY_SET:
    case REDIS_REPLY_PUSH:
        if (r->elements == 0) {
            if (r->type == REDIS_REPLY_ARRAY)
                out = sdscat(out,"(empty array)\n");
            else if (r->type == REDIS_REPLY_MAP)
                out = sdscat(out,"(empty hash)\n");
            else if (r->type == REDIS_REPLY_SET)
                out = sdscat(out,"(empty set)\n");
            else if (r->type == REDIS_REPLY_PUSH)
                out = sdscat(out,"(empty push)\n");
            else
                out = sdscat(out,"(empty aggregate type)\n");
        } else {
            unsigned int i, idxlen = 0;
            char _prefixlen[16];
            char _prefixfmt[16];
            sds _prefix;
            sds tmp;

            /* Calculate chars needed to represent the largest index */
            i = r->elements;
            if (r->type == REDIS_REPLY_MAP) i /= 2;
            do {
                idxlen++;
                i /= 10;
            } while(i);

            /* Prefix for nested multi bulks should grow with idxlen+2 spaces */
            memset(_prefixlen,' ',idxlen+2);
            _prefixlen[idxlen+2] = '\ 0';
            _prefix = sdscat(sdsnew(prefix),_prefixlen);

            /* Setup prefix format for every entry */
            char numsep;
            if (r->type == REDIS_REPLY_SET) numsep = '~';
            else if (r->type == REDIS_REPLY_MAP) numsep = The '#';
            else numsep = ') ';
            snprintf(_prefixfmt,sizeof(_prefixfmt),"%%s%%%ud%c ",idxlen,numsep);

            for (i = 0; i < r->elements; i++) {
                unsigned int human_idx = (r->type == REDIS_REPLY_MAP) ?
                                         i/2 : i;
                human_idx++; /* Make it 1-based. */

                /* Don't use the prefix for the first element, as the parent * caller already prepended the index number. */
                out = sdscatprintf(out,_prefixfmt,i == 0 ? "" : prefix,human_idx);

                /* Format the multi bulk entry */
                tmp = cliFormatReplyTTY(r->element[i],_prefix);
                out = sdscatlen(out,tmp,sdslen(tmp));
                sdsfree(tmp);

                /* For maps, format the value as well. */
                if (r->type == REDIS_REPLY_MAP) {
                    i++;
                    sdsrange(out,0.2 -);
                    out = sdscat(out,"= >");
                    tmp = cliFormatReplyTTY(r->element[i],_prefix);
                    out = sdscatlen(out,tmp,sdslen(tmp));
                    sdsfree(tmp);
                }
            }
            sdsfree(_prefix);
        }
    break;
    default:
        fprintf(stderr."Unknown reply type: %d\n", r->type);
        exit(1);
    }
    return out;
}
Copy the code

We can also use nc command instead of redis-cli command line:

➜  ~ sudo nc 127.0.0.1 6379
set a a
+OK
get a
$1
a
Copy the code

Other instructions

  • Common errors in Redis (src/redis-cli.c)
#define REDIS_ERR -1
#define REDIS_OK 0

/* When an error occurs, the err flag in a context is set to hold the type of * error that occurred. REDIS_ERR_IO means there was an I/O error and you * should use the "errno" variable to find out what is wrong. * For other values, the "errstr" field will hold a description. */
#define REDIS_ERR_IO 1 /* Error in read or write */
#define REDIS_ERR_EOF 3 /* End of file */
#define REDIS_ERR_PROTOCOL 4 /* Protocol error */
#define REDIS_ERR_OOM 5 /* Out of memory */
#define REDIS_ERR_TIMEOUT 6 /* Timed out */
#define REDIS_ERR_OTHER 2 /* Everything else... * /

#define REDIS_REPLY_STRING 1
#define REDIS_REPLY_ARRAY 2
#define REDIS_REPLY_INTEGER 3
#define REDIS_REPLY_NIL 4
#define REDIS_REPLY_STATUS 5
#define REDIS_REPLY_ERROR 6
#define REDIS_REPLY_DOUBLE 7
#define REDIS_REPLY_BOOL 8
#define REDIS_REPLY_MAP 9
#define REDIS_REPLY_SET 10
#define REDIS_REPLY_ATTR 11
#define REDIS_REPLY_PUSH 12
#define REDIS_REPLY_BIGNUM 13
#define REDIS_REPLY_VERB 14

/* Default max unused reader buffer. */
#define REDIS_READER_MAX_BUF (1024*16)

/* Default multi-bulk element limit */
#define REDIS_READER_MAX_ARRAY_ELEMENTS ((1LL<<32) - 1)
Copy the code

Redis command object

Redis commands are managed using the redisCommand data structure.

The data structure

typedef void redisCommandProc(client *c);
// Function pointer type, pointing to the command implementation function
typedef int redisGetKeysProc(struct redisCommand *cmd, robj **argv, int argc, getKeysResult *result);
struct redisCommand {
    char *name;
    redisCommandProc *proc;
    // Limit the number of commands. -n Indicates at least N parameters, including the command itself
    int arity;
    // String mode set command attributes between the use of | operation, program internal automatic parsing, function populateCommandTable
    char *sflags;   /* Flags as string representation, one char per flag. */
    // Convert the flags string type to integer, multiple attributes
    uint64_t flags; /* The actual flags, obtained from the 'sflags' field. */
    /* Use a function to determine keys arguments in a command line. * Used for Redis Cluster redirect. */
    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, rejected_calls, failed_calls;
    int id;     /* Command ID. This is a progressive ID starting from 0 that is assigned at runtime, and is used in order to check ACLs. A connection is able to execute a given command if the user associated to the connection has this command bit set in the bitmap of allowed commands. */
};
Copy the code

For SFlag, see Redis Design and Implementation

Record flag is the flag value with the result of the operation, sflag see populateCommandTable function (SRC/server. C/populateCommandTable)

    for (int j = 0; j < argc; j++) {
        char *flag = argv[j];
        if(! strcasecmp(flag,"write")) {
            c->flags |= CMD_WRITE|CMD_CATEGORY_WRITE;
        } else if(! strcasecmp(flag,"read-only")) {
            c->flags |= CMD_READONLY|CMD_CATEGORY_READ;
        } else if(! strcasecmp(flag,"use-memory")) {
            c->flags |= CMD_DENYOOM;
        } else if(! strcasecmp(flag,"admin")) {
            c->flags |= CMD_ADMIN|CMD_CATEGORY_ADMIN|CMD_CATEGORY_DANGEROUS;
        } else if(! strcasecmp(flag,"pub-sub")) {
            c->flags |= CMD_PUBSUB|CMD_CATEGORY_PUBSUB;
        } else if(! strcasecmp(flag,"no-script")) {
            c->flags |= CMD_NOSCRIPT;
        } else if(! strcasecmp(flag,"random")) {
            c->flags |= CMD_RANDOM;
        } else if(! strcasecmp(flag,"to-sort")) {
            c->flags |= CMD_SORT_FOR_SCRIPT;
        } else if(! strcasecmp(flag,"ok-loading")) {
            c->flags |= CMD_LOADING;
        } else if(! strcasecmp(flag,"ok-stale")) {
            c->flags |= CMD_STALE;
        } else if(! strcasecmp(flag,"no-monitor")) {
            c->flags |= CMD_SKIP_MONITOR;
        } else if(! strcasecmp(flag,"no-slowlog")) {
            c->flags |= CMD_SKIP_SLOWLOG;
        } else if(! strcasecmp(flag,"cluster-asking")) {
            c->flags |= CMD_ASKING;
        } else if(! strcasecmp(flag,"fast")) {
            c->flags |= CMD_FAST | CMD_CATEGORY_FAST;
        } else if(! strcasecmp(flag,"no-auth")) {
            c->flags |= CMD_NO_AUTH;
        } else if(! strcasecmp(flag,"may-replicate")) {
            c->flags |= CMD_MAY_REPLICATE;
        } else {
            /* Parse ACL categories here if the flag name starts with @. */
            uint64_t catflag;
            if (flag[0] = =The '@' &&
                (catflag = ACLGetCommandCategoryFlagByName(flag+1)) != 0)
            {
                c->flags |= catflag;
            } else {
                sdsfreesplitres(argv,argc);
                returnC_ERR; }}}Copy the code

There are many specific commands

struct redisCommand redisCommandTable[] ={{"module",moduleCommand,2 -."admin no-script".0.NULL.0.0.0.0.0.0},

    {"get",getCommand,2."read-only fast @string".0.NULL.1.1.1.0.0.0},
     / /...
}
Copy the code

{“set”,setCommand,-3, “write use-memory @string”, 0,NULL,1,1,1,0,0,0}

The resources

  • Redis Design and Implementation, Huang Jianhong