前言
你有没有想过:Redis客户端(如redis-cli、Jedis、redis-py)是怎么和Redis服务器通信的?
Redis使用了一个简单高效的文本协议------RESP(REdis Serialization Protocol)。我们可以用任何语言实现这个协议,从而与Redis交互。
今天我们用C语言从零实现一个Redis客户端:
· 完整的RESP协议编解码
· TCP连接管理
· 常用命令支持(SET、GET、DEL等)
· 连接池基础
· 命令行交互模式
一、RESP协议核心原理
- 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 数组/命令参数
- 请求编码示例
```
命令: 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
```
二、完整代码实现
- 基础结构
```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;
```
- 网络连接
```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");
}
}
```
- 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;
}
```
- 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;
}
```
- 客户端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;
}
```
- 连接池基础
```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);
}
```
- 命令行交互模式
```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协议的理解~