43、C语言与Redis基础:hiredis库介绍、redisConnect、redisCommand、redisReply处理
说实话,很多做嵌入式或者C语言开发的朋友,一听到Redis就觉得那是Java、Python的专利。我早年也这么想,直到有个项目需要在嵌入式设备上做高速缓存,才真正把C和Redis拉到一起。今天咱们就聊聊hiredis这个库,它是C语言连接Redis的官方客户端,轻量、高效,说白了就是给C程序装上了一把访问Redis的钥匙。
hiredis库是什么?
hiredis是Redis官方为C语言提供的客户端库。它不像那些高级语言的客户端那样封装得花里胡哨,而是保持了C语言一贯的简洁和直接。你想想看,Redis本身就是用C写的,hiredis自然也是原汁原味的C风格。
我个人习惯把hiredis看作一个"翻译官"——它把C语言里的函数调用,翻译成Redis服务器能理解的RESP协议。你不需要手动拼接字符串、计算长度、处理粘包,这些脏活累活hiredis都替你干了。
- 同步和异步两种API,同步用起来最顺手
- 支持Redis的所有命令,没有阉割
- 内存管理完全交给调用者,灵活但需要小心
- 编译后体积很小,适合嵌入式场景
安装hiredis
在Linux上安装hiredis很简单。我建议直接从源码编译,这样能保证版本和特性都是最新的。
# 下载源码
git clone https://github.com/redis/hiredis.git
cd hiredis
# 编译安装
make
sudo make install
# 编译时链接
gcc -o myapp myapp.c -lhiredis
嗯,这里要注意:如果你是在交叉编译环境下做嵌入式开发,记得把库和头文件都放到正确的路径下。我曾经在ARM板子上折腾了一下午,最后发现是链接路径写错了。
redisConnect:建立连接
连接Redis是第一步。hiredis提供了redisConnect函数,用法非常直白:
#include <hiredis/hiredis.h>
redisContext *c = redisConnect("127.0.0.1", 6379);
if (c == NULL || c->err) {
if (c) {
printf("连接错误: %s\n", c->errstr);
redisFree(c);
} else {
printf("无法分配redisContext\n");
}
return -1;
}
printf("连接成功!\n");
redisConnect返回一个redisContext指针。这个结构体里保存了连接的状态、错误信息等。如果返回NULL,说明内存分配失败了;如果c->err非零,说明连接过程中出了网络问题。
redisConnect是阻塞的,如果Redis服务器挂了,你的程序也会卡住。用redisConnectWithTimeout可以指定超时:
struct timeval timeout = { 2, 0 }; // 2秒
redisContext *c = redisConnectWithTimeout("127.0.0.1", 6379, timeout);
redisCommand:发送命令
连接建立好了,接下来就是发命令。hiredis的redisCommand函数用起来有点像printf,支持格式化字符串:
redisReply *reply;
// 设置一个键值对
reply = redisCommand(c, "SET name %s", "zhangsan");
freeReplyObject(reply);
// 获取键值对
reply = redisCommand(c, "GET name");
if (reply->type == REDIS_REPLY_STRING) {
printf("name = %s\n", reply->str);
}
freeReplyObject(reply);
// 使用二进制安全的方式
reply = redisCommand(c, "SET key %b", binary_data, (size_t)data_len);
freeReplyObject(reply);
你看,%s对应字符串,%b对应二进制数据(需要指定长度)。hiredis内部会帮你把参数拼成完整的Redis命令,然后发送给服务器。
redisCommand返回的redisReply对象必须用freeReplyObject释放。忘记释放会导致内存泄漏。我在早期项目中就吃过这个亏,跑了一周的内存监控,发现内存只增不减,最后定位到是这里忘了释放。
redisReply处理:读懂Redis的回复
Redis的回复有几种类型,hiredis用redisReply结构体来表示。这个结构体里有个type字段,告诉你回复是什么类型:
| 类型常量 | 含义 | 说明 |
|---|---|---|
| REDIS_REPLY_STRING | 字符串 | 对应reply->str,长度在reply->len |
| REDIS_REPLY_ARRAY | 数组 | 对应reply->elements个元素,每个元素是reply->element[i] |
| REDIS_REPLY_INTEGER | 整数 | 对应reply->integer |
| REDIS_REPLY_NIL | 空值 | 表示键不存在或命令返回nil |
| REDIS_REPLY_STATUS | 状态 | 对应reply->str,比如"OK" |
| REDIS_REPLY_ERROR | 错误 | 对应reply->str,包含错误信息 |
处理数组类型时,需要递归遍历。比如用LRANGE获取列表所有元素:
reply = redisCommand(c, "LRANGE mylist 0 -1");
if (reply->type == REDIS_REPLY_ARRAY) {
for (size_t i = 0; i < reply->elements; i++) {
redisReply *child = reply->element[i];
if (child->type == REDIS_REPLY_STRING) {
printf("元素 %zu: %s\n", i, child->str);
}
}
}
freeReplyObject(reply);
为什么会这样设计?因为Redis的回复可以是嵌套的,比如HGETALL返回的数组里,键和值交替出现。hiredis把这种复杂性暴露给你,让你自己决定怎么解析。说白了,这就是C语言的风格——给你足够的控制权,也给你足够的责任。
完整的示例:从连接到查询
下面是一个完整的例子,把今天讲的东西串起来:
#include <stdio.h>
#include <stdlib.h>
#include <hiredis/hiredis.h>
int main() {
// 1. 连接Redis
redisContext *c = redisConnect("127.0.0.1", 6379);
if (c == NULL || c->err) {
printf("连接失败: %s\n", c ? c->errstr : "分配失败");
return 1;
}
printf("连接成功\n");
// 2. 执行命令
redisReply *reply;
// SET
reply = redisCommand(c, "SET user:1:name %s", "Alice");
if (reply->type == REDIS_REPLY_STATUS) {
printf("SET结果: %s\n", reply->str);
}
freeReplyObject(reply);
// GET
reply = redisCommand(c, "GET user:1:name");
if (reply->type == REDIS_REPLY_STRING) {
printf("GET结果: %s\n", reply->str);
} else if (reply->type == REDIS_REPLY_NIL) {
printf("键不存在\n");
}
freeReplyObject(reply);
// 3. 清理
redisFree(c);
return 0;
}
编译运行:
gcc -o redis_demo redis_demo.c -lhiredis
./redis_demo
避坑指南
我这些年用hiredis踩过不少坑,挑几个典型的说说:
- 内存泄漏:每个
redisCommand返回的redisReply都要freeReplyObject。我建议在函数出口统一释放,或者用goto cleanup的模式。 - 连接复用:不要每次命令都新建连接。Redis连接是有开销的,复用同一个
redisContext就好。hiredis内部是线程不安全的,多线程场景下每个线程用各自的连接。 - 错误处理:每次调用
redisCommand后都要检查返回值是否为NULL。如果网络断了,返回NULL,这时候需要重新连接。 - 二进制安全:Redis的字符串是二进制安全的,可以存\0字节。用
%b格式化符,并传入长度,不要用strlen。
redisAppendCommand和redisGetReply组合,实现管道(pipelining)效果,能大幅提升吞吐量。不过要注意,管道模式下回复的顺序和命令顺序一一对应。
知识体系总览
下面这张图把hiredis的核心流程画出来了,从连接到命令执行,再到回复处理,一目了然:
这张图把整个流程串起来了:先连接,再发命令,然后根据回复类型做不同处理,最后记得释放资源。每一步都环环相扣,漏掉一个环节程序就可能出问题。
好了,关于hiredis的基础用法就聊到这儿。说白了就是三板斧:连接、命令、处理回复。你上手写几个例子,很快就能找到感觉。记住,C语言操作Redis没有那么多花架子,扎实地把每一步做好,程序自然就稳了。