C++客服端访问redis
完整C++调用redisAPI示例代码:https://gitee.com/HGtz2222/bitproject.git
Redis协议
网络通信中,不同层都有协议,传输层TCP/UDP,网络层IP,这些协议都是定好的,改不了。
应用层则不一样,为了满足需求可以自定义符合自身要求的协议。redis则有自己定制的协议,RESP则是redis的协议。
redis用RESP协议向应用层协议发送请求,服务器就会按照协议进行解析得知请求放回响应。
选择redis-plus-plus
redis C++客服端->redis-plus-plus。
功能全面:支持 Redis 所有核心命令(字符串、列表、集合、哈希、有序集合等),以及集群、哨兵模式。
易用性强:API 设计简洁直观,与 Redis 原生命令高度一致,降低学习成本。
性能优秀:底层基于 hiredis,保留 C 语言的高性能特性,同时支持异步操作(本文以同步操作为主,满足大部分场景需求)。
跨平台:支持 Linux(Ubuntu/CentOS)、macOS 等主流操作系统,编译配置简单。
环境搭建
ubuntu一般就会有下面
GCC/G++ 7.0+(支持 C++17,redis-plus-plus 依赖 C++17 特性)
Git(用于下载源码)
CMake 3.0+(用于编译 redis-plus-plus,CentOS 需单独升级)
Redis 服务(本地或远程,确保 6379 端口可访问,本文以本地 Redis 为例)
安装hiredis(C语言的依赖库)
redis-plus-plus 基于 hiredis 实现,必须先安装 hiredis。直接通过系统包管理器安装,无需手动编译。
sudo apt update && sudo apt install libhiredis-dev
下载redis-plus-plus源码
git clone https://github.com/sewenew/redis-plus-plus.git
cd redis-plus-plus # 进入源码目录
编译安装redis-plus-plus
1.创建目录,防止污染源码
mkdir build && cd build
2.生成makefile
cmake ..
3.编译
cmake ..
4.安装到系统目录
sudo make install
连接Redis
redis通信不需要自己实现,有现成的库可以使用。
#include <iostream>
#include <vector>
#include <string>
#include <unordered_map>#include <sw/redis++/redis++.h>using std::cout;
using std::endl;
using std::vector;
using std::string;
using std::unordered_map;int main()
{//创建 Redis 对象的时候,需要在构造函数中,指定 redis 服务器的地址和端口sw::redis::Redis redis("tcp://127.0.0.1:6379");//URL 唯一资源定位符//此时咱们的 redis 客户端和服务端是在同一台主机上,使用本地环回就可以了//使用 ping 方法,让客户端给服务器发一个 PING,然后服务器就会返回一个 PONG,就通过 返回值 获取到string result = redis.ping();cout<< result << endl;return 0;
}
makefile文件
这里是直接链接了静态库
hello: hello.cc
g++ -std=c++17 -o $@ $^ /usr/local/lib/libredis++.a /usr/lib/x86_64-linux-gnu/libhiredis.a -pthread
.PHONY:clean
clean:
rm hello
结果
lfz@instance-hojuqq09:~/WorkPlace/redis-c/test$ ./hello
PONG
Redis-set数据结构实操
bool set(const sw::redis::StringView &key, const sw::redis::StringView &val, bool keepttl, sw::redis::UpdateType type = sw::redis::UpdateType::ALWAYS);
const sw::redis::StringView &key
:要设置值的键,sw::redis::StringView
是一种只读类型,相比std::string
针对只读操作进行了优化,在传递键时能提升效率,采用引用传递避免了不必要的拷贝 。const sw::redis::StringView &val
:与键key
对应的要设置的值,同样是sw::redis::StringView
只读类型,也是通过引用传递。bool keepttl
:用于指定在设置新值时,是否保留键原有的过期时间。如果该值为true
,在更新键值对时,原有的过期时间会被保留;如果为false
,则会根据后续是否设置过期时间相关参数来确定过期时间,若未设置相关过期参数,那么该键就没有过期时间。-
sw::redis::UpdateType type = sw::redis::UpdateType::ALWAYS:指定更新的类型,是一个枚举类型。默认值为 sw::redis::UpdateType::ALWAYS,表示无论键是否存在,都会进行设置操作。它可能还有其他取值,比如仅在键存在时更新等,具体取决于代码中对该枚举类型的定义。(EXIST就是XX,最后一个就是NX)。
std::optional<sw::redis::StringView> get(const sw::redis::StringView &key);
std::optional<sw::redis::StringView>
类型。若键存在,返回包含对应值的 optional
对象;若键不存在或操作失败,返回空 optional
(可通过 has_value()
判断是否成功获取值)。
用cout去打印时需要用optional结构中的value方法获取到能满足cout<<重载函数的参数类型,不然就会报错。
size_t exists(const sw::redis::StringView &key);
// 或支持多个键的重载版本
size_t exists(const std::initializer_list<sw::redis::StringView> &keys);
const sw::redis::StringView &key:要检查的单个键,使用 StringView 类型减少拷贝。
const std::initializer_list<sw::redis::StringView> &keys:要检查的多个键的初始化列表,支持一次性检查多个键。
size_t del(const sw::redis::StringView &key);
// 或支持多个键的重载版本
size_t del(const std::initializer_list<sw::redis::StringView> &keys);
onst sw::redis::StringView &key
:要删除的单个键。const std::initializer_list<sw::redis::StringView> &keys
:要删除的多个键的初始化列表。
std::vector<sw::redis::StringView> keys(const sw::redis::StringView &pattern);
const sw::redis::StringView &pattern
:匹配模式,支持通配符(如 *
匹配任意字符,?
匹配单个字符,[]
匹配指定范围的字符)
bool expire(const sw::redis::StringView &key, std::chrono::seconds seconds);
const sw::redis::StringView &key
:要设置过期时间的键。std::chrono::seconds seconds
:过期时间(以秒为单位),超过该时间后键会被自动删除
插入迭代器介绍
std::back_inserter(keys)
是 C++ 标准库中的一个非常有用的工具,它属于 <iterator>
头文件。它的主要作用是为容器(如 std::vector
、std::list
、std::deque
等)提供一个插入迭代器(Insert Iterator),使得可以通过迭代器的方式向容器尾部插入元素,而不需要手动调用 push_back()
或 insert()
。
| 调用容器的 |
底层实现原理
template<typename Container>
class back_insert_iterator {Container* container; // 指向目标容器的指针
public:// 构造函数:保存容器的指针explicit back_insert_iterator(Container& c) : container(&c) {}// 关键:重载 operator=,赋值时调用 push_backback_insert_iterator& operator=(const typename Container::value_type& value) {container->push_back(value); // 实际插入操作return *this;}// 其他迭代器操作(为了兼容算法,实际不做任何事情)back_insert_iterator& operator*() { return *this; }back_insert_iterator& operator++() { return *this; }
};
插入迭代器示例
std::vector<int> dest;
auto iter = std::back_inserter(dest); // 获取插入迭代器// 以下操作会被转换为 dest.push_back(42);
*iter = 42; // 看起来像解引用+赋值,实际调用 operator=
示例
#include <sw/redis++/redis++.h>
#include <cstdio>
#include <string>
#include <vector>
#include <unordered_map>
#include <chrono>// 测试通用命令
void testGenericCommands(sw::redis::Redis& redis) {printf("===================== Generic 系列命令 =====================\n");// 1. EXISTS 命令:判断键是否存在printf("\n【1. EXISTS 命令】\n");redis.flushdb(); // 清空当前数据库(避免干扰测试)bool set_ok = redis.set("key1", "Hello"); // 设置 key1 = Helloprintf("redis < SET key1 \"Hello\"\n");printf("redis > %s\n", set_ok ? "OK" : "(nil)");long long exists_count = redis.exists("key1"); // 判断 key1 是否存在printf("redis < EXISTS key1\n");printf("redis > %lld\n", exists_count); // 输出 1(存在)exists_count = redis.exists("nosuchkey"); // 判断不存在的键printf("redis < EXISTS nosuchkey\n");printf("redis > %lld\n", exists_count); // 输出 0(不存在)redis.set("key2", "World"); // 设置 key2 = Worldprintf("redis < SET key2 \"World\"\n");printf("redis > %s\n", set_ok ? "OK" : "(nil)");exists_count = redis.exists({"key1", "key2", "nosuchkey"}); // 批量判断printf("redis < EXISTS key1 key2 nosuchkey\n");printf("redis > %lld\n", exists_count); // 输出 2(key1、key2 存在)// 2. DEL 命令:删除键printf("\n【2. DEL 命令】\n");redis.flushdb();redis.set("key1", "Hello");redis.set("key2", "World");printf("redis < SET key1 \"Hello\"\nredis > OK\n");printf("redis < SET key2 \"World\"\nredis > OK\n");long long del_count = redis.del({"key1", "key2", "key3"}); // 批量删除printf("redis < DEL key1 key2 key3\n");printf("redis > %lld\n", del_count); // 输出 2(仅 key1、key2 被删除)// 3. KEYS 命令:模糊查询键printf("\n【3. KEYS 命令】\n");redis.flushdb();// 批量设置键值对std::unordered_map<std::string, std::string> kvs = {{"firstname", "Jack"},{"lastname", "Stuntman"},{"age", "35"}};redis.mset(kvs.begin(), kvs.end());printf("redis < MSET firstname Jack lastname Stuntman age 35\nredis > OK\n");// 查询包含 "name" 的键std::vector<std::string> keys;std::insert_iterator<std::vector<std::string>> ins(keys, keys.begin());redis.keys("*name*", ins); // * 代表任意字符printf("redis < KEYS *name*\n");int n = 1;for (auto& key : keys) {printf("redis > %d) %s\n", n++, key.c_str()); // 输出 firstname、lastname}// 查询以 "a" 开头、后面跟 2 个字符的键(?? 代表任意单个字符)keys.clear();redis.keys("a??", ins);printf("redis < KEYS a??\n");n = 1;for (auto& key : keys) {printf("redis > %d) %s\n", n++, key.c_str()); // 输出 age}// 4. EXPIRE + TTL 命令:设置过期时间 + 查询剩余时间(秒)printf("\n【4. EXPIRE + TTL 命令】\n");redis.flushdb();redis.set("mykey", "Hello");printf("redis < SET mykey \"Hello\"\nredis > OK\n");bool expire_ok = redis.expire("mykey", std::chrono::seconds(10)); // 10 秒后过期printf("redis < EXPIRE mykey 10\n");printf("redis > %d\n", expire_ok ? 1 : 0); // 输出 1(设置成功)long long ttl = redis.ttl("mykey"); // 查询剩余时间(秒)printf("redis < TTL mykey\n");printf("redis > %lld\n", ttl); // 输出 10(接近 10 秒,因执行有延迟)// 5. PTTL 命令:查询剩余时间(毫秒)printf("\n【5. PTTL 命令】\n");redis.flushdb();redis.set("mykey", "Hello");redis.expire("mykey", std::chrono::seconds(10));printf("redis < SET mykey \"Hello\"\nredis > OK\n");printf("redis < EXPIRE mykey 10\nredis > 1\n");long long pttl = redis.pttl("mykey"); // 毫秒级剩余时间printf("redis < PTTL mykey\n");printf("redis > %lld\n", pttl); // 输出约 10000(10 秒 = 10000 毫秒)// 6. TYPE 命令:判断键的数据类型printf("\n【6. TYPE 命令】\n");redis.flushdb();redis.set("key1", "value"); // 字符串类型redis.lpush("key2", "value"); // 列表类型redis.sadd("key3", "value"); // 集合类型printf("redis < SET key1 \"value\"\nredis > OK\n");printf("redis < LPUSH key2 \"value\"\nredis > 1\n");printf("redis < SADD key3 \"value\"\nredis > 1\n");std::string type = redis.type("key1");printf("redis < TYPE key1\nredis > \"%s\"\n", type.c_str()); // 输出 stringtype = redis.type("key2");printf("redis < TYPE key2\nredis > \"%s\"\n", type.c_str()); // 输出 listtype = redis.type("key3");printf("redis < TYPE key3\nredis > \"%s\"\n", type.c_str()); // 输出 setprintf("===========================================================\n");
}int main() {// 连接本地 Redis(默认端口 6379,无密码)// 若 Redis 有密码,格式为:tcp://:password@127.0.0.1:6379sw::redis::Redis redis("tcp://127.0.0.1:6379");// 测试通用命令testGenericCommands(redis);return 0;
}