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通信不需要自己实现,有现成的库可以使用。

cpp 复制代码
#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::vectorstd::liststd::deque等)提供一个​​插入迭代器(Insert Iterator)​ ​,使得可以通过迭代器的方式向容器尾部插入元素,而不需要手动调用 push_back()insert()

|----------------------|---------------------------------------------------|
| std::back_inserter | 调用容器的 push_back(),适用于 vectorlistdeque等。 |

底层实现原理

cpp 复制代码
template<typename Container>
class back_insert_iterator {
    Container* container; // 指向目标容器的指针
public:
    // 构造函数:保存容器的指针
    explicit back_insert_iterator(Container& c) : container(&c) {}

    // 关键:重载 operator=,赋值时调用 push_back
    back_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; }
};

插入迭代器示例

cpp 复制代码
std::vector<int> dest;
auto iter = std::back_inserter(dest); // 获取插入迭代器

// 以下操作会被转换为 dest.push_back(42);
*iter = 42; // 看起来像解引用+赋值,实际调用 operator=

示例

cpp 复制代码
#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 = Hello
    printf("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 = World
    printf("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());  // 输出 string
    type = redis.type("key2");
    printf("redis < TYPE key2\nredis > \"%s\"\n", type.c_str());  // 输出 list
    type = redis.type("key3");
    printf("redis < TYPE key3\nredis > \"%s\"\n", type.c_str());  // 输出 set
 
    printf("===========================================================\n");
}
 
int main() {
    // 连接本地 Redis(默认端口 6379,无密码)
    // 若 Redis 有密码,格式为:tcp://:password@127.0.0.1:6379
    sw::redis::Redis redis("tcp://127.0.0.1:6379");
 
    // 测试通用命令
    testGenericCommands(redis);
 
    return 0;
}
相关推荐
洛克大航海3 小时前
Ubuntu安装Hbase
大数据·linux·数据库·ubuntu·hbase
赖small强3 小时前
Linux内存管理-缓存系统中的Major和Minor详解
linux·缓存·交换缓存机制·major fault·minor fault
小蒜学长4 小时前
springboot餐厅信息管理系统设计(代码+数据库+LW)
java·数据库·spring boot·后端
Justin_194 小时前
mysql数据库高级特性(一)
数据库·mysql
邂逅you4 小时前
用python操作mysql之pymysql库基本操作
数据库·python·mysql
啊森要自信4 小时前
【GUI自动化测试】YAML 配置文件应用:从语法解析到 Python 读写
android·python·缓存·pytest·pip·dash
心 一4 小时前
接口安全测试实战:从数据库错误泄露看如何构建安全防线
数据库·安全
点灯小铭4 小时前
基于单片机的PID调节脉动真空灭菌器上位机远程监控设计
数据库·单片机·嵌入式硬件·毕业设计·课程设计
小高Baby@5 小时前
Redis Key的设计
数据库·redis·缓存