目录

文章目录

  • 目录
  • Redis 客户端
  • HIREDIS
    • 安装
    • 验证
  • Synchronous API
    • redisConnect
    • redisCommand
    • freeReplyObject
    • redisFree
  • Asynchronous API
    • redisAsyncConnect
    • Sending commands and their callbacks
    • redisAsyncDisconnect
  • Pipelining
  • Errors

Redis 客户端

Redis 拥有几乎所有主流编程语言的客户端(https://redis.io/clients),其中 C 语言的客户端推荐使用:

  • hiredis:https://github.com/redis/hiredis
  • hiredis-vip:https://github.com/vipshop/hiredis-vip
  • hiredispool:https://github.com/aclisp/hiredispool

HIREDIS

hiredis 是一个轻量级的 C 语言编程客户端。它非常的简洁,仅仅提供了对 redis 通信协议的最小支持。同时它使用了一个高级别的 printf-like API,所以对于习惯了 printf 风格的 C 编程用户来说,其非常容易使用,而且 API 中没有明确的绑定每个 Redis 命令。

安装

$ git clone https://github.com/redis/hiredis.git
$ cd hiredis
$ make
$ make install

安装完成后 hiredis 的静态链接库和动态链接库都会存放到 /usr/local/lib/ 目录下:

$ ll /usr/local/lib/
总用量 716
-rw-r--r-- 1 root root 459182 6月  17 15:52 libhiredis.a
lrwxrwxrwx 1 root root     18 6月  17 15:52 libhiredis.so -> libhiredis.so.0.14
-rwxr-xr-x 1 root root 269776 6月  17 15:52 libhiredis.so.0.14

也可以将库文件拷贝到系统目录下:

$ cp libhiredis.so /usr/lib64 /usr/lib
$ /sbin/ldconfig

验证

#include <stdio.h>
#include <hiredis/hiredis.h>int main() {redisContext *conn = redisConnect("127.0.0.1", 6379);if (conn == NULL || conn->err) {if (conn) {printf("Error: %s\n", conn->errstr);} else {printf("Can't allocate redis context\n");}}redisReply *reply = redisCommand(conn, "set foo 123");freeReplyObject(reply);reply = redisCommand(conn, "get foo");printf("foo: %s\n", reply->str);freeReplyObject(reply);redisFree(conn);return 0;
}

编译运行:

$ gcc test.c -o test -lhiredis
$  ./test
foo: 123

Synchronous API

使用 hiredis 的 Synchronous API,需要掌握以下函数原型。

redisConnect

redisContext *redisConnect(const char *ip, int port)

指针函数,输入 Redis Server 的 IP:Port,返回 redisContext 结构体类型指针。

redisContext 结构体用于保存连接状态,包含了一个 Int 类型的结构体成员 err。当 err 非空时,表示连接处于一个错误状态,此时可以通过查看 String 类型的 errstr 成员来获取错误信息。所以在调用 redisConnect() 后应该检查 redisContext 结构体的 err 成员来判断连接是否成功。

typedef struct redisContext {int err;              /* Error flags, 0 when there is no error */char errstr[128];     /* String representation of error when applicable */int fd;int flags;char *obuf;           /* Write buffer */redisReader *reader;  /* Protocol reader */enum redisConnectionType connection_type;struct timeval *timeout;struct {char *host;char *source_addr;int port;} tcp;struct {char *path;} unix_sock;
} redisContext;

NOTE:redisContext 是线程非安全的,也就是说,多个线程同时访问一个 redisContext 很可能会出现问题。

redisCommand

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

空类型指针函数,具有可变长形参,输入 redisContext 结构体类型指针、Redis SQL 语句字符串。返回值为空类型指针,可以强制类型转换为任意类型,一般强制转换成为 redisReply 类型,然后对其进行后续处理,例如:查看执行结果或执行错误的结果。

redisCommand() 是一个 printf-like API,向 Redis Server 发送指令。

  • 最简单的形式:
reply = redisCommand(context, "SET foo bar");
  • 使用 %s 插入字符串的形式:
reply = redisCommand(context, "SET foo %s", value);
  • 发送多个分离的字符串的形式:
reply = redisCommand(context, "SET %s %s", key, value);
  • 发送二进制字符串的形式,需要指出字符串的长度:
reply = redisCommand(context, "SET foo %b", (size_t)valuelen);
  • 类似于命令行的形式:

    • argc 存放命令参数的个数,例如:argc = 3
    • argv 存放每个命令参数的指针,例如:argv = {"set", "foo", "bar"}
    • argvlen 参数是每个参数字符串的长度,函数会根据字符串长度来决定字符串的终止,而不是 ‘\0’。例如 set foo bar 的 argvlen == {3, 3, 3}。如果 argvlen 为空,那么函数内部会自动调用 strlen() 求每个参数的长度。
