hiredis使用错误分析总结

hiredis是redis的官方C API,但其API使用需要有一些注意的地方。

hiredis提供了一个名为redisCommand的API用于向redis 服务器发送命令, redisCommand的函数原型如下:

void *redisCommand(redisContext *c, const char *format, ...)

这个函数参数外观和printf的可变参数机制相同,如果使用时认为和printf的可变参数机制 一样,会被这个问题绊住,花费很长时间去调试。我就遇到了这个问题。

这个可变机制,是用于需要格式化的value值,而不能用于key的格式化。

比如我有一个key value需要设置,比如用户信息表,包含4个字段,分别为username, MobilePhone, HomePhone, eMail。 除了用户名都是可选字段。我们只能选择hash存储, 因为选择key/value形式需要预先将先前设置的值取出来,再进行设置,否则会冲掉之前 的设置。 hash存储有两种写入数据库有两种方法,分别为HSET和HMSET

127.0.0.1:6379> help HSET

HSET key field value summary: Set the string value of a hash field since: 2.0.0 group: hash

127.0.0.1:6379> help HMSET

HMSET key field value [field value …] summary: Set multiple hash fields to multiple values since: 2.0.0 group: hash

先来了一个数据,每个字段都有值,username, MobilePhone, HomePhone, eMail。那我采用HMSET 来入库,调用的代码如下:

redisCommand(c, "HMSET %s mobile %s home %s email %s", username, MobilePhone,
                HomePhone, eMail)

下一次这个用户的某几个字段有修改。我也调用该函数进行,但为了不覆盖之前的设置。 我对先对参数进行拼接,然后传递给redisCommand。拼接后字符串为cmd

std::string cmd = "HMSET aaabbb phone 123 home 456"
reply = redisCommand(c, "%s", cmd.c_str() );

这是就遇到了问题,错误出现了,“HMSET: ERR unknown command ‘HMSET aaabbb phone 123 home 456’” redisContext对象返回设置的errstr设置为错误的参数个数。

这个问题经过分析是hiredis的接口对参数进行了编码转换,该函数实现中,当发现百分号(%)和空格时 的时候,会对空格进行参数累加。

总结错误用法如下:

reply = redisCommand(c, "%s", "HMSET aaabbb phone 123 home 456" )
//HMSET: ERR unknown command 'HMSET aaabbb phone 123 home 456'
消息内容为:
*1
$31
HMSET aaabbb phone 123 home 456
-ERR unknown command 'HMSET aaabbb phone 123 home 456'

reply = redisCommand(c, "HMSET %s", "aaabbb phone 123 home 456" )
//HMSET: ERR wrong number of arguments for 'hmset' command

reply = redisCommand(c, "HMSET aaabbb %s", "phone 123 home 456" )
//HMSET: ERR wrong number of arguments for 'hmset' command

只能按照每一个参数一个百分号的形式进行格式化,或者完全进行格式化形式进行。

正确用法如下:

reply = redisCommand(c,"HMSET aaabbb phone %s home %s", "123", "456" );
reply = redisCommand(c,"HMSET aaabbb phone 123 home 456" );
reply = redisCommand(c,"HMSET %s phone %s home %s", "aaabbb", "123", "456" );

拼接之后的字符串为:

*6
$5
HMSET
$6
aaabbb
$5
phone
$3
123
$4
home
$3
456

*6 表示六个参数分割,$5表示后面的字符长度为5, $6表示后面字符长度为6, 每一个参数都是先传递长度,再传递实际内容,并且以回车换行分割。这也是 使用redis-cli时输入以下命令对服务器发起的请求字符串:

127.0.0.1:6379> HMSET aaabbb phone 123 home 456,

返回值为:

+OK

总结如下,在使用redisCommand函数时,需要遵守其用法,不能预先拼接好, 而是使用空格来分割各部分。