在 Visual Studio 上连接 WSL 使用 redis-plus-plus

今天玩一下 redis-plus-plus 这个东西。这个是一个基于 c++ 的 redis 的客户端库。可以使我们的程序作为一个客户端连接到我们的 redis 服务器,对服务器进行操作。

安装

redis-plus-plus 是基于 hiredis 的,所以要先安装 hiredis。可以直接使用包管理器。

sudo apt install libhiredis-dev

然后我们下载一下 redis-plus-plus 的源码。

git clone https://github.com/sewenew/redis-plus-plus.git

然后我们找到 redis-plus-plus 的文件夹下面,以此执行以下几个命令。

mkdir build && cd build
cmake .. 
make
sudo make install

安装成功后,我们可以在 usr/local/include 中多出 sw ⽬录, 并且内部包含 redis-plus-plus 的⼀系列头⽂件. 会在 usr/local/lib 中多出⼀系列 libredis 库⽂件,后面我们在创建项目的时候要用到。

然后我们开启一个 Visual Studio 的 Linux 项目,使用 ssh 连接到咱们的虚拟机或者 WSL 。打开项目属性->链接器->输入->附加依赖项,在里面加入我们的库的路径,分别是:

/usr/local/lib/libredis++.a;
/lib/x86_64-linux-gnu/libhiredis.a;

系统不同库文件的路径也可能会不一样,我们需要找到自己的库文件在哪里。加入之后我们就可以 #include <sw/redis++/redis++.h> 包含头文件正式使用关于 redis-plus-plus 的功能啦。

使用

下面给出一些常用的 API。

auto redis = Redis("tcp://127.0.0.1:6379");
auto res = redis.ping();
std::cout << res << std::endl;

连接到 redis 服务端,字符串中的IP地址和端口要换成自己的服务端所在的地址。

ping() 可以测试我们的连接是否成功,如果成功,我们会收到一个 PONG ,它会以返回值的形式存在 res 中。

 redis.set("key", "val");
    auto val = redis.get("key");    // val is of type OptionalString. See 'API Reference' section for details.
    if (val) {
        // Dereference val to get the returned value of std::string type.
        std::cout << *val << std::endl;
    }   // else key doesn't exist.

set 函数可以设置一个 string 类型的值到数据库中,第一个参数为 key,第二个是对应的 value。

get 函数可以从数据库中获得 key 对应的 value,该值会以返回值的形式返回。

    std::vector<std::string> vec = {"a", "b", "c"};
    redis.rpush("list", vec.begin(), vec.end());

    redis.rpush("list", {"a", "b", "c"});

    vec.clear();
    redis.lrange("list", 0, -1, std::back_inserter(vec));

上文为给 list 对象插入内容的两种方式,rpush 为从右侧插入,lpush 为左插
第一种:使用 vector 容器。第一个参数为 key,第二第三个参数为容器的两个迭代器,表示插入的区间。
第二种:直接插入一个右值,第一个参数为 key,第二个参数是要插入的右值。

最后一行内容为把 list 对象中的元素存入到 vector 容器中
第一个参数为对应的 key,第二第三个参数为 list 中元素的下标,表示要存入的区间,第三个参数为 -1 表示,此区间直到对象末尾。
最后一个参数是一种用于为容器添加元素的迭代器,上文中表示的是尾插的迭代器。

    redis.hset("hash", "field", "val");

    // Another way to do the same job.
    redis.hset("hash", std::make_pair("field", "val"));

两种方式插入哈希类型,第一种是直接插入,第二种是使用 pair 对象进行插入。

    std::unordered_map<std::string, std::string> m = {
        {"field1", "val1"},
        {"field2", "val2"}
    };
    redis.hmset("hash", m.begin(), m.end());

使用 map 批量插入。

    m.clear();
    redis.hgetall("hash", std::inserter(m, m.begin()));

把 hash 对象中所有的键和值都存到 map 中。
第一个参数为 hash 对象对应的 key,第三个参数为插入的迭代器。

    std::vector<OptionalString> vals;
    redis.hmget("hash", {"field1", "field2"}, std::back_inserter(vals));

把 hash 对象中指定的键对应的值存到 map 中。
第一个参数为 hash 对象对应的 key,第二个参数为需要存储的值对应的 keys,第三个参数为插入的迭代器。

    redis.sadd("set", "m1")

在 set 对象中插入一个值。

    std::unordered_set<std::string> set = {"m2", "m3"};
    redis.sadd("set", set.begin(), set.end());

    redis.sadd("set", {"m2", "m3"});

批量插入的两种方法:使用 unordered_set 容器和直接插入。

    set.clear();
    redis.smembers("set", std::inserter(set, set.begin()));

