从零实现一个Redis客户端:RESP协议与网络编程

前言

你有没有想过:Redis客户端(如redis-cli、Jedis、redis-py)是怎么和Redis服务器通信的?

Redis使用了一个简单高效的文本协议------RESP(REdis Serialization Protocol)。我们可以用任何语言实现这个协议,从而与Redis交互。

今天我们用C语言从零实现一个Redis客户端:

· 完整的RESP协议编解码

· TCP连接管理

· 常用命令支持(SET、GET、DEL等)

· 连接池基础

· 命令行交互模式


一、RESP协议核心原理

  1. RESP的数据类型

符号 类型 格式 示例

  • 简单字符串 +OK\r\n 状态回复
  • 错误 -ERR unknown command\r\n 错误回复

: 整数 :1000\r\n 整数回复

批量字符串 6\r\nfoobar\r\n 字符串/Bulk

* 数组 *2\r\n3\\r\\nfoo\\r\\n3\r\nbar\r\n 数组/命令参数

  1. 请求编码示例

```

命令: SET mykey hello

RESP编码:

*3\r\n (数组长度3)

$3\r\n (第一个参数长度3)

SET\r\n (第一个参数内容)

$5\r\n (第二个参数长度5)

mykey\r\n (第二个参数内容)

$5\r\n (第三个参数长度5)

hello\r\n (第三个参数内容)

最终: *3\r\n3\\r\\nSET\\r\\n5\r\nmykey\r\n$5\r\nhello\r\n

```


二、完整代码实现

  1. 基础结构

```c

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include <unistd.h>

#include <sys/socket.h>

#include <netinet/in.h>

#include <netdb.h>

#include <arpa/inet.h>

#include <errno.h>

#include <pthread.h>

#include <time.h>

// RESP数据类型

typedef enum {

RESP_STRING, // 简单字符串 (+)

RESP_ERROR, // 错误 (-)

RESP_INTEGER, // 整数 (:)

RESP_BULK, // 批量字符串 ($)

RESP_ARRAY, // 数组 (*)

RESP_NIL, // 空 (Redis的$-1)

RESP_OK // 解析成功标识

} resp_type_t;

// RESP值结构

typedef struct resp_value {

resp_type_t type;

union {

char *str; // 简单字符串/错误/批量字符串

long long integer; // 整数

struct {

struct resp_value **elements;

int count;

} array;

};

} resp_value_t;

// Redis客户端结构

typedef struct redis_client {

int sock_fd;

char *host;

int port;

int connect_timeout_ms;

int read_timeout_ms;

pthread_mutex_t mutex;

} redis_client_t;

```

  1. 网络连接

```c

// 创建socket并连接

redis_client_t *redis_connect(const char *host, int port) {

redis_client_t *client = malloc(sizeof(redis_client_t));

if (!client) return NULL;

client->host = strdup(host);

client->port = port;

client->connect_timeout_ms = 3000;

client->read_timeout_ms = 3000;

pthread_mutex_init(&client->mutex, NULL);

// 解析主机

struct hostent *server = gethostbyname(host);

if (!server) {

fprintf(stderr, "无法解析主机: %s\n", host);

free(client->host);

free(client);

return NULL;

}

// 创建socket

client->sock_fd = socket(AF_INET, SOCK_STREAM, 0);

if (client->sock_fd < 0) {

perror("socket");

free(client->host);

free(client);

return NULL;

}

// 设置连接超时

struct timeval tv;

tv.tv_sec = client->connect_timeout_ms / 1000;

tv.tv_usec = (client->connect_timeout_ms % 1000) * 1000;

setsockopt(client->sock_fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));

setsockopt(client->sock_fd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv));

// 连接

struct sockaddr_in addr;

addr.sin_family = AF_INET;

addr.sin_port = htons(port);

memcpy(&addr.sin_addr.s_addr, server->h_addr, server->h_length);

if (connect(client->sock_fd, (struct sockaddr*)&addr, sizeof(addr)) < 0) {

perror("connect");

close(client->sock_fd);

free(client->host);

free(client);

return NULL;

}

printf("已连接到 Redis %s:%d\n", host, port);

return client;

}

void redis_disconnect(redis_client_t *client) {

if (client) {

close(client->sock_fd);

free(client->host);

pthread_mutex_destroy(&client->mutex);

free(client);

printf("已断开 Redis 连接\n");

}

}

```

  1. RESP编码器(发送命令)

