简介:这份资源是专为Windows平台准备的Redis编译产物,面向需要在C/C++项目中集成Redis的开发者,解决Redis原生面向Linux、在Windows下编译困难的问题。压缩包共20个文件,以lib静态库、exe可执行文件、h头文件为主,另含c源码、dll与说明文档,整体约6.9MB,并区分x86与x64两套目录,分别适配32位和64位系统。核心的hiredis.lib提供高效的Redis客户端通信与回复解析能力,Win32_Interop.lib则承担Windows下的系统调用兼容工作,头文件可直接用于工程引用。资源还涉及Debug使用/MTD、Release使用/MT的运行库配置差异,便于开发者理解链接选项与调试、发布版本的取舍。目前已有1723人学习下载,适合希望快速搭建Windows下Redis开发环境、研究跨平台编译与链接细节的中高级开发者参考。
1. 为什么 Windows 上编译 Redis 客户端库总翻车:一份开箱即用的 hiredis 静态库
在 Windows 上做 C/C++ 项目,只要涉及 Redis,十有八九绕不开 hiredis。Linux 下apt install libhiredis-dev一行搞定的事,到了 Windows 就变成一场拉锯战:MSVC 版本对不上、CMake 生成器选错、Win32_Interop 的符号冲突、链接时一堆LNK2019未解析外部符号。很多人最后干脆放弃,改用别的语言绕过去。这份资源就是冲着这个痛点来的——它是一套在 Windows 下已经编译好的 Redis 客户端库,包含hiredis.lib、Win32_Interop.lib以及配套头文件,拿到就能直接在自己的工程里引用,省掉从源码编译的全过程。适合两类人:一是需要在 Windows 桌面端或服务端程序里直连 Redis 的 C/C++ 开发者,二是想研究 hiredis 在 Windows 下如何适配、想自己动手改编译参数的中级玩家。下面我按「这套库是什么 → 怎么接进工程 → 参数怎么配 → 坑在哪」的顺序拆一遍。
2. hiredis 与 Win32_Interop:两个库到底谁在干活
2.1 hiredis 负责协议,Win32_Interop 负责把 POSIX 抹平
hiredis 本身是 Redis 官方推荐的 C 客户端,核心职责是把 Redis 的 RESP 协议封装成redisContext、redisCommand这类接口。它的源码是照着 POSIX 写的,大量用到read、write、poll、socket、unistd.h这些 Linux 下才有的东西。直接拿 MSVC 编译,第一步就会在头文件包含处报错。
Win32_Interop 就是微软 Redis 分支里抽出来的适配层,它把 POSIX 那套调用映射到 Windows 的 Winsock 和 Win32 API 上。常见做法是:hiredis 的源码里通过宏判断,在 Windows 平台下包含 Win32_Interop 提供的替代头文件,链接时再挂上Win32_Interop.lib。所以这两个库是绑定关系,缺一个都跑不起来。
理解这层关系很重要,因为它决定了你链接时的顺序和依赖。hiredis.lib里会引用Win32_Interop.lib导出的符号,如果你只链了前者,链接器会告诉你找不到redisWin32_...之类的函数。这也是很多人第一次接这套库时最常撞的墙。
2.2 静态库和动态库的选型差别
这份资源给的是.lib,需要先确认它是静态库还是动态库的导入库。判断方法很直接:看有没有配套的.dll。如果只有.lib和头文件,那基本是静态库,编译出来的 exe 不依赖额外 DLL,分发省心;如果有.dll,那.lib只是导入库,运行时必须把 DLL 放到 exe 同目录或系统路径下。
我一般倾向于静态库,原因是 Windows 上 DLL 的搜索路径玄学太多,尤其是把程序拷到别的机器上跑的时候,少一个 DLL 就是一次翻车。静态库把代码直接塞进 exe,体积大一点,但部署干净。这份资源如果确认是静态库,那对独立分发的工具类程序非常友好。
2.3 头文件的组织方式决定了 include 路径怎么写
一套规范的库,头文件通常分两层:对外暴露的公共头(比如hiredis.h、read.h、sds.h)和内部适配头(Win32_Interop 那一堆)。接入工程时,你只需要把公共头的目录加进 include path,内部头由公共头自己#include进去。
但现实里经常遇到头文件平铺在一个目录的情况,这时候就得手动把整个目录加进 include path,否则编译到hiredis.h内部包含win32fixes.h时会找不到。建议先扫一眼目录结构,确认hiredis.h里 include 的那些文件名,能不能在同一个目录或子目录里找到。找不到就是路径没配对。
3. 把库接进 Visual Studio 工程:四步走完
3.1 第一步:确认平台工具集和运行库匹配
这是最容易被忽略、又最致命的一步。.lib是用某个特定版本的 MSVC 工具集编译出来的,如果你的工程用的是另一个大版本(比如库是 v142 编的,工程是 v143),链接时可能报LNK2038运行库不匹配,或者更隐蔽的堆损坏。
先看库的编译信息。如果资源里带了说明或目录名有暗示(比如vc142、x64),照着设。没有的话,用dumpbin /headers hiredis.lib看机器类型,确认是 x86 还是 x64。然后在 VS 里把工程平台设成一致,项目属性 → 常规 → 平台工具集尽量选和库一致的版本。运行库设置(/MT还是/MD)也要对齐,静态库用/MT编的,你工程用/MD,混起来就是一堆重复定义或堆问题。
3.2 第二步:配置 include 目录和库目录
打开项目属性 → C/C++ → 常规 → 附加包含目录,把存放头文件的目录加进去。如果头文件分公共和内部两个目录,两个都加,省得后面找不到。
然后是项目属性 → 链接器 → 常规 → 附加库目录,把hiredis.lib和Win32_Interop.lib所在的目录加进去。注意这里填的是目录,不是文件名。
# 用 dumpbin 确认 lib 的架构和依赖,避免链接期才发现不匹配 dumpbin /headers hiredis.lib | findstr "machine" dumpbin /headers Win32_Interop.lib | findstr "machine" # 查看 lib 导出的符号,确认它引用了哪些外部函数 dumpbin /symbols hiredis.lib | findstr "External"上面第一条命令看机器类型,输出x64或x86,和你工程平台对齐。第二条看符号表,如果发现大量redisWin32开头的未定义符号,说明它依赖 Win32_Interop,链接时必须带上后者。这一步做完,心里就有底了。
3.3 第三步:链接器里挂上两个 lib
项目属性 → 链接器 → 输入 → 附加依赖项,把hiredis.lib和Win32_Interop.lib都写进去。顺序上,把hiredis.lib放前面,Win32_Interop.lib放后面,因为前者依赖后者,链接器从左到右解析,顺序反了可能报未解析符号。
如果还报ws2_32.lib相关的符号找不到,把ws2_32.lib也加上。Win32_Interop 底层用的是 Winsock,需要这个系统库。同理可能还需要iphlpapi.lib,看具体报错补。
// 一个最小可运行的连接测试,验证库是否真的接对了 #include <stdio.h> #include <hiredis.h> int main() { // 连接本机 6379 端口的 Redis,超时 1.5 秒 struct timeval timeout = { 1, 500000 }; redisContext *c = redisConnectWithTimeout("127.0.0.1", 6379, timeout); if (c == NULL || c->err) { if (c) { printf("连接失败: %s\n", c->errstr); redisFree(c); } else { printf("分配 context 失败\n"); } return 1; } // 发一条 PING,正常返回 PONG redisReply *reply = (redisReply *)redisCommand(c, "PING"); printf("PING 返回: %s\n", reply->str); freeReplyObject(reply); redisFree(c); return 0; }这段代码是验证库能不能用的最小闭环。redisConnectWithTimeout的第三个参数是超时结构体,tv_sec是秒,tv_usec是微秒,这里设 1.5 秒。连接成功后c->err为 0,否则c->errstr里有原因。redisCommand是同步接口,发出去会阻塞等回复,返回的redisReply必须用freeReplyObject释放,redisContext必须用redisFree释放,漏一个就是内存泄漏。跑通这段,说明库、头文件、链接全部正确。
3.4 第四步:处理编译期的宏定义冲突
有些工程在包含hiredis.h之前已经包含了winsock2.h或windows.h,顺序不对会报重复定义。常见做法是把winsock2.h放在windows.h之前,或者定义WIN32_LEAN_AND_MEAN减少windows.h的包含范围。
如果报redisContext未定义,检查是不是头文件路径没配对,或者宏HIREDIS_WIN32之类的没定义。有些编译好的库要求调用方定义特定宏才会暴露 Windows 适配的接口,这个要看资源里有没有附带说明文件。没有说明的话,试着在工程预处理器里加上WIN32和_WIN32,这两个 MSVC 默认就有,一般不用手动加。
4. 参数配置与常见调用模式:别只会 PING
4.1 连接参数:超时、认证、数据库选择
redisConnectWithTimeout只解决了地址和超时。如果 Redis 设了密码,连接后要立刻发AUTH;如果要选库,发SELECT。这两步不做,后续命令会返回NOAUTH或操作到错误的库。
// 连接后先认证再选库,顺序不能反 redisReply *authReply = (redisReply *)redisCommand(c, "AUTH %s", "yourpassword"); if (authReply->type == REDIS_REPLY_ERROR) { printf("认证失败: %s\n", authReply->str); } freeReplyObject(authReply); redisReply *selReply = (redisReply *)redisCommand(c, "SELECT %d", 2); freeReplyObject(selReply);AUTH的参数用%s占位,hiredis 会自动做转义。SELECT用%d传库号。注意每次redisCommand返回的 reply 都要释放,认证和选库的返回值虽然一般不看,但内存还是要还。
4.2 命令拼接:格式化占位符和二进制安全
hiredis 的redisCommand支持 printf 风格的格式化,%s、%d、%b都能用。%b是二进制安全的关键,它需要两个参数:指针和长度。存图片、序列化数据这类含\0的内容,必须用%b,用%s会在第一个\0处截断。
// 存二进制数据必须用 %b,传指针和长度两个参数 char binaryData[] = {0x01, 0x00, 0x02, 0x03}; size_t dataLen = sizeof(binaryData); redisReply *setReply = (redisReply *)redisCommand(c, "SET %b %b", "mykey", (size_t)5, binaryData, dataLen); freeReplyObject(setReply);这里%b对应 key 和 value 各一次,每次都要跟一个长度参数。key 虽然是字符串,但用%b更保险,避免 key 里意外含特殊字符。返回值类型是REDIS_REPLY_STATUS,str里是OK。
4.3 管道模式:批量命令减少往返
逐条发命令,每条都要等一个网络往返,批量写入时性能很差。hiredis 提供redisAppendCommand加redisGetReply的管道模式,把多条命令一次性发出去,再依次收回复。
// 管道模式:先攒命令,再统一收回复 for (int i = 0; i < 1000; i++) { redisAppendCommand(c, "SET key:%d value:%d", i, i); } for (int i = 0; i < 1000; i++) { redisReply *reply; if (redisGetReply(c, (void **)&reply) == REDIS_OK) { freeReplyObject(reply); } }redisAppendCommand只把命令写进输出缓冲区,不阻塞。redisGetReply才真正读回复。注意管道模式下如果中间某条命令出错,回复里会带错误信息,但连接不会断,后续命令照常执行。要判断整体成功与否,得逐条检查 reply 的type。
5. 避坑与排查:五个血泪教训
5.1 现象:链接报 LNK2019 找不到 redisWin32 符号
原因:只链了hiredis.lib,没链Win32_Interop.lib,或者链接顺序反了。hiredis 在 Windows 下把 POSIX 调用转发给了 Win32_Interop,符号依赖是硬性的。
解决:在附加依赖项里把Win32_Interop.lib加在hiredis.lib后面,同时确认两个 lib 的架构一致。还不行就用dumpbin /symbols看看到底缺哪个符号,对症补库。
5.2 现象:程序运行到 redisCommand 崩溃,堆损坏
原因:库和工程的运行库设置不一致。库用/MT编的,工程用/MD,两边对内存分配和释放的理解不同,跨模块 free 就炸。
解决:把工程运行库改成和库一致。不确定库用哪个,就两种都试一遍,哪种不崩用哪种。更稳妥的做法是拿到库的编译说明,照着设。
5.3 现象:连接本机 Redis 超时,但 redis-cli 能连上
原因:Redis 配置里bind 127.0.0.1只监听本地回环,而程序连的是局域网 IP;或者防火墙拦了出站。还有一种可能是 Redis 开了保护模式,只接受本地连接。
解决:先用redis-cli -h <ip> -p 6379从同一台机器测,确认网络通。然后检查 Redis 配置文件的bind和protected-mode。程序侧把超时设长一点,排除偶发网络抖动。
5.4 现象:中文或二进制 value 存进去读出来乱码
原因:用了%s而不是%b,遇到\0截断;或者读取时没按二进制处理,被当字符串截断。
解决:写入用%b传长度,读取时看 reply 的len字段而不是str,str在二进制数据里不可靠。redisReply的len才是真实长度。
5.5 现象:多线程下偶发崩溃或数据错乱
原因:hiredis 的redisContext不是线程安全的,多个线程共用一个 context 发命令,回复会串。
解决:每个线程用自己的redisContext,或者加锁串行化。常见做法是搞一个连接池,每个线程从池里取独立连接。别图省事全局共用一个,那是定时炸弹。
6. 进阶技巧:用连接池压住高并发,再补一个自检脚本
单连接在低并发下够用,一旦 QPS 上去,同步接口的阻塞特性会让线程全卡在等回复上。我一般会封一个极简连接池:启动时建 N 个redisContext,用互斥锁保护一个空闲队列,取连接时 pop,用完 push 回去。连接断了就重建,别让坏连接留在池里。
// 极简连接池骨架,重点在取用和归还的配对 class RedisPool { std::vector<redisContext*> idle_; std::mutex mtx_; public: RedisPool(const char* ip, int port, int size) { for (int i = 0; i < size; i++) { idle_.push_back(redisConnect(ip, port)); } } redisContext* acquire() { std::lock_guard<std::mutex> lock(mtx_); if (idle_.empty()) return nullptr; // 池空,调用方决定等待还是新建 auto c = idle_.back(); idle_.pop_back(); return c; } void release(redisContext* c) { std::lock_guard<std::mutex> lock(mtx_); if (c->err) { redisFree(c); return; } // 坏连接直接丢弃 idle_.push_back(c); } };acquire返回 nullptr 时,调用方可以选择阻塞等待或临时新建连接,看业务容忍度。release里检查c->err,有错的连接不还回池子,直接释放,避免下次取到坏连接。池大小一般设成并发线程数的 1 到 1.5 倍,太大浪费 Redis 的连接数配额,太小起不到缓冲作用。
再补一个自检习惯:每次换机器或换编译环境,先跑一遍第 3.3 节那段 PING 测试,确认库能链、能连、能收发。这个动作花不了一分钟,但能挡掉后面几小时的排查。从那以后我每次接入新的预编译库,都强制先跑最小闭环再写业务代码,省得业务逻辑和链接问题搅在一起分不清。希望帮到你。
本文还有配套的精品资源,点击获取