void *redisCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen);// EXAMPLE:
char hkey[] = "123456";
char hset[] = "hset";
char key[] = "testkey";
char hvalue[] = "3210";
int argc = 4;
char *argv[] = {hset,key,hkey,hvalue};
size_t argvlen[] = {4,6,4,3};
redisCommandArgv(context,argc,argv,argvlen);

当 redisCommand() 调用失败时,返回为 NULL,同时设置 redisContext 结构体的 err 成员。一旦错误返回,redisContext 不能被重用,此时应该建立一个新的连接。

当 redisCommand() 成功调用时,返回的 redisReply 类型指针。

/* This is the reply object returned by redisCommand() */
typedef struct redisReply {int type;                    /* REDIS_REPLY_* 命令执行结果的返回类型 */long long integer;           /* The integer when type is REDIS_REPLY_INTEGER 存储执行结果返回为整数 */size_t len;                  /* Length of string 字符串值的长度 */char *str;                   /* Used for both REDIS_REPLY_ERROR and REDIS_REPLY_STRING 存储命令执行结果返回是字符串 */size_t elements;             /* number of elements, for REDIS_REPLY_ARRAY 返回结果是数组的大小 */struct redisReply **element; /* elements vector for REDIS_REPLY_ARRAY 存储执行结果返回是数组 */
} redisReply;

redisReply 结构体的 type 成员应该用于检查 Reply 类型,包括:

  • REDIS_REPLY_STRING == 1:字符串回复。返回值是字符串,字符串数值为 redis->str,字符串长度为 redis->len
  • REDIS_REPLY_ARRAY == 2:批量操作回复。返回值是数组,数组大小为 redis->elements,数组值元素为 redis->element[i],每个元素都是一个 type==REDIS_REPLY_STRING 的 redisReply 结构体类型指针。元素的返回值可以通过 redis->element[i]->str 访问。
  • REDIS_REPLY_INTEGER == 3:整型答复。返回值为 long long int,数值为 reply->integer
  • REDIS_REPLY_NIL == 4:零(nil)对象回复。表示执行结果为空,没有可以访问的数据。
  • REDIS_REPLY_STATUS == 5:状态回复。返回命令执行的状态,状态信息为 reply->str,状态字符串长度为 reply->len。比如:set foo bar 返回的状态为 OK,则 reply->str 为 “OK”。
  • REDIS_REPLY_ERROR == 6 :错误回复。命令执行错误,错误信息存放在 reply->str 当中。

freeReplyObject

void freeReplyObject(void *reply);

空类型函数,输入 redisCommand() 返回的 redisReply 指针变量,并释放 redisReply 指针变量所占用的内存。

在代码中应该使用 freeReplyObject() 来释放 Reply 占用的内存。注意,freeReplyObject() 会递归的释放数组中的资源,不需要手动释放数组资源。

redisFree

void redisFree(redisContext *c);

空类型函数,输入 redisConnect() 返回的 conn 指针变量。该 API 会断开 Redis Server 的连接(关闭 Socket)并释放 redisContext 结构体所占用的内存。

Asynchronous API

同步调用简单,而异步调用的性能更高。hiredis 的 Asynchronous API 可以简易的与一些基于事件(Event)的函数库结合使用,例如 libev 和 ibevent。

redisAsyncConnect

redisAsyncConnect() 用于与 Redis Server 建立非阻塞连接,返回 redisAsyncContext 结构体类型指针,结构体的 err 成员用来检查连接的过程中是否发生了错误。注意,因为创建的是非阻塞的连接,所以 Kernel 并不会立马返回连接的结果。

redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379);
if (c->err) {printf("Error: %s\n", c->errstr);// handle error
}

同样,redisAsyncContext 也是线程非安全的。

Sending commands and their callbacks

在异步调用场景中,Redis 的操作命令被加入到事件循环队列,当命令执行完后再调用相应的回调函数来获得执行结果。回调函数的原型为:

void(redisAsyncContext *c, void *reply, void *privdata);
  • privdata:是由调用者自己定义的数据类型。

以下是进行异步命令操作的函数:

int redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *format, ...);
int redisAsyncCommandArgv(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, int argc, const char **argv, const size_t *argvlen);