```c

// 编码字符串为RESP批量字符串格式

char *resp_encode_bulk(const char *str, int *len) {

if (str == NULL) {

*len = 5; // "$-1\r\n"

char *out = malloc(6);

strcpy(out, "$-1\r\n");

return out;

}

int str_len = strlen(str);

*len = snprintf(NULL, 0, "$%d\r\n%s\r\n", str_len, str) + 1;

char *out = malloc(*len);

snprintf(out, *len, "$%d\r\n%s\r\n", str_len, str);

return out;

}

// 编码命令为RESP数组(用于发送)

char *resp_encode_command(char **argv, int argc, int *out_len) {

// 计算总长度

int total_len = 0;

char **bulks = malloc(sizeof(char*) * argc);

int *bulk_lens = malloc(sizeof(int) * argc);

for (int i = 0; i < argc; i++) {

bulksi = resp_encode_bulk(argvi, &bulk_lensi);

total_len += bulk_lensi;

}

// 添加数组头

int header_len = snprintf(NULL, 0, "*%d\r\n", argc) + 1;

char *result = malloc(header_len + total_len + 1);

snprintf(result, header_len, "*%d\r\n", argc);

int pos = header_len - 1;

for (int i = 0; i < argc; i++) {

memcpy(result + pos, bulksi, bulk_lensi);

pos += bulk_lensi;

free(bulksi);

}

resultpos = '\0';

*out_len = pos;

free(bulks);

free(bulk_lens);

return result;

}

// 发送命令

int redis_send_command(redis_client_t *client, char **argv, int argc) {

pthread_mutex_lock(&client->mutex);

int len;

char *data = resp_encode_command(argv, argc, &len);

int sent = send(client->sock_fd, data, len, 0);

free(data);

if (sent != len) {

pthread_mutex_unlock(&client->mutex);

return -1;

}

pthread_mutex_unlock(&client->mutex);

return 0;

}

```

  1. RESP解码器(接收回复)

```c

// 从socket读取一行(以\r\n结尾)

char *read_line(int sock_fd) {

char *buf = malloc(4096);

int pos = 0;

char c;

while (recv(sock_fd, &c, 1, 0) > 0) {

if (pos > 0 && bufpos-1 == '\r' && c == '\n') {

bufpos-1 = '\0'; // 去掉\r\n

return buf;

}

bufpos++ = c;

if (pos >= 4095) break;

}

free(buf);

return NULL;

}

// 解析RESP响应

resp_value_t *resp_parse(redis_client_t *client) {

char *line = read_line(client->sock_fd);

if (!line) return NULL;

resp_value_t *val = malloc(sizeof(resp_value_t));

char type = line0;

switch (type) {

case '+': // 简单字符串

val->type = RESP_STRING;

val->str = strdup(line + 1);

break;

case '-': // 错误

val->type = RESP_ERROR;

val->str = strdup(line + 1);

break;

case ':': // 整数

val->type = RESP_INTEGER;

val->integer = atoll(line + 1);

break;

case '$': { // 批量字符串

int len = atoi(line + 1);

if (len == -1) {

val->type = RESP_NIL;

val->str = NULL;

// 跳过剩下的\r\n

read_line(client->sock_fd);

} else {

val->type = RESP_BULK;

val->str = malloc(len + 1);

int bytes_read = 0;

while (bytes_read < len) {

int n = recv(client->sock_fd, val->str + bytes_read,

len - bytes_read, 0);

if (n <= 0) break;

bytes_read += n;

}

val->strlen = '\0';

read_line(client->sock_fd); // 跳过结尾的\r\n

}

break;

}

case '*': { // 数组

int count = atoi(line + 1);

if (count == -1) {

val->type = RESP_NIL;

val->array.elements = NULL;

val->array.count = 0;

} else {

val->type = RESP_ARRAY;

val->array.count = count;

val->array.elements = malloc(sizeof(resp_value_t*) * count);

for (int i = 0; i < count; i++) {

val->array.elementsi = resp_parse(client);

}

}

break;

}

default:

free(val);

free(line);

return NULL;

}

free(line);

return val;

}

```

  1. 客户端API(常用命令)

