处理命令回复

使用 hiredis 处理命令回复。

redisCommand()redisCommandArgv() 函数在发出命令时返回指向 redisReply 对象的指针(详见发出命令)。此类型支持 RESP2 和 RESP3 协议中定义的所有回复格式,因此其内容在不同调用之间差异很大。

一个简单的例子是 SET 命令返回的状态回复。下面的代码展示了如何从 redisReply 对象中获取它。

redisReply *reply = redisCommand(c, "SET greeting Hello");

// Check and free the reply.
if (reply != NULL) {
    printf("Reply: %s\n", reply->str);
    freeReplyObject(reply);
    reply = NULL;
}

null 回复表示错误,因此应始终进行检查。如果发生错误,则 redisContext 对象的整型 err 字段将包含非零错误码,其 errstr 字段将包含错误的文本描述。

对于 SET 命令,成功调用将简单地返回一个“OK”字符串,您可以使用 reply->str 字段访问它。示例中的代码将其打印到控制台,但您应检查特定值以确保命令正确执行。

redisCommand() 调用为回复分配内存,因此在使用完回复后应始终使用 freeReplyObject() 释放它。如果想重复使用回复变量,最好在释放后将其设置为 NULL,以避免之后意外使用陈旧的指针。

回复格式

Redis 的 RESP 协议支持多种不同的命令回复格式。

您可以在命令参考页面的末尾的 RESP2/RESP3 回复部分找到命令的回复格式(例如,INCRBY 页面显示该命令返回一个整数结果)。您也可以使用回复对象的 type 字段来确定格式。此字段包含针对每种类型的不同整数值。hiredis.h 头文件为所有这些整数值定义了常量(例如 REDIS_REPLY_STRING)。

redisReply 结构体有多个字段用于包含不同类型的回复,根据 type 字段的值设置不同的字段。下表显示了类型常量、相应的回复类型以及可用于访问回复值的字段。

常量 类型 redisReply 的相关字段 RESP 协议
REDIS_REPLY_STATUS 简单字符串 reply->str:字符串值 (char*)
reply->len:字符串长度 (size_t)
2, 3
REDIS_REPLY_ERROR 简单错误 reply->str:字符串值 (char*)
reply->len:字符串长度 (size_t)
2, 3
REDIS_REPLY_INTEGER 整型 reply->integer:整型值 (long long) 2, 3
REDIS_REPLY_NIL Null 无数据 2, 3
REDIS_REPLY_STRING 批量字符串 reply->str:字符串值 (char*)
reply->len:字符串长度 (size_t)
2, 3
REDIS_REPLY_ARRAY 数组 reply->elements:元素数量 (size_t)
reply->element:数组元素 (redisReply)
2, 3
REDIS_REPLY_DOUBLE 双精度浮点数 reply->str:作为字符串的双精度浮点数值 (char*)
reply->len:字符串长度 (size_t)
3
REDIS_REPLY_BOOL 布尔值 reply->integer:布尔值,0 或 1 (long long) 3
REDIS_REPLY_MAP Map reply->elements:元素数量 (size_t)
reply->element:数组元素 (redisReply)
3
REDIS_REPLY_SET Set reply->elements:元素数量 (size_t)
reply->element:数组元素 (redisReply)
3
REDIS_REPLY_PUSH Push reply->elements:元素数量 (size_t)
reply->element:数组元素 (redisReply)
3
REDIS_REPLY_BIGNUM 大整数 reply->str:作为字符串的数值 (char*)
reply->len:字符串长度 (size_t)
3
REDIS_REPLY_VERB 原样字符串 reply->str:字符串值 (char*)
reply->len:字符串长度 (size_t)
reply->vtype:内容类型 (char[3])
3

回复格式处理示例

以下部分将更详细地解释如何处理特定的回复类型。

整型

REDIS_REPLY_INTEGERREDIS_REPLY_BOOL 回复类型都将值包含在 reply->integer 中。但是,REDIS_REPLY_BOOL 很少使用。即使命令本质上返回一个布尔值,回复通常也会报告为整型。

// Add some values to a set.
redisReply *reply = redisCommand(c, "SADD items bread milk peas");

if (reply->type == REDIS_REPLY_INTEGER) {
    // Report status.
    printf("Integer reply\n");
    printf("Number added: %lld\n", reply->integer);
    // >>> Number added: 3
}

freeReplyObject(reply);
reply = NULL;


reply = redisCommand(c, "SISMEMBER items bread");

// This also gives an integer reply but you should interpret
// it as a boolean value.
if (reply->type == REDIS_REPLY_INTEGER) {
    // Respond to boolean integer value.
    printf("Integer reply\n");
    
    if (reply->integer == 0) {
        printf("Items set has no member 'bread'\n");
    } else {
        printf("'Bread' is a member of items set\n");
    }
    // >>> 'Bread' is a member of items set
}

freeReplyObject(reply);
reply = NULL;

字符串

REDIS_REPLY_STATUSREDIS_REPLY_ERRORREDIS_REPLY_STRINGREDIS_REPLY_DOUBLEREDIS_REPLY_BIGNUMREDIS_REPLY_VERB 都以字符串形式返回,主要区别在于如何解释它们。对于所有这些类型,字符串值都在 reply->str 中返回,字符串长度在 reply->len 中。下面的示例展示了一些可能性。

