尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Windows下编译使用libmemcached:从源码到API调用的完整指南

Windows下编译使用libmemcached:从源码到API调用的完整指南 简介libmemcached-win32 是一份可在 win32 平台下用 VC2008 编译的 libmemcached 源码包面向 Windows 下需要集成 Memcache 客户端的 C/C 开发者尤其适合因现有 Windows 版客户端性能一般而转向 libmemcached 的场景。整个资源共 74 个文件压缩包仅 47KB以 37 个 C 源文件和 28 个头文件为主体并连同 vcproj 工程文件、Makefile.w32 编译脚本、def 导出定义等可直接在 Visual Studio 2008 环境中打开并参与编译。源码覆盖了 memcached 的协议解析、数据读写、服务器连接、哈希算法等核心实现头文件接口完整方便按需裁剪或二次封装对于希望为 ASP 等上层应用提供高效 win32 memcache 客户端的开发者这份包省去了自行移植和配置的大量工作。已有 229 人学习下载作者同时说明目前仅确认可编译尚未做到完整测试实际部署时需结合自身使用场景验证功能和性能。 很多做 Windows 服务端开发的朋友应该都经历过这种尴尬项目在 Linux 上跑得好好的memcached 客户端也用的是成熟的 libmemcached结果一到 Windows 环境要么编译不过要么跑起来各种诡异的崩溃。我自己最早接触 libmemcached-win32 这个方向就是因为当时的项目需要把一套消息处理模块从 Linux 平滑迁到 Windows又不想把底层缓存访问逻辑重写一遍。说实话那会儿网上关于 Windows 下编译和使用 libmemcached 的资料非常零散我踩了不少坑才把整套流程跑通。这篇博文我就把自己从编译到接入的完整经验整理出来希望对同样需要在 win32 平台用上 libmemcached 的人有点帮助。libmemcached 本身是一个 C/C 的 memcached 客户端库提供了一套相当完整的 API支持连接池、一致性哈希、二进制协议、异步操作等能力。官方仓库虽然以 Unix 系为主但代码本身并非完全不可移植通过 CMake 和合适的依赖库是能在 Windows 上编译出可用版本的。关键点在于环境准备、依赖选型和几个源码层的微调。1. 项目核心背景与整体设计思路1.1 libmemcached 是什么为什么 Windows 上需要它简单说libmemcached 就是让你在 C/C 代码里操作 memcached 服务端的一套工具库。它解决的问题很直接不用你手动拼协议报文、维护 socket 连接、处理超时重传而是用一组符合直觉的函数调用即可完成缓存读写。举个例子你只要调用memcached_set(memc, key, key_len, value, value_len, expiration, flags)就能写入一条缓存再调用memcached_get就能读出来剩下的连接管理和协议细节对调用方完全透明。这套库在 Linux 生态里几乎算是标配因为很多后端服务本身就运行在 Linux 上。但 Windows 开发者同样有高性能缓存访问的需求尤其是一些从 Linux 迁移过来的服务端程序或者需要在 Windows 上进行本地开发、联调、压力测试的场景。直接换一个纯 Windows 的 memcached 客户端库往往意味着要改动大量业务层代码。在这种情况下能够直接把 libmemcached 编译成 win32 版本保持调用接口不变自然是最省事的方案。1.2 Windows 场景的独特痛点与方案取舍在 Windows 上跑 libmemcached最大的问题不是代码本身而是它依赖的 POSIX 线程模型和部分 Unix 习惯的 API。libmemcached 底层在多线程场景下会用到pthread而 Windows 默认没有这个线程库所以必须借助 pthread-win32 这类兼容层来提供pthread_t、pthread_mutex_t等类型和函数。此外还有编译工具链的问题。Windows 下主要有两条路线一条是用 Visual Studio 配 MSVC 编译器另一条是用 MinGW-w64 配 GCC。两条路线我都试过结论是 MSVC 路线编译出来的 DLL 在对接其他 MSVC 工程时更省心但源码层面需要更多适配MinGW 路线编译更快生成文件部署反而简单因为对 VC runtime 的依赖更少。我的建议是如果你的项目本身是 Visual Studio 工程就优先走 MSVC如果只是需要一个能跑的 DLL 供自己程序调用MinGW 会省掉很多莫名其妙的问题。注意不管选哪条路线都不要寄希望于“直接下载官方二进制包就能用”。官方仓库并没有维护 Windows 预编译产物网上能搜到的老旧版本大多对应的是 libmemcached 早期 API和 1.0 以后的新接口差异较大直接用容易踩坑。1.3 最终方案选型与整体流程规划我当时的规划很简单用 CMake 生成 Visual Studio 工程编译 x64 静态库和动态库依赖 pthread-win32 和 OpenSSL用于 SASL 认证把编译产物和头文件整理到一个独立目录之后在业务工程里直接引用。整个过程分为五步准备第三方依赖pthread-win32、OpenSSL获取 libmemcached 源码并调整 Windows 适配用 CMake 生成 VS 工程并编译整理头文件、库文件和 DLL在测试工程里验证 API 调用。这套流程跑通之后你的 Windows 程序就能用一套和 Linux 几乎一致的代码操作 memcached迁移成本大大降低。2. 环境搭建与依赖处理细节2.1 编译工具链选择MSVC vs MinGW先说结论如果你追求稳定和后续调试体验用 Visual Studio 2019 或 2022 配 MSVC 就够了。libmemcached 源码层面本身用到了不少 C99 特性比如可变长数组、一些头文件声明方式MSVC 在新版本里对 C99/C11 的支持已经比以前好很多但仍有几个函数需要手动处理比如snprintf在旧版本 VS 里的行为差异。如果你不想一个个改源码MinGW-w64 是更快的路径。因为它自带完整的 POSIX 兼容层选项很多在 MSVC 下会报错的头文件和函数在 MinGW 下能直接编译过去。我实测用 MinGW-w64 GCC 10 编译 libmemcached 1.0.18基本没怎么改动源码整个过程二十分钟以内结束。而 MSVC 路线我花了大半天处理各种兼容性问题。所以个人建议首次尝试、或者只做内部工具使用的建议先用 MinGW 打通流程如果你的交付物必须给 VS 工程引用再回头处理 MSVC 的适配。对比项MSVCMinGW-w64源码适配工作量较大需处理多个兼容点较小基本可直编产物对接 VS 工程天然兼容需要额外注意调用约定运行时依赖VC Redistributablelibgcc_s、libwinpthread调试体验风场好可直接断点支持但略弱2.2 第三方依赖准备pthread-win32 与 OpenSSLpthread-win32 是绕不开的。libmemcached 在启用多线程行为时内部会用pthread_rwlock_t等结构来保护memcached_st的某些数据成员。你可以从 pthread-win32 的官方仓库获取源码自己编译pthreadVC2.lib和pthreadVC2.dll也可以直接下载别人编译好的二进制包。需要注意要和编译 libmemcached 的架构一致我做的是 x64 版就选 x64 依赖。OpenSSL 在默认编译中不算必需品除非你要启用 SASL 认证或 TLS 的扩展支持。如果你确定不需要这些高级功能在 CMake 配置里可以关闭相关选项这样能少一个坑少装一个依赖。我当时因为测试环境没有认证要求直接关掉了 SASL整个编译清爽很多。2.3 源码获取与目录规划从 libmemcached 官方 GitHub 拉取源码时注意 checkout 到最新 tag。我推荐使用 1.0.18 这个版本它相对稳定API 也是现代风格。我习惯把源码放到一个干净的目录下结构如下D:\work\libmemcached-build\ deps\ pthread-win32\ openssl\ libmemcached-src\ out\ include\ lib\ bin\把依赖库和源码分开编译输出统一到 out 目录后续集成其他工程时只拷贝 out 下的文件即可不会污染原工程。这种目录规划虽然简单但在多次重新编译和切换架构时能省不少时间。3. 核心功能实现与 API 调用要点3.1 连接管理创建实例与添加服务器不论在哪个平台libmemcached 的第一个操作总是创建一个memcached_st实例。这是整个客户端状态的载体所有后续操作都围绕它展开。最简单的创建方式如下#include libmemcached/memcached.h memcached_st *memc memcached_create(NULL); if (memc NULL) { fprintf(stderr, failed to create memcached handle\n); return -1; } memcached_return rc memcached_server_add(memc, 127.0.0.1, 11211); if (memcached_failed(rc)) { fprintf(stderr, add server failed: %s\n, memcached_strerror(memc, rc)); memcached_free(memc); return -1; }注意两点第一memcached_create(NULL)是让库自己分配内存还有一种用法是传入一个已分配的结构但自己分配时记得最终要调用memcached_free否则会内存泄漏。第二memcached_failed(rc)是官方提供的返回值判断宏它本质上是检查返回值是否不等于MEMCACHED_SUCCESS比直接拿整数比较可读性和健壮性都好很多。3.2 基本缓存读写set、get、delete核心的数据访问操作和 Linux 下完全一致。这是一个基本写入和读取的例子const char *key username; size_t key_len strlen(key); const char *value zhangsan; size_t value_len strlen(value); time_t expiration 300; // 300 秒后过期 uint32_t flags 0; rc memcached_set(memc, key, key_len, value, value_len, expiration, flags); if (memcached_failed(rc)) { fprintf(stderr, set failed: %s\n, memcached_strerror(memc, rc)); return -1; } size_t return_len 0; uint32_t return_flags 0; char *result memcached_get(memc, key, key_len, return_len, return_flags, rc); if (memcached_failed(rc)) { fprintf(stderr, get failed: %s\n, memcached_strerror(memc, rc)); } else { // 注意 result 不一定是以 \0 结尾的字符串 printf(get value: %.*s\n, (int)return_len, result); free(result); // 释放返回值 }这里有个容易忽略的细节memcached_get返回的result是库内部 malloc 出来的一块内存用完之后必须由调用方释放。另外返回值也不保证包含字符串终止符\0所以打印或者拷贝时一定要用return_len来限制长度否则可能越界。这个习惯在 Windows 和 Linux 下都一样但我发现很多同学在迁移过程中会忽略这点导致内存泄漏或读到脏数据。删除操作就简单很多rc memcached_delete(memc, key, key_len, 0); // 第三个参数是延迟秒数0 表示立即删除3.3 批量操作与性能提升如果你的业务场景需要同时读取多个 key不要用循环去调memcached_get而应该使用批量接口。libmemcached 提供了memcached_mget和配合使用的memcached_fetch这是它相比某些简易客户端库的重要优势。const char *keys[] {key1, key2, key3}; size_t key_lens[] {4, 4, 4}; rc memcached_mget(memc, keys, key_lens, 3); char *value; size_t value_len; uint32_t flags; char *return_key; size_t return_key_len; while ((value memcached_fetch(memc, return_key, return_key_len, value_len, flags, rc)) ! NULL) { printf(key: %.*s, value: %.*s\n, (int)return_key_len, return_key, (int)value_len, value); free(value); // return_key 记得也要释放 free(return_key); }批量读的核心价值在于减少了多次网络往返。如果你的缓存服务分布在多台服务器上libmemcached 会根据一致性哈希自动分发请求把不同 key 的请求并行发到对应节点这对于高并发系统来说性能提升非常明显。我在压测中对比过批量读取 100 个 key 相比循环单点读取整体耗时能下降 50% 以上具体数字取决于网络延迟和服务端负载。3.4 行为设置与超时控制Windows 网络环境和 Linux 有差异特别是不稳定网络下容易卡住所以超时设置很重要。libmemcached 提供了一组行为开关通过memcached_behavior_set来配置。比较常用的是连接超时和读取超时struct timeval timeout; timeout.tv_sec 1; timeout.tv_usec 0; memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_CONNECT_TIMEOUT, (uint64_t)1000); memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_RCV_TIMEOUT, (uint64_t)1000); memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_SND_TIMEOUT, (uint64_t)1000);其中时间单位是毫秒。CONNECT_TIMEOUT控制建立 TCP 连接的超时RCV_TIMEOUT和SND_TIMEOUT控制每次读写操作的最大时间。这两个设置能有效避免后端 memcached 服务无响应时客户端线程一直阻塞。我实际遇到过一次因为没设超时导致整个进程挂住十几秒的情况排查到最后发现是一条网络断连引起 recv 永久等待后来统一加上超时控制才解决。3.5 多线程场景下的正确打开方式Windows 上的多线程模型虽然比传统 POSIX 更复杂但 libmemcached 本身是线程安全的前提是你按正确方式使用。关键点在于多个线程可以共享同一个memcached_st实例但同一个时刻只有一个线程能安全执行操作。如果多线程同时调用 set 或 get内部虽然会加锁保护但会引入不必要的互斥开销导致性能下降。我更推荐的做法是每个线程维护一个独立实例。这种方式在连接较多时看似浪费但收益是免锁访问性能明显更好。如果资源紧张也可以使用对象池方式预先创建多个memcached_st按线程 ID 或随机分配取一个用。我们内部的网关程序就是采用池化方案每个工作线程绑定一个独立实例实测比共享单实例的 QPS 提升约 30%。4. 编译与部署中的常见问题排查实录4.1 编译阶段C99 特性与 MSVC 冲突这是走 MSVC 路线最常遇到的一类问题。源码中的一些函数和 MSVC 不支持的特性会产生编译冲突。最常见的包括ssize_t类型在 MSVC 中不存在需要在编译宏里定义_SSIZE_T_DEFINED或直接在头文件补充 typedefsnprintf的旧版 MSVC 不支持需要#define snprintf _snprintf或升级到较新的 MSVC 版本getopt.h等 POSIX 工具头文件缺失虽然 libmemcached 主库用不到但用于测试的工具链可能会报错。我的建议是遇到这类报错不要硬调 libmemcached 源码逻辑能通过宏定义处理的就用宏能升级工具链的就升级。硬改源码逻辑虽然能让单个版本编译通过但之后拉新版本代码时还要重新维护补丁非常不划算。4.2 链接阶段pthread 符号无法解析MinGW 路线有个典型的链接报错undefined reference to pthread_rwlock_init或者pthread_create。出现这个报错有两种可能。第一种是编译 libmemcached 时没有链接 pthread 库需要在 CMake 配置中显式加上-lpthread或者-lpthreadVC2。第二种是库文件顺序问题在 GCC 链接时依赖库必须放在引用它的对象文件后面如果顺序不对就会报 undefined reference。解决办法是把 pthread 库放到链接命令的最后面或者使用pkg-config的方式管理依赖。4.3 运行时阶段DLL 找不到与 VC Runtime 报错编译成功只是第一步运行时才是最考验人的地方。最容易遇到的情况有两种一是你自己的程序启动时提示找不到libmemcached.dll、pthreadVC2.dll或libgcc_s_seh-1.dll。这是 Windows 下最常见的使用者错误根本原因是 DLL 不在搜索路径里。解决方案是把这些 DLL 全部拷贝到程序所在目录或者加入环境变量 PATH。我一般会写一个部署脚本把 out\bin 下的所有 DLL 统一拷贝到测试程序的 exe 目录避免手动漏拷。二是程序启动直接弹窗提示microsoft.vc80.crt或类似的运行库缺失这属于 MSVC 运行时依赖问题。如果你使用的 libmemcached 版本是用旧版 VC 编译的或者你本机没装对应版本的 VC Runtime就会触发。解决方案是安装对应的 Visual C Redistributable 包或者换成用新版本工具链重新编译的产物。我在实践中最省心的方式是用 Visual Studio 2019 之后版本重新编译依赖这样运行时只需要较新的 VC Runtime兼容性比老版本更好。4.4 网络层问题连接超时与防火墙干扰有时库本身编译和部署都没问题但程序连不上 memcached 服务。这时候先不要在客户端代码里找原因先用 telnet 或 nc 确认服务端端口是否通。例如telnet 127.0.0.1 11211在 Windows 上默认可能没有安装 telnet你可以改用 PowerShell 测试Test-NetConnection -ComputerName 127.0.0.1 -Port 11211如果端口不通优先检查服务端 memcached 是否绑定到了可访问的 IP 上以及 Windows 防火墙是否放行了 TCP 11211。很多时候本地开发机的问题都出在防火墙拦截而不是代码逻辑。4.5 常见问题速查表阶段现象原因解决建议编译找不到 ssize_t / snprintf 报错MSVC 对 POSIX 兼容弱补充宏定义或升级工具链编译pthread 相关符号 undefined未链接 pthread 库显式引入 pthreadVC2/libpthread链接依赖库顺序错误GCC 静态链接顺序敏感将 pthread 依赖放命令末尾运行提示 DLL 找不到DLL 不在搜索路径复制 DLL 到 exe 目录或设置 PATH运行提示 VC 运行库缺失缺少对应 VC Runtime安装配套 Redistributable运行连接一直超时防火墙/网络隔离用 Test-NetConnection 排除网络问题运行进程卡死无响应没有设置超时行为添加 CONNECT_TIMEOUT 和 RCV_TIMEOUT运行get 返回值是乱码没按 return_len 使用数据以长度为准不要依赖 \0 结尾4.6 一个性能排查实例最后聊一个我在实际项目里遇到的性能问题。当时模块从 Linux 迁到 Windows 后缓存读写延迟突然从 1ms 涨到 30ms刚开始以为是 libmemcached 在 Windows 上性能有问题差点准备换客户端库。后来排查发现问题根本不在库本身而是我们每读一个 key 都会新建和销毁一个memcached_st实例等于每次操作都重新建立 TCP 连接握手开销自然大。查清之后我把memcached_st的创建移到了模块初始化阶段改为全局复用再配合之前的超时设置延迟很快降回 2ms 以内。这个例子说明跨平台移植后性能问题往往不是库的问题而是使用方式在目标平台上被放大了。Windows 上新建 socket 的开销和 Linux 相比并不低连接复用尤为重要。5. 收尾一些实际操作中的体会如果非要给后来者一句建议我会说libmemcached-win32 这条路能通但一定要把依赖管理做到位。我在第一轮尝试时图省事直接拿别人编译好的旧版 DLL 去对接结果 API 头文件版本和库实现版本不一致折腾了整整一个周末才定位到问题是版本错配。后来老老实实从源码编译虽然过程麻烦了一点但后续接入任何 VS 工程、任何架构模式都心里有底。另外一个实用小技巧是编译时打开 memcached 的 debug 日志开关首次联调时把库的交互过程打印出来这在定位“为什么这台机器连不上、那台机器能连上”这类环境问题时特别有效。正式发布时再关掉即可。整个移植过程并不复杂核心就三件事选对工具链配好 pthread 兼容层记住 DLL 部署路径。把这三点处理好你在 Windows 上使用 libmemcached 的体验和 Linux 不会有太大差别。如果后面我再遇到新的坑会继续在这篇内容里补充更新希望这份经验能帮大家少走几步弯路。本文还有配套的精品资源点击获取
返回列表