浅谈c++中的开源框架类目--分享四(数据库)
这个系列Lee哥会分享一些c++中的开源框架。
主要从TCP协议栈、框架、并发性、数据库、国际化、压缩、日志、多媒体库、序列化、XML库、脚本、Json库、数学库、安全、WEB应用框架、网络库、异步事件等方面来分享。分享的内容不仅限于自己来写,有会借鉴网上大牛们的一些文章与大家分享。
之前我们讲了TCP、并发性和框架,今天我们来讲一讲数据库。
1.redis数据库的C客户端库hiredis
2.Facebook嵌入键的快速存储rocksDB
3.用于Sqite3的C++对象关系印射hiberlite
Redis C语言客户端库hiredis使用方法
原文转载于:https://blog.csdn.net/imxiangzi/article/details/52426070
本文偏重于应用,需要起码的redis基础知识。
第一部分
第1讲 Hiredis介绍
Hiredis是redis 数据库 一个轻量的C语言客户端库。
之所以轻量,是由于它只是简单的提供了对redis操作语句支持的接口,并没有实现具体的操作语句的功能。但正是由于这种设计,使得我们只要熟悉了通用的redis操作语句,就可以很容易的使用该库和redis数据库进行交互。
除了支持发送命令和接收应答/应答数据,它提供了对应答数据的解析操作。而且这个基于I/O层的数据流解析操作设计考虑到了复用性,可以对应答数据进行通用的解析操作。
Hirides仅仅支持二进制安全的redis协议,所以你只能针对版本号大于等于1.2.0的redis服务端使用。
Redis C语言客户端库hiredis包含多种API,包括同步命令操作API、异步命令操作API和对应答数据进行解析的API。
关于升级: 版本0.9.0是hiredis很多特性一次大的更新,但是对现有代码进行升级应该不会造成大的问题。升级时,要记住的关键一点是大于等于0.9.0的版本是使用redisContext*来保持连接状态,之前的版本只是使用了无状态的文件描述符。
第二部分 同步API
常用的同步API主要有:
redisContext *redisConnect(const char *ip, int port);
void *redisCommand(redisContext *c, const char *format, ...);
void freeReplyObject(void *reply);
第1讲 连接redis数据库
函数redisConnect被用来创建一个 redisContext。这个context是hiredis持有的连接状态。redisConnect结构体有一个整型的err变量来标识连接错误码,如果连接错误则为非零值。变量errstr标识连接结果的文字描述。更多这方面的信息会在以下Errors章节说明。当你使用 redisConnect 来创建连接时应该检查err变量来判断是否连接正常建立。
redisContext *c = redisConnect("127.0.0.1", 6379);
if (c != NULL && c->err) {
printf("Error: %s\n", c->errstr); // handle error }
第2讲 发送命令到redis
有多种方法可以发送命令到redis。
首先介绍redisCommand。此函数类似于printf的使用方式,如
reply = redisCommand(context, "SET foo bar");
类似于printf的s%格式化方式,如
reply = redisCommand(context, "SET foo %s", value);
当你需要发送二进制安全的命令时,可以采用%b的格式化方式,同时需要一个 字符串指针 和 size_t类型的字符串长度参数 ,如下
reply = redisCommand(context, "SET foo %b", value, (size_t) valuelen);
在API内部,Hiredis根据不同的参数分割命令,将其转化为“操作redis数据库的标准命令”,你可以格式化多个参数来构造redis的命令,如下
reply = redisCommand(context, "SET key:%s %s", myid, value);
第3讲 处理redis应答
当命令被成功执行后,redisCommand会有相应的返回值。如果有错误发生,返回值为NULL或者redisReply结构体中的err变量将会被设置成相应的值(请参照Errors章节)。一旦有错误发生,context不能被重用,并且你需要建立一个新的连接。
redisCommand执行后返回值类型为redisReply。通过redisReply结构体中的type变量,可以确定命令执行的情况。
-
REDIS_REPLY_STATUS:
- 返回执行结果为状态的命令。比如set命令的返回值的类型是REDIS_REPLY_STATUS,然后只有当返回信息是"OK"时,才表示该命令执行成功。可以通过reply->str得到文字信息,通过reply->len得到信息长度。
-
REDIS_REPLY_ERROR:
- 返回错误。错误信息可以通过reply->str得到文字信息,通过reply->len得到信息长度。
-
REDIS_REPLY_INTEGER:
- 返回整型标识。可以通过reply->integer变量得到类型为long long的值。
-
REDIS_REPLY_NIL:
- 返回nil对象,说明不存在要访问的数据。
-
REDIS_REPLY_STRING:
- 返回字符串标识。可以通过reply->str得到具体值,通过reply->len得到信息长度。
-
REDIS_REPLY_ARRAY:
- 返回数据集标识。数据集中元素的数目可以通过reply->elements获得,每个元素是个redisReply对象,元素值可以通过reply->element[..index..].*形式获得,用在获取多个数据结果的操作。
注意1: 执行完命令调用后,应该通过freeReplyObject()释放redisReply。对于嵌套对象(比如数组)要注意,并不需要嵌套进行释放,若进行嵌套释放,会造成内存破坏。
注意2: hiredis当前版本(0.10.0)在使用异步API时,会自己释放replies对象。这意味着,你使用异步API时并不需要主动调用freeReplyObject,relpy对象在回调返回时将会被自动释放。但是这种行为也许会在将来的版本中改变,所以升级时请密切关注升级日志。
第4讲 清理连接资源
断开连接并且释放context使用以下函数
void redisFree(redisContext *c);
此函数立马关闭socket并且释放创建context时分配的资源。
第5讲 发送多个命令参数
和redisCommand函数相似,redisCommandArgv函数可以用于传输多个命令参数。函数原型为
void *redisCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen);
类似于 lpush, del key1 key2..., zadd key score1 member1 score2 member2...这类命令, 其中 argc是传递参数的个数, argv主要用于传递的string的value, 而argvlen 是每个string的size。
此函数返回值与redisCommand相似。参考 https:// gist.github.com/dspezia /1455082
第6讲 管道(PipeLine)
为了搞清楚Hiredis在 阻塞连接 下的 管线操作 ,需要理解其内部执行过程。(context的输出缓冲区中是命令,其输入缓冲区中是reply)。
当任何类似于redisCommand的函数被调用,Hiredis首先将命令格式化为redis支持的命令协议。被格式化后的命令被放入 context的输出缓冲区 ,这个缓冲区是动态的,所以它可以容纳任意数量的命令。在命令进入 输出缓冲区 后,redisGetReply 函数被调用,这个函数有以下两种执行方式:
- 输入缓冲区非空:
- 从输入缓冲区中尝试解析单独的reply对象并且返回reply
- 如果没有reply能被解析,执行步骤2
- 将整个输出缓冲区写入socket
- 从socket中读取数据直到有一个reply能被解析
Hiredis为了有效利用socket还提供了redisGetReply的接口。对于管线命令,需要完成的唯一事情就是填充输出缓冲区。有两个函数被用于执行此操作,这两个函数基本与redisCommand函数功能类似,但是他们不返回reply.
void redisAppendCommand(redisContext *c, const char *format, ...);
void redisAppendCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen);
当这两个函数被一次或多次调用时,通过redisGetReply依次返回replies。redisGetReply的返回值要么是REDISOK或是REDISERR,REDIS_ERR意味着获得reply发生了错误,想要得到具体的错误原因可以通过err变量来获取。
以下通过一个简单例子说明管线的使用:
redisReply *reply;
redisAppendCommand(context,"SET foo bar");
redisAppendCommand(context,"GET foo");
redisGetReply(context,&reply); // reply for SET
freeReplyObject(reply);
redisGetReply(context,&reply); // reply for GET
freeReplyObject(reply);
redisGetReply这个API也可以被用来实现 阻塞的订阅模式
reply = redisCommand(context,"SUBSCRIBE foo");
freeReplyObject(reply);
while(redisGetReply(context,&reply) == REDIS_OK)
// consume message
freeReplyObject(reply);