```c

// SET命令

int redis_set(redis_client_t *client, const char *key, const char *value) {

char *argv\[\] = {"SET", (char*)key, (char*)value};

if (redis_send_command(client, argv, 3) < 0) return -1;

resp_value_t *resp = resp_parse(client);

if (!resp) return -1;

int ok = (resp->type == RESP_STRING && strcmp(resp->str, "OK") == 0);

free(resp->str);

free(resp);

return ok ? 0 : -1;

}

// GET命令

char *redis_get(redis_client_t *client, const char *key) {

char *argv\[\] = {"GET", (char*)key};

if (redis_send_command(client, argv, 2) < 0) return NULL;

resp_value_t *resp = resp_parse(client);

if (!resp) return NULL;

char *value = NULL;

if (resp->type == RESP_BULK || resp->type == RESP_STRING) {

value = strdup(resp->str);

}

if (resp->str) free(resp->str);

free(resp);

return value;

}

// DEL命令(返回删除的数量)

int redis_del(redis_client_t *client, const char *key) {

char *argv\[\] = {"DEL", (char*)key};

if (redis_send_command(client, argv, 2) < 0) return -1;

resp_value_t *resp = resp_parse(client);

if (!resp || resp->type != RESP_INTEGER) {

if (resp) free(resp);

return -1;

}

int count = resp->integer;

free(resp);

return count;

}

// EXISTS命令

int redis_exists(redis_client_t *client, const char *key) {

char *argv\[\] = {"EXISTS", (char*)key};

if (redis_send_command(client, argv, 2) < 0) return -1;

resp_value_t *resp = resp_parse(client);

if (!resp || resp->type != RESP_INTEGER) {

if (resp) free(resp);

return -1;

}

int exists = resp->integer;

free(resp);

return exists;

}

// KEYS命令

char **redis_keys(redis_client_t *client, const char *pattern, int *count) {

char *argv\[\] = {"KEYS", (char*)pattern};

if (redis_send_command(client, argv, 2) < 0) return NULL;

resp_value_t *resp = resp_parse(client);

if (!resp || resp->type != RESP_ARRAY) {

if (resp) free(resp);

return NULL;

}

*count = resp->array.count;

char **keys = malloc(sizeof(char*) * (*count));

for (int i = 0; i < *count; i++) {

if (resp->array.elementsi->type == RESP_BULK) {

keysi = strdup(resp->array.elementsi->str);

} else {

keysi = NULL;

}

}

// 释放RESP结构

for (int i = 0; i < *count; i++) {

free(resp->array.elementsi->str);

free(resp->array.elementsi);

}

free(resp->array.elements);

free(resp);

return keys;

}

```

  1. 连接池基础

```c

// 连接池节点

typedef struct conn_pool_node {

redis_client_t *client;

int in_use;

time_t last_used;

struct conn_pool_node *next;

} conn_pool_node_t;

// 连接池

typedef struct {

conn_pool_node_t *conns;

int max_connections;

int current_connections;

pthread_mutex_t mutex;

char *host;

int port;

} redis_pool_t;

redis_pool_t *redis_pool_create(const char *host, int port, int max_conn) {

redis_pool_t *pool = malloc(sizeof(redis_pool_t));

pool->max_connections = max_conn;

pool->current_connections = 0;

pool->conns = NULL;

pool->host = strdup(host);

pool->port = port;

pthread_mutex_init(&pool->mutex, NULL);

return pool;

}

redis_client_t *redis_pool_get(redis_pool_t *pool) {

pthread_mutex_lock(&pool->mutex);

// 查找空闲连接

conn_pool_node_t *node = pool->conns;

while (node) {

if (!node->in_use) {

node->in_use = 1;

node->last_used = time(NULL);

pthread_mutex_unlock(&pool->mutex);

return node->client;

}

node = node->next;

}

// 创建新连接

if (pool->current_connections < pool->max_connections) {

redis_client_t *client = redis_connect(pool->host, pool->port);

if (client) {

conn_pool_node_t *new_node = malloc(sizeof(conn_pool_node_t));

new_node->client = client;

new_node->in_use = 1;

new_node->last_used = time(NULL);

new_node->next = pool->conns;

pool->conns = new_node;

pool->current_connections++;

pthread_mutex_unlock(&pool->mutex);

return client;

}

}

pthread_mutex_unlock(&pool->mutex);

return NULL;

}

void redis_pool_put(redis_pool_t *pool, redis_client_t *client) {

pthread_mutex_lock(&pool->mutex);

conn_pool_node_t *node = pool->conns;

while (node) {

if (node->client == client) {

node->in_use = 0;

node->last_used = time(NULL);

break;

}

node = node->next;

}

pthread_mutex_unlock(&pool->mutex);

}

```

  1. 命令行交互模式

