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非零,说明连接过程中出了网络问题。

我的经验: 连接Redis时,最好设置一个超时时间。默认情况下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
小技巧: 如果你需要批量执行命令,可以用redisAppendCommandredisGetReply组合,实现管道(pipelining)效果,能大幅提升吞吐量。不过要注意,管道模式下回复的顺序和命令顺序一一对应。

知识体系总览

下面这张图把hiredis的核心流程画出来了,从连接到命令执行,再到回复处理,一目了然:

hiredis 核心流程 redisConnect 建立TCP连接 成功 redisCommand 发送命令 + 等待回复 redisReply 解析回复类型 根据 type 字段处理 REDIS_REPLY_STRING reply->str REDIS_REPLY_ARRAY 遍历 element[] REDIS_REPLY_INTEGER reply->integer REDIS_REPLY_NIL 键不存在 freeReplyObject 释放内存,防止泄漏 redisFree(c)

这张图把整个流程串起来了:先连接,再发命令,然后根据回复类型做不同处理,最后记得释放资源。每一步都环环相扣,漏掉一个环节程序就可能出问题。

好了,关于hiredis的基础用法就聊到这儿。说白了就是三板斧:连接、命令、处理回复。你上手写几个例子,很快就能找到感觉。记住,C语言操作Redis没有那么多花架子,扎实地把每一步做好,程序自然就稳了。

公众号:蓝海资料掘金营,微信deep3321