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;
}
相关推荐
运维行者_8 小时前
企业无线网络监控的挑战与智能化演进趋势
大数据·运维·服务器·网络·数据库
国强_dev8 小时前
技术探讨:使用 stunnel 加密转发数据库连接时,如何获取客户端真实 IP?
数据库·网络协议·tcp/ip
@insist1238 小时前
系统规划与管理师-信息系统规划核心工作要点解析
数据库·软考·系统规划与管理师·软件水平考试·系统规划与管理工程师
超级数据查看器8 小时前
超级数据查看器 v10.0 发布
java·大数据·数据库·sqlite·安卓
数安3000天9 小时前
增量数据如何自动分类分级,避免目录“过期“?
大数据·数据库
桌面运维家9 小时前
如何用半缓存云桌面将服务器硬盘容量扩展至本地终端?
运维·服务器·缓存
南墙上的石头10 小时前
麒麟 V10 重装人大金仓 V8R6 踩坑实录(含 MySQL 兼容模式)
数据库·mysql
画中有画11 小时前
论向量数据库在项目中的应用
数据库
spider_xcxc11 小时前
Redis 数据库高质量实践指南(一)
运维·数据库·redis·oracle·云计算
l1t12 小时前
在linux和windows中解决duckdb 1.6dev版本输出执行计划报错问题
linux·运维·数据库·windows·duckdb