
草莓熊Lotso个人主页❄️个人专栏:《C知识分享》 《Linux 入门到实践零基础也能懂》✨生活是默默的坚持毅力是永久的享受 博主简介文章目录前言一. RESPRedis 通信的底层协议1.1 为什么需要应用层协议1.2 请求与响应格式二. redis-plus-plus 环境搭建2.1 安装依赖hiredis2.2 编译安装 redis-plus-plus2.3 编译配置Makefile 示例三. 基础入门连接与通用命令3.1 Hello World建立连接3.2 接口设计的三个核心范式3.3 通用键命令四. 五大核心数据类型实战4.1 String 类型4.2 List 类型4.3 Set 类型4.4 Hash 类型4.5 ZSet 类型结尾前言前面我们一直在 redis-cli 中通过命令行理解 Redis 的数据类型与底层原理但真实业务开发最终都要落到代码层面。很多 C 开发者使用 Redis 客户端时只会机械调用 API遇到网络异常、返回值异常、编译报错就无从下手本质上是对通信协议、库的设计逻辑缺乏认知。本文顺着「底层协议 → 环境搭建 → 接口设计 → 全类型实战」的完整脉络先拆解 RESP 序列化协议的核心设计再系统讲解 redis-plus-plus 这个主流 C 客户端的安装、接口范式与五大类型常用操作配合源码设计视角解读带你从 “会用函数” 进阶到 “懂原理、能避坑”。一. RESPRedis 通信的底层协议1.1 为什么需要应用层协议Redis 基于 TCP 传输数据但 TCP 本身只是面向字节流的传输层协议没有业务语义。客户端发一串字节服务端要能识别成哪条命令服务端返回一串字节客户端要能解析成对应结果 —— 这就需要双方约定一套统一的数据格式规范也就是RESPREdis Serialization Protocol。和 HTTP 这类通用应用层协议不同RESP 是 Redis 专门设计的序列化协议也是所有客户端库的底层基础。它有几个鲜明特点实现简单解析高效格式规则极少解析逻辑不复杂性能开销很低肉眼可读本质是文本协议抓包后可以直接看懂内容调试非常友好与传输层弱耦合默认跑在 TCP 上但理论上也能适配其他可靠传输层一问一答模型客户端发送一条命令请求服务端返回一个对应响应模型简单清晰。1.2 请求与响应格式请求侧所有 Redis 命令最终都会被序列化为「批量字符串数组」的形式发送。比如SET key value会被编码成包含 3 个元素的数组每个元素对应命令的一部分。响应侧服务端根据命令类型返回不同类型的 RESP 数据主要包括简单字符串、错误信息、整数、批量字符串、嵌套数组等。例如GET一个不存在的 key 会返回空批量字符串SET成功返回简单字符串OKLLEN返回整数。实际开发中我们不需要手写协议解析hiredis 等底层库已经把序列化和解析的工作封装好了。但理解 RESP 的价值在于抓包排查线上问题、理解返回值的本质、甚至自研轻量客户端时能做到心里有数。二. redis-plus-plus 环境搭建C 生态下有多个 Redis 客户端库我们选用功能最完善、接口设计最现代的redis-plus-plus。它基于 hiredis 做底层封装完整支持 C11/14/17接口风格统一是工业界常用的 C Redis SDK。2.1 安装依赖hiredisredis-plus-plus 本身不做协议解析底层依赖 C 语言实现的 hiredis 库处理网络通信与 RESP 编解码需要先安装依赖# Ubuntu / Debianaptinstalllibhiredis-dev# CentOS / RHELyuminstallhiredis-devel.x86_642.2 编译安装 redis-plus-plus该库需要通过源码编译安装采用标准 CMake 流程建议使用 build 目录隔离编译产物避免污染源码# 进入源码目录创建编译目录mkdirbuildcdbuild# 生成 Makefilecmake..# 编译-j 后接 CPU 核心数加快速度make-j4# 安装到系统目录makeinstall安装完成后核心文件分布库文件/usr/local/lib/下的libredis.a静态库与libredis.so动态库头文件/usr/local/include/sw/redis/入口头文件为redis.h。2.3 编译配置Makefile 示例编写业务代码时编译链接需要包含三部分redis 库、hiredis 库、pthread 线程库。一个最简 Makefile 如下all: hello hello: hello.cc g -stdc17 -o $ $^ \ /usr/local/lib/libredis.a \ /usr/lib/x86_64-linux-gnu/libhiredis.a \ -pthread .PHONY: clean clean: rm -rf hello三. 基础入门连接与通用命令3.1 Hello World建立连接核心类是sw::redis::Redis构造函数传入服务端地址格式为tcp://IP:端口。#includeiostream#includesw/redis/redis.hintmain(){// 连接本地 Redis 服务默认端口 6379sw::redis::Redisredis(tcp://127.0.0.1:6379);// ping 检测连通性std::string resredis.ping();std::coutresstd::endl;// 输出 PONGreturn0;}编译运行后输出PONG即代表连接正常。3.2 接口设计的三个核心范式redis-plus-plus 的接口设计高度统一掌握这三个规律所有命令都可以举一反三不用死记硬背。1. 只读参数使用 StringView类似 C17 标准库的std::string_view它是只读字符串视图避免不必要的字符串拷贝针对只读场景做了性能优化。所有输入的 key、member 等只读参数都采用这个类型。2. 空值语义用 Optional 表达对于可能返回 nil 的命令比如 get 不存在的 key库中使用std::optional包装返回值例如OptionalString。⚠️ 重要踩坑点不要直接调用.value()取值。如果 optional 处于无效状态直接取值会抛出std::bad_optional_access异常导致程序崩溃。 正确做法是先通过 if 判断optional 可隐式转换为 bool 类型autovalredis.get(not_exist_key);if(val){std::cout值为val.value()std::endl;}else{std::coutkey 不存在std::endl;}3. 多结果返回使用输出迭代器所有返回多个元素的命令keys、lrange、smembers 等都不直接返回固定容器而是要求传入一个输出迭代器。 这是典型的泛型解耦设计库不限制用户使用 vector、set 还是自定义容器只要支持对应插入动作就能用灵活性非常高。std::vectorstd::stringkeys;redis.keys(*,std::back_inserter(keys));3.3 通用键命令exists / del既支持单个 key也支持初始化列表批量操作返回值为成功操作的 key 数量。// 判断单个 key 是否存在longlongexist_cntredis.exists(key);// 批量删除多个 keylonglongdel_cntredis.del({key1,key2,key3});expire / ttl过期时间支持std::chrono时间类型语义清晰避免魔法数字。#includechronousingnamespacestd::chrono_literals;redis.expire(key,10s);longlongremainredis.ttl(key);type返回 key 对应 value 的类型字符串和命令行输出一致。std::string typeredis.type(key);// 返回 string / list / hash / set / zset四. 五大核心数据类型实战4.1 String 类型基础读写与 NX/XX 选项set支持传入过期时间和写入策略UpdateType::EXIST对应命令行的 XX仅更新UpdateType::NOT_EXIST对应 NX仅新增。// 仅当 key 不存在时写入redis.set(key,value,0s,sw::redis::UpdateType::NOT_EXIST);mset / mget批量操作支持初始化列表和迭代器两种传参方式适合批量写入与批量读取场景减少网络 IO。// 批量写入redis.mset({{k1,v1},{k2,v2},{k3,v3}});// 批量读取std::vectorsw::redis::OptionalStringresult;redis.mget({k1,k2,k4},std::back_inserter(result));其他常用命令getrange/setrange做子串读写incr/decr做原子增减返回long long类型的计算后的值用法和命令行一一对应。4.2 List 类型lpush / rpush / lrange插入支持单个元素、初始化列表、迭代器三种形式范围查询通过输出迭代器接收结果。redis.lpush(list,{a,b,c});std::vectorstd::stringres;redis.lrange(list,0,-1,std::back_inserter(res));lpop / rpop弹出列表一端的元素返回OptionalString列表为空时返回无效值。blpop / brpop阻塞弹出命令返回OptionalStringPairpair 的 first 是数据所属的 keysecond 是弹出的元素。支持同时监听多个 key哪个列表先有元素就返回哪个。autoresredis.blpop({list1,list2},10s);if(res){std::cout来源key: res-first, 元素: res-secondstd::endl;}4.3 Set 类型sadd / smemberssmembers的结果用std::set容器存储更贴合语义。注意 set 没有push_back方法不能用back_inserter要使用std::inserter。redis.sadd(tag_set,{java,cpp,redis});std::setstd::stringresult;redis.smembers(tag_set,std::inserter(result,result.end()));sismember / spop / scardsismember判断元素是否存在返回 1 或 0spop随机弹出并返回一个元素返回OptionalStringscard获取集合元素总数返回long long。sinter / sinterstore集合交集运算同样通过输出迭代器接收结果也支持将运算结果存入新的 key。4.4 Hash 类型hset / hgethset写法非常灵活支持单个键值对、std::pair、初始化列表、迭代器多种形式。// 单字段写入redis.hset(user:1,name,张三);// 批量写入redis.hset(user:1,{{age,22},{score,95}});hexists / hdel / hlen分别用于判断字段是否存在、删除字段、获取字段总数语义和命令行完全一致。hkeys / hvals / hmget均为多结果返回通过输出迭代器写入容器适合批量获取字段名、字段值。4.5 ZSet 类型zadd / zrangezadd传入 member 与对应分数支持批量写入。zrange有两种接收模式库会根据迭代器指向的容器类型自动适配容器元素为string只返回 member容器元素为pairstring, double同时返回 member 和 score。redis.zadd(rank,吕布,99);redis.zadd(rank,{{赵云,98},{典韦,97}});// 只获取成员std::vectorstd::stringmembers;redis.zrange(rank,0,-1,std::back_inserter(members));// 同时获取成员与分数std::vectorstd::pairstd::string,doubleres;redis.zrange(rank,0,-1,std::back_inserter(res));zcard / zrem / zscore / zrankzscore查询元素分数返回OptionalDoublezrank查询元素的升序排名返回 optional 包装的整数。源码视角redis-plus-plus 的设计哲学站在 C 系统编程的角度看这个库的架构设计非常值得借鉴核心有三点1. 高度统一的接口约定所有多入参命令都支持「单个值 初始化列表 迭代器」三种写法所有多返回值都用输出迭代器所有可能为空的返回都用 optional 包装。使用者学会一个命令就能类推所有命令学习成本极低也不容易用错。2. 泛型编程解耦容器用输出迭代器替代直接返回 vector是典型的 STL 式设计思路库不假设用户用什么容器用户可以根据场景选择 vector、set、deque 甚至自定义容器灵活性和扩展性都更强。3. 不重复造轮子的分层封装redis-plus-plus 并没有从零实现协议解析和网络通信而是基于成熟的 hiredis C 库做上层封装把原始的 RESP 响应转换成类型安全、语义清晰的 C 接口。底层做性能、上层做易用性是工业界非常经典的分层封装思路。核心考点总结RESP 协议Redis 的应用层序列化协议基于 TCP采用一问一答模型请求为批量字符串数组响应包含简单字符串、整数、批量字符串等多种类型核心特点实现简单、解析高效、文本可读。环境搭建底层依赖 hiredis 做协议与网络处理redis-plus-plus 需源码编译安装编译必须链接 redis、hiredis、pthread 三个库。接口设计范式入参支持单个值、初始化列表、迭代器三种形式多返回值通过输出迭代器接收解耦容器类型空值使用std::optional先判断再取值禁止直接调用 value ()。各类型核心用法Stringset 的 NX/XX 策略、mset/mget 批量操作Listblpop 返回 key 元素 pair支持多 key 监听Setsmembers 适配 set 容器需使用std::inserterZSetzrange 根据容器类型自动适配是否返回分数。常见坑点optional 空值取值触发异常set 容器误用 back_inserter 导致编译失败遗漏 pthread 链接导致符号未定义。结尾 我是草莓熊 Lotso若这篇技术干货帮你打通了学习中的卡点 【关注】跟我一起深耕技术领域从基础到进阶见证每一次成长 ❤️ 【点赞】让优质内容被更多人看见让知识传递更有力量 ⭐ 【收藏】把核心知识点、实战技巧存好需要时直接查、随时用 【评论】分享你的经验或疑问比如曾踩过的技术坑一起交流避坑 ️ 【投票】用你的选择助力社区内容方向告诉大家哪个技术点最该重点拆解 技术之路难免有困惑但同行的人会让前进更有方向愿我们都能在自己专注的领域里一步步靠近心中的技术目标结语从 RESP 字节级协议到 hiredis C 语言基础库再到 redis-plus-plus 现代 C 封装整个客户端体系是一层一层的语义抽象底层负责性能与协议上层负责易用与安全。理解了这个分层逻辑再遇到连接异常、返回值异常、编译报错就能快速定位到网络层、协议层还是业务层的问题。掌握基础命令之后后续还可以继续深入管道、事务、连接池、哨兵与集群客户端等进阶能力进一步适配生产环境的高可用、高性能需求。✨把这些内容吃透超牛的放松下吧✨ʕ˘ᴥ˘ʔづきらど