简介面向需要在 Windows 平台集成 Redis 的 C/C 开发者这份资源提供了一套可直接链接的编译产物省去在非 Linux 环境下自行移植与编译的麻烦。压缩包共 20 个文件约 6.9MB以 lib 静态库为主辅以头文件、示例源码、可执行程序与动态库并附说明文档按 x86 与 x64 分目录组织Debug 与 Release 版本分别对应 /MTd 与 /MT 运行库配置便于按目标平台与构建模式选用。核心的 hiredis 客户端库简化了与 Redis 服务器的通信与回复解析Win32_Interop 则承担 Windows 下的系统调用兼容配合头文件即可在项目中完成连接、命令收发等基础开发。已有 1723 人学习下载适合希望快速验证 Windows 下 Redis 客户端接入、减少环境搭建成本的中级开发者参考。1. 在 Windows 上把 Redis 客户端跑起来一份开箱即用的编译产物很多做 C/C 服务端开发的人第一次在 Windows 上接 Redis都会卡在同一个地方Linux 下apt install一行命令搞定的事到了 Windows 就变成一场编译马拉松。Redis 官方源码本身对 Windows 支持有限hiredis 虽然能编但 Win32 相关的兼容层、socket 头文件、链接顺序随便一个环节不对就是一堆LNK2019未解析外部符号。这份资源解决的正是这个痛点——它把已经编译好的hiredis.lib、Win32_Interop.lib以及配套头文件打包在一起拿到手就能直接在自己的 Windows 工程里链接使用不用再折腾 MSVC 工具链和第三方依赖。适合谁适合那些在 Windows 上写 C/C 客户端、需要连 Redis 做缓存或消息队列但又不想把时间耗在环境搭建上的开发者。下面我按「这东西是什么 → 怎么接进工程 → 怎么改」的顺序拆一遍。2. 资源构成与工程接入从 lib 到第一个 SET 命令2.1 包里到底有什么每个文件干什么用先把压缩包摊开看。这类 Windows 编译产物通常包含三类东西静态库、头文件、以及可能的示例工程。核心是下面这几个文件类型作用hiredis.lib静态库hiredis 的核心实现负责 RESP 协议解析、连接管理、命令收发Win32_Interop.lib静态库Windows 平台兼容层封装 socket、线程、错误码等 POSIX 与 Win32 的差异hiredis.h头文件对外 API 声明redisContext、redisReply等结构体定义read.h/sds.h头文件回复读取与动态字符串的内部接口部分 API 需要win32fixes.h头文件Win32 类型修正比如ssize_t、strcasecmp的替代定义这里要理解一个关键点hiredis.lib不是独立可用的它内部调用了Win32_Interop.lib里的兼容函数。所以链接时两个库都得带上而且顺序有讲究——MSVC 的链接器是从左到右解析符号的hiredis.lib依赖Win32_Interop.lib那么hiredis.lib要写在前面。这个顺序搞反了报错信息会指向一堆你根本没调用过的函数很容易让人误以为是库本身有问题。头文件的包含顺序同样不能乱。win32fixes.h必须在hiredis.h之前引入因为它定义了 hiredis 头文件里会用到的平台相关类型。常见做法是在自己的预编译头或者统一的redis_wrapper.h里按这个顺序排好业务代码只包含 wrapper避免每个.cpp都去操心顺序。2.2 在 Visual Studio 工程里配置链接假设你有一个现成的 VS 工程现在要接入这份库。步骤不复杂但每一步都有坑。第一步把include目录加到「附加包含目录」。右键项目 → 属性 → C/C → 常规 → 附加包含目录填入头文件所在路径。注意用相对路径或者宏比如$(SolutionDir)third_party\redis\include别写死绝对路径否则换台机器就崩。第二步把lib目录加到「附加库目录」路径在 链接器 → 常规 → 附加库目录。第三步在 链接器 → 输入 → 附加依赖项 里填hiredis.lib Win32_Interop.lib ws2_32.libws2_32.lib是 Windows socket 库Win32_Interop底层要用到它漏了会报WSAStartup、socket之类的未解析符号。这个是最容易被忽略的一条因为报错信息不会直接告诉你「你缺 ws2_32」。第四步代码里初始化 Winsock。hiredis 在 Windows 下不会自动帮你做这件事必须在程序启动时调一次#include winsock2.h #include win32fixes.h #include hiredis.h int main(void) { WSADATA wsaData; // 初始化 Winsock 2.2hiredis 的 socket 操作依赖它 if (WSAStartup(MAKEWORD(2, 2), wsaData) ! 0) { return -1; } struct timeval timeout { 2, 0 }; // 连接超时 2 秒 redisContext *c redisConnectWithTimeout(127.0.0.1, 6379, timeout); if (c NULL || c-err) { if (c) { printf(Connection error: %s\n, c-errstr); redisFree(c); } WSACleanup(); return -1; } redisReply *reply (redisReply *)redisCommand(c, SET %s %s, foo, bar); if (reply NULL) { printf(Command failed\n); } else { printf(SET result: %s\n, reply-str); freeReplyObject(reply); } redisFree(c); WSACleanup(); return 0; }这段代码的逻辑很直白先起 Winsock再建连接发一条SET读回复最后释放。几个参数值得说清楚。redisConnectWithTimeout的第三个参数是struct timeval{2, 0}表示 2 秒 0 微秒超时后c-err会被置位errstr里是具体原因。redisCommand是可变参数版本内部会做格式化所以SET %s %s这种写法是安全的但要注意它返回的redisReply必须手动freeReplyObject否则每发一条命令漏一块内存跑久了就是内存缓慢上涨——这种问题排查起来相当玄学因为崩溃点往往不在漏内存的地方。reply-str只在reply-type REDIS_REPLY_STATUS或REDIS_REPLY_STRING时有效直接访问其他类型的str会拿到野指针。稳妥的写法是先判断type再取字段。这一点在官方文档里写得不算显眼但实际项目里翻车的不少。2.3 连接池与线程安全的边界单连接在单线程里用没问题但服务端场景基本都要多线程。hiredis 的redisContext本身不是线程安全的多个线程共用一个 context 发命令回复会串。常见做法是每个线程一个连接或者自己封一个连接池。连接池的核心逻辑不复杂维护一个redisContext的队列取的时候 pop用完 push 回去。但要注意连接失效的处理——Redis 服务端有timeout配置空闲太久的连接会被服务端断开池子里拿出来的连接可能已经死了。所以取连接后最好做一次PING探活失败就重建。这个探活有开销折中方案是记录连接的最近使用时间超过阈值才探活。// 简化的连接池取连接逻辑 redisContext *pool_acquire(conn_pool_t *pool) { redisContext *c NULL; pthread_mutex_lock(pool-lock); if (pool-count 0) { c pool-conns[--pool-count]; } pthread_mutex_unlock(pool-lock); if (c NULL) { c redisConnectWithTimeout(pool-host, pool-port, pool-timeout); if (c NULL || c-err) { if (c) redisFree(c); return NULL; } } else { // 复用前探活避免拿到被服务端断开的死连接 redisReply *r redisCommand(c, PING); if (r NULL || r-type ! REDIS_REPLY_STATUS) { if (r) freeReplyObject(r); redisFree(c); c redisConnectWithTimeout(pool-host, pool-port, pool-timeout); } else { freeReplyObject(r); } } return c; }这里的pthread_mutex在 Windows 下要换成CRITICAL_SECTION或者std::mutex取决于你的工程用的是 C 还是 C。如果用 Cstd::mutex加std::lock_guard更省心。池的容量、超时时间这些参数建议做成配置项不同业务对延迟和连接数的要求差别很大写死后期改起来麻烦。3. 核心 API 拆解命令收发、回复解析与错误处理3.1 redisCommand 与 redisReply 的配合方式hiredis 的 API 设计很克制核心就两个结构体redisContext代表连接redisReply代表一次回复。所有命令都通过redisCommand或其变体发出返回一个堆上的redisReply。redisReply的type字段决定了怎么读数据type 值含义有效字段REDIS_REPLY_STATUS状态回复如OKstrREDIS_REPLY_ERROR错误回复如-ERR ...strREDIS_REPLY_INTEGER整数回复如:1integerREDIS_REPLY_STRING批量字符串str,lenREDIS_REPLY_ARRAY数组回复elements,element[]REDIS_REPLY_NIL空回复无数组回复要递归处理element是一个redisReply**每个元素又可能是上面任意类型。写解析代码时建议封一个递归函数别在业务逻辑里一层层if嵌套否则代码很快就没法看了。void print_reply(redisReply *r, int depth) { if (r NULL) return; switch (r-type) { case REDIS_REPLY_STATUS: case REDIS_REPLY_STRING: printf(%*s%s\n, depth * 2, , r-str); break; case REDIS_REPLY_INTEGER: printf(%*s%lld\n, depth * 2, , r-integer); break; case REDIS_REPLY_ARRAY: for (size_t i 0; i r-elements; i) { print_reply(r-element[i], depth 1); } break; case REDIS_REPLY_NIL: printf(%*s(nil)\n, depth * 2, ); break; case REDIS_REPLY_ERROR: printf(%*s(error) %s\n, depth * 2, , r-str); break; } }r-integer是long long在 32 位 Windows 上别用%d打印会截断。r-len是字符串长度str不一定以\0结尾二进制安全所以涉及二进制数据时要用len而不是strlen。这两点在实际项目里都踩过尤其是存 protobuf 序列化结果的时候用strlen会丢数据。3.2 错误处理与重连策略redisContext的err字段是判断连接状态的关键。err非 0 表示连接已不可用此时errstr里有描述。注意命令执行返回NULL不一定代表连接断了也可能是内存分配失败但c-err被置位就一定得重建连接。一个健壮的封装通常长这样redisReply *safe_command(redisContext **pc, const char *fmt, ...) { va_list ap; va_start(ap, fmt); redisReply *r redisvCommand(*pc, fmt, ap); va_end(ap); if (r NULL) { // 连接可能已断尝试重连一次 redisFree(*pc); *pc redisConnectWithTimeout(127.0.0.1, 6379, (struct timeval){2, 0}); if (*pc NULL || (*pc)-err) { return NULL; } va_start(ap, fmt); r redisvCommand(*pc, fmt, ap); va_end(ap); } return r; }这里用redisvCommand是因为要把va_list透传redisCommand内部也是转成redisvCommand的。重连只做一次避免在服务端彻底挂掉时无限重试把线程卡死。重连后要不要重发原命令取决于命令是否幂等——SET可以重发INCR重发就会多算一次。这个判断得在业务层做库本身给不了答案。3.3 管道与批量命令的性能差异逐条发命令时每条都要等一个 RTT。本地 Redis 的 RTT 可能只有几十微秒但跨机器就是毫秒级批量写 1000 条就是 1 秒起步。hiredis 提供了管道pipeline模式把多条命令攒起来一次发出去再一次性读回复。// 管道模式批量写入减少网络往返 redisAppendCommand(c, SET key1 v1); redisAppendCommand(c, SET key2 v2); redisAppendCommand(c, SET key3 v3); for (int i 0; i 3; i) { redisReply *r NULL; if (redisGetReply(c, (void **)r) REDIS_OK) { // 逐条取回复顺序与发送顺序一致 freeReplyObject(r); } else { // 连接异常后续回复不可信需重建连接 break; } }redisAppendCommand只负责把命令写进发送缓冲区不读回复redisGetReply才真正读。管道模式下如果中途某条命令出错后续回复的对应关系会乱所以一旦redisGetReply返回非REDIS_OK就得放弃这批剩余回复重建连接。这个细节不注意会出现「回复错位」——你以为读到的是第二条的回复其实是第三条的数据就悄悄错了。4. 避坑与排查Windows 下链接和运行时的常见问题4.1 链接报 LNK2019 未解析符号现象编译通过链接阶段报一堆unresolved external symbol符号名里带_imp__或者指向WSAStartup、socket、close之类。原因要么漏了ws2_32.lib要么库的链接顺序不对要么 Debug/Release 混用——用 Debug 配置去链 Release 版的.lib运行库不匹配/MDvs/MT也会出这类错。解决先确认附加依赖项里有hiredis.lib、Win32_Interop.lib、ws2_32.lib三个且hiredis.lib在前。再检查项目属性 → C/C → 代码生成 → 运行库确保和库编译时用的运行库一致。如果库是/MT编的你的工程也得/MT混用会在链接期或运行期出问题。4.2 运行时崩溃在 redisConnect 内部现象程序启动后调redisConnect直接崩调用栈停在 socket 相关函数。原因没调WSAStartup。Windows 下 socket API 必须先初始化 Winsockhiredis 不会替你做。解决在main或程序初始化阶段调一次WSAStartup(MAKEWORD(2, 2), wsaData)退出时WSACleanup()。如果是 DLL 工程放在DllMain的DLL_PROCESS_ATTACH里注意别在DllMain里做太重的操作。4.3 中文或二进制数据读出来是乱码/截断现象SET进去的中文GET出来长度不对或者尾部丢字节。原因用了strlen而不是reply-len。hiredis 是二进制安全的str字段不保证以\0结尾尤其是数据里本身含\0的时候。解决所有读字符串的地方统一用reply-len作为长度memcpy到自己的缓冲区时也按len拷。存 protobuf、图片这类二进制数据时尤其要注意。4.4 多线程下回复串包现象两个线程同时用一个redisContextA 线程读到了 B 线程命令的回复。原因redisContext不是线程安全的内部有共享的读写缓冲区。解决每线程独立连接或者用连接池。如果非要用一个连接必须在外层加锁把「发命令 读回复」整体包住但这样并发度就没了不如直接上池。4.5 长时间运行后连接假死现象服务跑几个小时后某些连接发命令一直超时但 Redis 服务端看起来正常。原因服务端timeout配置会断开空闲连接客户端这边redisContext还以为是好的发命令时才发现写不出去。解决连接池取连接时做PING探活或者设置redisOptions里的connect_timeout和command_timeout如果用的版本支持让超时可控。另外可以在应用层加心跳定期对空闲连接发PING。5. 二次开发与改造把这份库包成顺手的客户端封装拿到这份编译产物直接裸调 hiredis API 在 demo 里没问题但真接到业务里迟早要封一层。我一般的做法是分三层底层连接管理、中层命令封装、上层业务接口。连接管理这层负责池化、探活、重连对外只暴露acquire/release两个函数。命令封装层把常用的GET、SET、HGETALL包成带类型检查的函数返回值统一转成std::string或std::vector把redisReply的生命周期关在这一层里业务代码碰不到裸指针。上层就按业务语义组织比如cache_get_user、cache_set_session。改造时有几个点值得提前想清楚。第一错误码体系。hiredis 的错误信息是字符串业务层判断起来不方便建议在封装层映射成枚举连接错误、超时、服务端错误、协议错误分开处理。第二超时配置。redisConnectWithTimeout只管连接超时命令执行超时在旧版 hiredis 里没有直接支持得靠redisSetTimeout或者自己用非阻塞模式加select这块要按实际版本确认。第三内存分配器。如果工程里统一用了自定义 allocatorhiredis 默认用malloc/free跨分配器释放会崩要么统一要么在封装层做拷贝隔离。验证改造是否到位我习惯跑三个用例单线程连续发一万条SET/GET看内存是否平稳多线程各发一千条看回复是否错位手动kill掉 Redis 再发命令看重连逻辑是否按预期工作。这三个过了基本能上测试环境。说个血泪教训早期我图省事在业务代码里直接redisCommand然后freeReplyObject结果有个分支忘了释放压测时内存曲线缓慢爬升查了两天才定位到。从那以后我每次封装客户端都强制让redisReply只在封装层内部出现业务代码拿到的永远是值类型。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?