// Set a numeric value in a string.
reply = redisCommand(c, "SET number 1.5");

// This gives a status reply.
if (reply->type == REDIS_REPLY_STATUS) {
    // Report status.
    printf("Status reply\n");
    printf("Reply: %s\n", reply->str); // >>> Reply: OK
}

freeReplyObject(reply);
reply = NULL;


// Attempt to interpret the key as a hash.
reply = redisCommand(c, "HGET number field1");

// This gives an error reply.
if (reply->type == REDIS_REPLY_ERROR) {
    // Report the error.
    printf("Error reply\n");
    printf("Reply: %s\n", reply->str);
    // >>> Reply: WRONGTYPE Operation against a key holding the wrong kind of value
}

freeReplyObject(reply);
reply = NULL;


reply = redisCommand(c, "GET number");

// This gives a simple string reply.
if (reply->type == REDIS_REPLY_STRING) {
    // Display the string.
    printf("Simple string reply\n");
    printf("Reply: %s\n", reply->str); // >>> Reply: 1.5
}

freeReplyObject(reply);
reply = NULL;


reply = redisCommand(c, "ZADD prices 1.75 bread 5.99 beer");

// This gives an integer reply.
if (reply->type == REDIS_REPLY_INTEGER) {
    // Display the integer.
    printf("Integer reply\n");
    printf("Number added: %lld\n", reply->integer);
    // >>> Number added: 2
}

freeReplyObject(reply);
reply = NULL;


reply = redisCommand(c, "ZSCORE prices bread");

// This gives a string reply with RESP2 and a double reply
// with RESP3, but you handle it the same way in either case.
if (reply->type == REDIS_REPLY_STRING) {
    printf("String reply\n");
    
    char *endptr; // Not used.
    double price = strtod(reply->str, &endptr);
    double discounted = price * 0.75;
    printf("Discounted price: %.2f\n", discounted);
    // >>> Discounted price: 1.31
}

freeReplyObject(reply);
reply = NULL;

数组和 Map

数组(回复类型 REDIS_REPLY_ARRAY)和 Map(回复类型 REDIS_REPLY_MAP)由同时检索多个值的命令返回。对于这两种类型,回复中的元素数量包含在 reply->elements 中,指向数组本身的指针是 reply->element。数组中的每个项都是 redisReply 类型。数组元素通常是简单类型,而不是数组或 Map。

下面的示例展示了如何从 列表中 获取项。

reply = redisCommand(c, "RPUSH things thing0 thing1 thing2 thing3");

printf("Added %lld items\n", reply->integer);
// >>> Added 4 items
freeReplyObject(reply);
reply = NULL;


reply = redisCommand(c, "LRANGE things 0 -1");

for (int i = 0; i < reply->elements; ++i) {
    if (reply->element[i]->type == REDIS_REPLY_STRING) {
        printf("List item %d: %s\n", i, reply->element[i]->str);
    }
}
// >>> List item 0: thing0
// >>> List item 1: thing1
// >>> List item 2: thing2
// >>> List item 3: thing3

Map 本质上与数组相同,但额外保证项将以键值对的形式列出。下面的示例展示了如何使用 HGETALL哈希 中获取所有字段。

const char *hashCommand[] = {
    "HSET", "details",
    "name", "Mr Benn",
    "address", "52 Festive Road",
    "hobbies", "Cosplay"
};

reply = redisCommandArgv(c, 8, hashCommand, NULL);

printf("Added %lld fields\n", reply->integer);
// >>> Added 3 fields

freeReplyObject(reply);
reply = NULL;


reply = redisCommand(c, "HGETALL details");

// This gives an array reply with RESP2 and a map reply with
// RESP3, but you handle it the same way in either case.
if (reply->type == REDIS_REPLY_ARRAY) {        
    for (int i = 0; i < reply->elements; i += 2) {
        char *key = reply->element[i]->str;
        char *value = reply->element[i + 1]->str;
        printf("Key: %s, value: %s\n", key, value);
    }
    // >>> Key: name, value: Mr Benn
    // >>> Key: address, value: 52 Festive Road
    // >>> Key: hobbies, value: Cosplay
}

错误处理

当命令成功执行时,上下文对象的 err 字段将被设置为零。如果命令失败,它将返回 NULLREDIS_ERR,具体取决于您使用的函数和命令。发生这种情况时,context->err 将包含错误码。

  • REDIS_ERR_IO:在创建连接或尝试写入或读取数据时发生 I/O 错误。无论何时 context->err 包含 REDIS_ERR_IO,您都可以使用标准库文件 errno.h 的功能来查找有关错误的更多信息。
  • REDIS_ERR_EOF:服务器关闭了连接,导致读取为空。
  • REDIS_ERR_PROTOCOL:解析 RESP 协议时发生错误。
  • REDIS_ERR_OTHER:任何其他错误。目前仅在无法解析连接主机名时使用。

上下文对象还有一个 errstr 字段,其中包含描述性错误消息。

为此页面评分
返回顶部 ↑