与同步接口调用类似。执行成功返回 REDIS_OK,否则返回 REDIS_ERR。比如:在连接已经关闭的情况下再调用 redisAsyncCommand() 就会返回 REDIS_ERR。回调执行完毕后如果 reply 不为空,那么回调执行完毕后将自动对 reply 的资源进行回收。而当 Context 发生错误时,回调得到的 reply 则为空。

redisAsyncDisconnect

一个异步的连接可以通过下面这个函数终止:

void redisAsyncDisconnect(redisAsyncContext *ac);

当这个函数被调用时,异步连接并不会被立即关闭,而是等待所有与这个连接关联的异步命令操作执行完毕,并且回调事件已经执行完毕后才关闭此连接,这时在响应关闭连接事件的回调函数中得到的状态为 REDIS_OK,此连接的资源也将会被自动回收。

异步的 Context 可设置一个响应断开连接事件的回调函数,当连接断开时会相应执行。回调函数的原型为:

void(const redisAsyncContext *c, int status);

在断开连接的情况下,当连接是由用户自己断开的,那么 status 参数为 REDIS_OK。如果出现了其他错误,则 status 参数为 REDIS_ERR,此时可以通过 err 成员得到准确的错误码。

一个异步 Context 仅能设置一次断开连接的回调,如果再进行下一次设置将会返回 REDIS_ERR。设置断开连接回调函数的原型为:

int redisAsyncSetDisconnectCallback(redisAsyncContext *ac, redisDisconnectCallback *fn);

当回调执行完毕后 Context 会自己释放资源。该回调事件给创建一个新连接提供了便利。

Pipelining

当 redisCommand 系列的 API 被调用时,hiredis 首先会根据 Redis 协议格式化该 API 传入的命令。然后将格式化的命令放入 redisContext 的输出缓冲区(output buffer)中。输出缓冲区是动态的,所以它可以容纳任意数量的命令。将命令放入输出缓冲区后,可以调用redisGetReply() 来获取执行命令的结果。redisGetReply() 有以下两种执行场景:

  • 输入缓冲区(input buffer)为空:

    • 尝试解析来自输入缓冲区的单个回复(Reply)并将其返回。
    • 如果没有回复可以被解析,则进入第 2 种场景。
  • 输入缓冲区不为空:

    • 将输出缓冲区的内容写入套接字,传输到 Redis Server。
    • 轮询从套接字读取,直到可以解析单个回复。

对于序列化的命令,唯一需要做的事情就是将其填充到输出缓冲区。由于这个原因,hiredis 还可以使用 redisAppendCommand 来发送命令,并使用 redisGetReply 获取命令的单个回复。

void redisAppendCommand(redisContext *c, const char *format, ...);
void redisAppendCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen);

redisGetReply() 返回值为 REDIS_OK 或 REDIS_ERR,是一个 int 类型。后者表示读取回复时出错,同样可以通过 err 成员获取到错误信息。

hiredis Pipelining 的使用方法如下:

redisReply *reply;
redisAppendCommand(context,"SET foo bar");
redisAppendCommand(context,"GET foo");
redisGetReply(context,(void *)&reply); // reply for SET
freeReplyObject(reply);
redisGetReply(context,(void *)&reply); // reply for GET
freeReplyObject(reply);

该 APIs 还可以实现 Redis 的发布订阅机制(blocking subscriber):

reply = redisCommand(context, "SUBSCRIBE foo");
freeReplyObject(reply);
while(redisGetReply(context, (void *)&reply) == REDIS_OK) {// consume messagefreeReplyObject(reply);
}

Errors

当 hiredis 提供的 API(函数)调用不成功时,会返回 NULL 或 REDIS_ERR。此时 redisContext 结构体的 err 成员为非 0,并设置为以下常量之一:

  • REDIS_ERR_IO:创建连接时发生了 I/O 错误(尝试写入套接字或从套接字读取)。此时可以在程序中 include errno.h,并使用使用全局变量 errno 来查询错误。
  • REDIS_ERR_EOF:Redis Server 已经关闭导致的错误。
  • REDIS_ERR_PROTOCOL:解析 redis 协议时发生错误。
  • REDIS_ERR_OTHER:任何其他的错误。

以上错误,都可以通过 redisContext 结构体的 errstr 成员来查看错误信息。