把 set 容器中的元素存入到 set 容器中。

    if (redis.sismember("set", "m1")) {
        std::cout << "m1 exists" << std::endl;
    }   // else NOT exist.

判断数据库中对应的 set 对象中是否有该元素,如果有则返回 true 否则返回 false。

    redis.zadd("sorted_set", "m1", 1.3);

在数据库中的 sorted set 对象中插入元素,第一个参数为 key,第二个参数为插入的元素,第三个参数为插入的元素的分数。

    std::unordered_map<std::string, double> scores = {
        {"m2", 2.3},
        {"m3", 4.5}
    };
    redis.zadd("sorted_set", scores.begin(), scores.end());

在数据库中的 sorted set 对象中,使用 unorder_map 容器批量插入元素。

    std::vector<std::pair<std::string, double>> zset_result;
    redis.zrangebyscore("sorted_set",
            UnboundedInterval<double>{},            // (-inf, +inf)
            std::back_inserter(zset_result));

将 sorted set 对象中的元素存储到 vector 容器中。其结果是有序的,如果保存到 unordered_map 中 会失去顺序。
第一个参数为 key,第二个参数为一个区间(上文中表示的为正无穷到负无穷),第三个参数为一个插入的迭代器。

    std::vector<std::string> without_score;
    redis.zrangebyscore("sorted_set",
            BoundedInterval<double>(1.5, 3.4, BoundType::CLOSED),   // [1.5, 3.4]
            std::back_inserter(without_score));

此种方法只会吧元素的内容保存到队列中,而不会保存分数。第二个参数表示为 1.5 到 3.4 的一个闭区间。

    std::vector<std::pair<std::string, double>> with_score;
    redis.zrangebyscore("sorted_set",
            BoundedInterval<double>(1.5, 3.4, BoundType::LEFT_OPEN),    // (1.5, 3.4]
            std::back_inserter(with_score));

上文中第二个参数表示的是一个 1.5 到 3.4 的左开右闭区间。

    // Script returns a single element.
    auto num = redis.eval<long long>("return 1", {}, {});

    // Script returns an array of elements.
    std::vector<std::string> nums;
    redis.eval("return {ARGV[1], ARGV[2]}", {}, {"1", "2"}, std::back_inserter(nums));

    // mset with TTL
    auto mset_with_ttl_script = R"(
        local len = #KEYS
        if (len == 0 or len + 1 ~= #ARGV) then return 0 end
        local ttl = tonumber(ARGV[len + 1])
        if (not ttl or ttl <= 0) then return 0 end
        for i = 1, len do redis.call("SET", KEYS[i], ARGV[i], "EX", ttl) end
        return 1
    )";

    // Set multiple key-value pairs with TTL of 60 seconds.
    auto keys = {"key1", "key2", "key3"};
    std::vector<std::string> args = {"val1", "val2", "val3", "60"};
    redis.eval<long long>(mset_with_ttl_script, keys.begin(), keys.end(), args.begin(), args.end());

上文是一些脚本编程的使用方法。

 // Create a pipeline.
    auto pipe = redis.pipeline();

    // Send mulitple commands and get all replies.
    auto pipe_replies = pipe.set("key", "value")
                            .get("key")
                            .rename("key", "new-key")
                            .rpush("list", {"a", "b", "c"})
                            .lrange("list", 0, -1)
                            .exec();

    // Parse reply with reply type and index.
    auto set_cmd_result = pipe_replies.get<bool>(0);

    auto get_cmd_result = pipe_replies.get<OptionalString>(1);

    // rename command result
    pipe_replies.get<void>(2);

    auto rpush_cmd_result = pipe_replies.get<long long>(3);

    std::vector<std::string> lrange_cmd_result;
    pipe_replies.get(4, back_inserter(lrange_cmd_result));

如上是 redis 管道操作的相关代码:
使用 pipeline() 函数创建一个管道对象 pipe。然后我们可以直接对管道对象调用 前面的命令函数,管道会为我们保存这些命令,在我们调用 exec() 函数时统一发送给 redis 服务端。服务端在处理完这些命令后也会统一给我们进行回复。这样可以减少客户端与服务端的交互次数,节省网络开销。命令执行结果我们存储在了 pipe_replies 对象中,我们可以使用 get() 函数加数据类型,使用类似于数组的下标的传参方式获取对应的命令的结果,也可以使用 get() 函数加数组下标传参加插入迭代器的方式把结果存入容器中。

 // Create a transaction.
    auto tx = redis.transaction();

    // Run multiple commands in a transaction, and get all replies.
    auto tx_replies = tx.incr("num0")
                        .incr("num1")
                        .mget({"num0", "num1"})
                        .exec();

    // Parse reply with reply type and index.
    auto incr_result0 = tx_replies.get<long long>(0);

    auto incr_result1 = tx_replies.get<long long>(1);

    std::vector<OptionalString> mget_cmd_result;
    tx_replies.get(2, back_inserter(mget_cmd_result));

