Databases 17 min read

Mastering Redis RESP Protocol: From Wire Format to Command Objects

This article explains how Redis uses the simple text‑based RESP protocol for client‑server communication, details each reply type, shows how to capture traffic with tcpdump, and dives into the internal C structures and error codes that power Redis commands.

Ops Development Stories
Ops Development Stories
Ops Development Stories
Mastering Redis RESP Protocol: From Wire Format to Command Objects

RESP Protocol

Redis communicates with clients using the Redis Serialization Protocol (RESP), a lightweight text protocol where every command or data element ends with

\r\n

(CRLF) to avoid packet sticking.

Network layer : TCP/stream sockets with CRLF termination, e.g.,

+ok\r\n

.

Request format :

*<argument count>\r\n<argument length>\r\n<argument data>\r\n...

(example:

*2\r\n3\r\nget\r\n$13\r\nusername:1234\r\n

).

Simple string reply : starts with

+

, e.g.,

+OK\r\n

.

Error reply : starts with

-

, e.g.,

-ERR unknown command 'sa'\r\n

.

Integer reply : starts with

:

, e.g.,

:0\r\n

.

Bulk reply : starts with

$

, e.g.,

$6\r\nfoobar\r\n

(empty bulk is

$-1

).

Array (multi‑bulk) reply : starts with

*

, e.g.,

*5\r\n:1\r\n:2\r\n:3\r\n:4\r\n$6\r\nfoobar\r\n

.

When client and server run on the same machine, the protocol can be optimized to use the loopback interface.

Example of capturing traffic with

tcpdump

:

# linux
tcpdump -i lo port 6379 -Ann
# mac
tcpdump -i lo0 port 6379 -Ann

Testing with the command

set msg100 1

on macOS yields the following packet dump (truncated for brevity):

➜  ~ sudo tcpdump -i lo0 port 6379 -Ann
... length 32: RESP "set" "msg100" "1"
... length 5: RESP "OK"

The

redis-cli

client formats replies for human‑readable output. Core formatting logic is shown below:

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;
    // ... other cases omitted for brevity ...
    }
    return out;
}

Alternatively, the

nc

(netcat) command can be used to interact with Redis directly:

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

Error Codes

Common Redis error codes are defined as macros:

#define REDIS_ERR -1
#define REDIS_OK 0
#define REDIS_ERR_IO 1
#define REDIS_ERR_EOF 3
#define REDIS_ERR_PROTOCOL 4
#define REDIS_ERR_OOM 5
#define REDIS_ERR_TIMEOUT 6
#define REDIS_ERR_OTHER 2

#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

Shared objects for common replies are created in

createSharedObjects()

(excerpt):

void createSharedObjects(void) {
    shared.ok = createObject(OBJ_STRING,sdsnew("+OK\r\n"));
    shared.emptybulk = createObject(OBJ_STRING,sdsnew("$0\r\n\r\n"));
    shared.czero = createObject(OBJ_STRING,sdsnew(":0\r\n"));
    // ... many other shared objects ...
}

Command Objects

Redis commands are described by the

redisCommand

structure, which includes the command name, implementation pointer, arity, flag strings, and runtime statistics.

typedef void redisCommandProc(client *c);
typedef int redisGetKeysProc(struct redisCommand *cmd, robj **argv, int argc, getKeysResult *result);
struct redisCommand {
    char *name;
    redisCommandProc *proc;
    int arity;
    char *sflags;
    uint64_t flags;
    redisGetKeysProc *getkeys_proc;
    int firstkey;
    int lastkey;
    int keystep;
    long long microseconds, calls, rejected_calls, failed_calls;
    int id;
};

During command table initialization, flag strings are parsed into bitmask values (excerpt of

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,"admin")) {
        c->flags |= CMD_ADMIN|CMD_CATEGORY_ADMIN|CMD_CATEGORY_DANGEROUS;
    }
    // ... other flag handling ...
    else {
        if (flag[0] == '@' && (catflag = ACLGetCommandCategoryFlagByName(flag+1)) != 0) {
            c->flags |= catflag;
        } else {
            sdsfreesplitres(argv,argc);
            return C_ERR;
        }
    }
}

Example entries in the command table:

{"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},
{"set",setCommand,-3,"write use-memory @string",0,NULL,1,1,1,0,0,0}

These definitions determine how Redis parses, validates, and executes each command.

References

《Redis 设计与实现》黄健宏

databaseRedisnetworkingC programmingProtocolRESP
Ops Development Stories
Written by

Ops Development Stories

Maintained by a like‑minded team, covering both operations and development. Topics span Linux ops, DevOps toolchain, Kubernetes containerization, monitoring, log collection, network security, and Python or Go development. Team members: Qiao Ke, wanger, Dong Ge, Su Xin, Hua Zai, Zheng Ge, Teacher Xia.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.