hiredis — Redis 的 C 语言客户端相关推荐

  1. 基于redis和R语言构建并行计算平台(yiyou)

    最近研究gearman时发现不少问题,关于队列持久化的问题搞了半个月还是没能解决,并且国内可以参考的资料太少,所以考虑换一种方案试试.如下贴出gearman集群的架构: 可以看到该架构存在的问题,当持 ...

  2. redis数据库c语言接口

    redis数据库拥有方便快捷的c语言接口,下面我将用程序操作redis数据库. 首先redis的c语言接口API是hiredis,下载地址为https://github.com/redis/hired ...

  3. 带你100% 地了解 Redis 6.0 的客户端缓存

    点击上方"方志朋",选择"设为星标" 回复"666"获取新整理的面试文章 近日 Redis 6.0.0 GA 版本发布,这是 Redis 历 ...

  4. 十九、Redis 6.0 的客户端缓存

    一.为什么需要客户端缓存? 我们都知道,使用 Redis 进行数据的缓存的主要目的是减少对 MySQL 等数据库的访问,提供更快的访问速度,毕竟 <Redis in Action> 中提到 ...

  5. CAT 3.0 开源发布,支持多语言客户端及多项性能提升

    项目背景 CAT(Central Application Tracking),是美团点评基于 Java 开发的一套开源的分布式实时监控系统.美团点评基础架构部希望在基础存储.高性能通信.大规模在线访问 ...

  6. Redis 6.0 的客户端缓存是怎么肥事?一文带你了解!

    来源 | 程序员历小冰 责编 | Carol 封图 | CSDN 付费下载于视觉中国 近日 Redis 6.0.0 GA 版本发布,这是 Redis 历史上最大的一次版本更新,包括了客户端缓存 (Cl ...

  7. Rserve的R语言客户端RSclient

    RSclient是实现Rserve通信的R语言客户端程序,对于统计人员使用RSclient调用Rserve运行R语言脚本,感觉会很奇怪.但对于实际应用架构来说却是很有帮助的,不仅可以统一Rserve的 ...

  8. Docker安装Redis,并且使用外部客户端链接

    本篇博客主要记录在centos7当中安装Redis,并且安装完成之后使用外部客户端链接. 目录 一.查看docker环境是否正常 二.下载Redis的镜像 三.查看Redis镜像是否下载成功 四.创建 ...

  9. 客户端华为p9调用摄像头出现空指针_带你 100% 了解 Redis 6.0 的客户端缓存

    文章来源:https://mp.weixin.qq.com/s/DFgygoDcXeJXjbjPt7BZiQ 原文作者:历小冰 近日 Redis 6.0.0 GA 版本发布,这是 Redis 历史上最 ...

最新文章

  1. c++ vector 赋值_Vector 源码剖析
  2. 因为犯罪被判三年刑,期间没办法还信用卡,银行会怎么做?
  3. mysql qps如何查看_一款查看mysql QPS的脚本
  4. js获取元素的方法与属性
  5. C++ 11 中的右值引用
  6. Java 并发数据结构
  7. mysql显示错误代码1067_每日一记--Mysql错误代码1067
  8. android蓝牙5.0扫描失败,bluetooth-lowenergy – BLE扫描的解决方案SCAN_FAILED_APPLICATION_REGISTRATION_FAILED?...
  9. 帝国网站mysql 数据库开发_帝国cms操作数据库函数范例(二次开发)
  10. 集合例题3.:现在有一个map集合如下:Map<Integer,String> map = new HashMap<Integer, String>();map.put(1, “张三丰“);map.
  11. 任正非带领华为三分天下的7大杀招
  12. Java程序员工作三年以内
  13. Ubuntu下vim如何保存退出
  14. 刀片机服务器虚拟化方案,IBM刀片服务器虚拟化方案
  15. 渗透测试之XSS(跨站脚本攻击)
  16. Linux3.x——USB Gadget HID keyboard + Mass storage
  17. 甲方安全之仿真钓鱼演练(邮件+网站钓鱼)
  18. Android系统适配蓝牙遥控器键值Hi3798MV100
  19. 2019年专利代理师资格考试报名
  20. 企微客户群都有哪些独特优势?

热门文章

  1. 分享Kali Linux 2016.2第41周镜像虚拟机
  2. suse mysql root密码忘记_SUSE11.4 找回 mysql root 密码?网上能找到的所有方法都试过了,不行......
  3. delphi中的函数传参如何传枚举参数_shell脚本的函数介绍使用和工作常用案例。建议收藏...
  4. 内存屏障linux,Linux内存屏障
  5. html region 折叠,js代码折叠的方法//#region 代码 //#endregion
  6. 高级计算机网络知识点复习
  7. 含有多个java程序的文件夹导入MyEclipes 出现错误的解决办法
  8. 上海交大情感脑电数据集(SEED)简介
  9. 高度可扩展的类脑神经拟态硬件,完成了字母识别和人脸识别
  10. Science:人类在实验室创建了微型“大脑”,含祖先基因的那种