```c

void interactive_mode(redis_client_t *client) {

printf("\n进入交互模式(输入命令,输入quit退出)\n");

char line4096;

while (1) {

printf("redis> ");

fflush(stdout);

if (!fgets(line, sizeof(line), stdin)) break;

// 去掉末尾换行

linestrcspn(line, "\\n") = 0;

if (strcmp(line, "quit") == 0 || strcmp(line, "exit") == 0) {

break;

}

// 简单解析命令(空格分割)

char *argv64;

int argc = 0;

char *token = strtok(line, " ");

while (token && argc < 64) {

argvargc++ = token;

token = strtok(NULL, " ");

}

if (argc == 0) continue;

// 发送命令

if (redis_send_command(client, argv, argc) == 0) {

resp_value_t *resp = resp_parse(client);

if (resp) {

// 打印响应

switch (resp->type) {

case RESP_STRING:

case RESP_BULK:

printf("%s\n", resp->str ? resp->str : "(nil)");

break;

case RESP_INTEGER:

printf("(integer) %lld\n", resp->integer);

break;

case RESP_ERROR:

printf("(error) %s\n", resp->str);

break;

case RESP_ARRAY:

printf("(array) %d items:\n", resp->array.count);

for (int i = 0; i < resp->array.count; i++) {

printf(" %d) %s\n", i+1,

resp->array.elementsi->str ?

resp->array.elementsi->str : "(nil)");

}

break;

default:

printf("(unknown)\n");

}

// 清理

if (resp->type == RESP_ARRAY) {

for (int i = 0; i < resp->array.count; i++) {

if (resp->array.elementsi->str) free(resp->array.elementsi->str);

free(resp->array.elementsi);

}

free(resp->array.elements);

} else if (resp->str) {

free(resp->str);

}

free(resp);

} else {

printf("(error) 解析响应失败\n");

}

} else {

printf("(error) 发送命令失败\n");

}

}

}

```


三、测试代码

```c

int main() {

printf("=== Redis客户端测试 ===\n");

redis_client_t *client = redis_connect("127.0.0.1", 6379);

if (!client) {

printf("连接失败,请确保Redis服务器已启动\n");

return 1;

}

// 测试SET/GET

printf("\n--- SET/GET测试 ---\n");

redis_set(client, "name", "RedisClient");

char *name = redis_get(client, "name");

printf("GET name: %s\n", name ? name : "(nil)");

free(name);

// 测试整数

redis_set(client, "count", "100");

char *count = redis_get(client, "count");

printf("GET count: %s\n", count);

free(count);

// 测试EXISTS

printf("\n--- EXISTS测试 ---\n");

printf("EXISTS name: %d\n", redis_exists(client, "name"));

printf("EXISTS nonexist: %d\n", redis_exists(client, "nonexist"));

// 测试DEL

printf("\n--- DEL测试 ---\n");

redis_del(client, "name");

name = redis_get(client, "name");

printf("DEL之后 GET name: %s\n", name ? name : "(nil)");

free(name);

// 测试KEYS

printf("\n--- KEYS测试 ---\n");

int count_keys;

char **keys = redis_keys(client, "*", &count_keys);

printf("所有键 (%d个):\n", count_keys);

for (int i = 0; i < count_keys; i++) {

printf(" %s\n", keysi);

free(keysi);

}

free(keys);

// 命令行交互模式

// interactive_mode(client);

redis_disconnect(client);

return 0;

}

```


四、总结

通过这篇文章,你学会了:

· RESP协议的完整格式(数据类型、编码规则)

· 命令编码和响应解码

· 常用Redis命令的客户端实现

· 连接池的基础设计

· 命令行交互模式

实现Redis客户端是学习网络协议编程的绝佳练习。掌握它,你就能理解任何基于TCP的应用层协议。

下一篇预告:《从零实现一个HTTP服务器:静态文件与CGI》


评论区分享一下你对Redis协议的理解~

相关推荐
玖玥拾1 小时前
C/C++ 基础笔记(六)
c语言·c++·内存管理
小小码农Come on1 小时前
Qt::WA_StyledBackground属性的作用
开发语言·qt
许彰午1 小时前
04_Java数组操作全解
java·开发语言·python
码不停蹄的玄黓1 小时前
Java 线程池 execute() 和 submit() 对比
java·开发语言
秋田君1 小时前
2026 前端新出路:掌握 C++ 核心语法,无缝衔接 QT 桌面开发
前端·c++·qt
handler011 小时前
【C++11 】Lambda 表达式、std::function 与 std::bind 解析
c++·c·c++11·bind·解耦·function·lamda
方也_arkling2 小时前
【Java-Day19】集合1(Collect单列集合)
java·开发语言
Xin_ye100862 小时前
C# 零基础到精通教程 - WPF 专题三:高级控件与自定义控件
开发语言·c#·wpf
SoftLipaRZC2 小时前
C语言自定义类型:结构体完全指南
c语言·开发语言