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.
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 -AnnTesting with the command
set msg100 1on 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-cliclient 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
aError 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 14Shared 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
redisCommandstructure, 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 设计与实现》黄健宏
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.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.