哎?这玩意用着和管道怎么这么像,那事务和管道有什么区别呢?

我不知道!

看下一趴吧

    // There's no *Redis::client_getname* interface.
    // But you can use *Redis::command* to get the client name.
    val = redis.command<OptionalString>("client", "getname");
    if (val) {
        std::cout << *val << std::endl;
    }

    // Same as above.
    auto getname_cmd_str = {"client", "getname"};
    val = redis.command<OptionalString>(getname_cmd_str.begin(), getname_cmd_str.end());

    // There's no *Redis::sort* interface.
    // But you can use *Redis::command* to send sort the list.
    std::vector<std::string> sorted_list;
    redis.command("sort", "list", "ALPHA", std::back_inserter(sorted_list));

    // Another *Redis::command* to do the same work.
    auto sort_cmd_str = {"sort", "list", "ALPHA"};
    redis.command(sort_cmd_str.begin(), sort_cmd_str.end(), std::back_inserter(sorted_list));

上文为通用命令的使用方法,在 Redis 客户端库的使用场景中,通用命令接口(Generic Command Interface)是一种允许用户以灵活、通用的方式执行 Redis 命令的机制。通常,客户端库会为一些常见的 Redis 命令(如 GETSETHSET 等)提供专门的方法。然而,Redis 支持的命令众多,客户端库不可能为每一个命令都提供特定的封装方法。通用命令接口就是为解决这一问题而设计的,它允许用户直接发送任意的 Redis 命令,而不依赖于库中预定义的特定方法。
我么可以使用 command() 函数在传参中传入我们要执行的命令,并且可以通过两种方式获得命令执行的结果。
第一种:在 command 函数后添加尖括号,里面为返回值类型,通过返回值获得执行结果。
第二种:在 command 函数添加最后一个参数为插入迭代器,把执行结果存入到容器中。

以上是本期的全部内容,下周同一时间期待在此与大家见面。

### 配置Visual Studio Code通过WSL使用GDB调试xv6 #### 安装必要的工具和扩展 为了能够在 Visual Studio Code 中利用 WSL 调试 xv6,需要安装一些必备组件。确保已经安装了适用于 Windows 的 WSL 和 Ubuntu 发行版,并且在 VSCode 中安装了 Remote - WSL 扩展[^2]。 #### 设置开发环境 由于项目采用 Makefile 构建而非 CMake,建议直接在 WSL 下操作源码并进行编译工作。可以通过 Git 将仓库克隆到本地 Linux 文件系统中以便于后续处理。 #### 修改启动配置文件 对于 `.gdbinit` 文件中的设置,在 `target remote 127.0.0.1` 前面加上 `#` 符号来注释该行可以防止 GDB 自动尝试连接远程目标设备,这一步骤有助于更好地控制调试过程[^1]。 #### 编写 launch.json 创建或编辑位于项目根目录下的 `.vscode/launch.json` 文件,添加如下 JSON 片段用于定义调试会话: ```json { "version": "0.2.0", "configurations": [ { "name": "(gdb) Launch", "type": "cppdbg", "request": "launch", "program": "${workspaceFolder}/build/xv6.img", // 根据实际情况调整路径 "args": [], "stopAtEntry": false, "cwd": "${workspaceFolder}", "environment": [], "externalConsole": true, "MIMode": "gdb", "setupCommands": [ { "description": "Enable pretty-printing for gdb", "text": "-enable-pretty-printing", "ignoreFailures": true } ], "preLaunchTask": "Build XV6" } ] } ``` 此配置指定了当发起调试请求时应使用的参数以及如何调用 GDB 进行调试。 #### 创建任务脚本 还需编写一个简单的任务描述符(tasks.json),它负责告诉 IDE 如何执行 make 来构建项目。同样放置于 .vscode 文件夹内: ```json { "version": "2.0.0", "tasks": [ { "label": "Build XV6", "type": "shell", "command": "make", "group": { "kind": "build", "isDefault": true }, "problemMatcher": ["$gcc"], "detail": "Generated task to build the project using 'make'" } ] } ``` 上述设定允许一键触发项目的编译流程,简化了开发者的工作流。 完成以上步骤之后就可以顺利地基于 WSL 使用 GDB 对 xv6 操作系统展开深入研究与调试了。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值