C/C++编程操作Redis数据库,hiredis包装redis数据库操作接口及测试(增删改查与连接)
介紹一個實際應用場景,對于客戶頻繁需要查詢的信息,可以將其放在redis內存數據庫中,相當于一個緩存,每次查的時候先去redis內存數據庫中去查詢,如果查詢不到再去oracle數據庫中查詢,這樣提高了效率。
本文,使用redis的C與語操作接口hiredis包裝了redis數據庫的增刪改查以及連接和斷開連接接口,可以用在實際應用中。
?
C/C++接口庫下載
git clone https://github.com/redis/hiredis.git
?
一、hiredis介紹
hiredis提供了一linux下訪問redis的接口,比如連接和操作redis數據庫。這里介紹本文要使用的幾個api,其他的可參考官網介紹。
redisContext *redisConnect(const char *ip, int port); ? ? ? ? /*連接redis*/
void *redisCommand(redisContext *c, const char *format, ...); /*redis數據庫操作 如SET GET AUTH等*/
void freeReplyObject(void *reply); ? ? ? ? ? ? ? ? ? ? ? ? ? ?/*釋放redisCommand返回得到的結果結構體*
void redisFree(redisContext *c); ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?/*釋放連接上下文斷開連接*/
hiredis中兩種比較重要的結構體定義如下:
typedef struct redisContext?
{
????int err;
????char errstr[128];
????int fd;
????int flags;
????char *obuf;
????redisReader *reader;
} redisContext;
?
typedef struct redisReply?
{
? ? ? int type;?
? ? ? long long integer;
? ? ? int len;
? ? ? char *str;?
? ? ? size_t elements;?
? ? ? struct redisReply **element;?
} redisReply;
在調用redisConnect連接數據庫時候,會返回一個redisContext結構體指針,可以看做是一個連接句柄,redisCommand需要用到該句柄。調用redisCommand返回一個redisReply指針(實際返回的類型是void*,需要顯示的轉換為redisReply*指針),指向的結構體包含了一些返回信息,如返回類型、返回值和長度等。
二、hiredis使用
(1)連接redis
調用redisConnect連接redis數據庫,得到一個redisContext*指針,如果連接失敗,則結構體中的err和errstr被置值。連接成功則err為0。使用示例如下:
? ? redisContext *c = NULL;
?
?? ?/*connect*/
?? ?c = redisConnect(ip, port);
(2)操作redis
使用redisCommand,該接口返回一個void*指針,顯式轉換為redisReply*指針,指向的結構中包含了一些返回信息,比較重要的是返回類型,代表了返回結果的類型。使用示例如下:
? ? ?redisReply *reply = NULL;
? ? ?reply ?= (redisReply *)redisCommand(c, "GET %s", key);
reply->type表明了返回的類型,常見的類型如下:
REDIS_REPLY_STATUS:
返回了命令執行狀態,狀態信息可以由reply-> str獲得。AUTH命令和SET命令正常情況下返回該值, reply-> str為OK。
REDIS_REPLY_ERROR:
表示執行出現了一個錯誤
REDIS_REPLY_INTEGER:?
返回了一個整數,整數值由eply->integer表示。例如DEL操作,如果key存在,正常情況下返回該類型,表示鍵的個數。
REDIS_REPLY_NIL:
返回了nil。 表示無相應數據,例如GET不存在的key。
REDIS_REPLY_STRING:
表示命令執行后返回了一個字符串。 返回的字符串使用reply-> str來獲取。 這個字 符串的長度為reply-> len。例如GET,鍵存在的情況下,返回一個字符串。
(3)釋放返回結果機構體
freeReplyObject(reply)
使用完畢后需要釋放reply機構體。
(4)斷開連接,清空redisContext
redisFree(c);
三、hiredis包裝redis數據庫的增刪改查、連接和斷開連接接口函數,接口函數如下:
redisContext *redis_connect(char *ip, unsigned int port, char *passwd);
void redis_disconnect(redisContext *c);
int redis_insert(redisContext *c, char *key, char *value, int ex);
int redis_delete(redisContext *c, char *key);
int redis_select(redisContext *c, char *key, char *value);
int redis_update(redisContext *c, char *key, char *value, int ex);
具體實現:
1.redisopr.h
#ifndef _REDISOPR_H_
#define _REDISOPR_H_
//string
redisContext *redis_connect(char *ip, unsigned int port, char *passwd);
void redis_disconnect(redisContext *c);
void redis_disconnect(redisContext *c);
int redis_insert(redisContext *c, char *key, char *value, int ex);
int redis_delete(redisContext *c, char *key);
int redis_select(redisContext *c, char *key, char *value);
int redis_update(redisContext *c, char *key, char *value, int ex);
//...
#endif
2.?redisopr.c
/********************************
?* redis opration
?* created time: 2018-01-01
?* created by poetteaes
?*******************************/
?
# include "hiredis/hiredis.h"
# include <stdlib.h>
# include <string.h>
?
# define NOT_FOUND -1403
?
/********************************
?* redis connect
?* return a pointer if success;?
?* return NULL if failed
?*******************************/
redisContext *redis_connect(char *ip, unsigned int port, char *passwd)
{
?? ?redisContext *c = NULL;
?? ?redisReply *replay = NULL;
?? ?
?? ?/*connect*/
?? ?c = redisConnect(ip, port);
?? ?if(c == NULL)
?? ?{
?? ??? ?printf("Error: redisConnect() error!\n");
?? ??? ?return NULL;
?? ?}
?? ?if(c->err != 0)?
?? ?{
?? ??? ?printf("Error: %s\n", c->errstr);
?? ??? ?redisFree(c);
? ? }
?? ?
?? ?/*auth if passwd is not NULL*/
?? ?if(passwd != NULL)
?? ?{
?? ??? ?replay ?= (redisReply *)redisCommand(c, "AUTH %s", passwd);
?? ??? ?if( replay == NULL)
?? ??? ?{
?? ??? ??? ?printf("Error: AUTH error!\n");
?? ??? ??? ?redisFree(c);
?? ??? ??? ?printf("redisFree\n");
?? ??? ? ? ?return NULL;
?? ??? ?}
?? ??? ?if( !(replay->type == REDIS_REPLY_STATUS && memcmp(replay->str, "OK", 2) == 0) )
?? ??? ?{
?? ??? ??? ?printf("Error: AUTH error!\n");
?? ??? ??? ?freeReplyObject(replay);
?? ??? ??? ?redisFree(c);
?? ??? ??? ?printf("redisFree\n");
?? ??? ? ? ?return NULL;
?? ??? ?}
?? ?}
?? ?
?? ?return c; /*connect success*/
}
?
/********************************
?* redis select
?* return length of value if success;?
?* return NOT_FOUND if not found
?* return -1 if failed
?*******************************/
int redis_select(redisContext *c, char *key, char *value)
{
?? ? redisReply *reply = NULL;
?? ??
?? ? if(value == NULL)
?? ? {
?? ??? ? printf("Error: Invalid output argument!\n");
?? ??? ? return -1;
?? ? }
?? ??
?? ? reply ?= (redisReply *)redisCommand(c, "GET %s", key);
? ? ?if( reply == NULL)
?? ? {
?? ??? ? printf("Error: GET error!\n");
?? ??? ? return -1;
?? ? }
?? ??
?? ? if(reply->type != REDIS_REPLY_STRING && reply->type != REDIS_REPLY_NIL)
? ? ?{
? ? ??? ?printf("Error: GET error!\n");
? ? ??? ?freeReplyObject(reply);
? ? ? ? return -1;
? ? ?}
?? ? if(reply->type == REDIS_REPLY_NIL)
?? ? {
?? ??? ? printf("Not found\n");
?? ??? ? freeReplyObject(reply);
?? ??? ? return NOT_FOUND;
?? ? }
?? ??
?? ? memcpy(value, reply->str, reply->len);
?? ? freeReplyObject(reply);
?? ? return (reply->len);
}
?
/********************************
?* redis insert
?* return 0 if success;?
?* return -1 if failed
?*******************************/
int redis_insert(redisContext *c, char *key, char *value, int ex)
{
?? ?redisReply *reply = NULL;
?? ?
?? ?if(ex < 0)
?? ?{
?? ??? ?printf("Error: Invalid input argument ex[%d]", ex);
?? ??? ?return -1;
?? ?}
?? ?
?? ?/*test if the key has been existed*/
?? ?reply = (redisReply *)redisCommand(c, "GET %s", key);
?? ?if( reply == NULL)
?? ?{
?? ? printf("Error: GET error!\n");
?? ? return -1;
?? ?}
?? ?if(reply->type != REDIS_REPLY_STRING && reply->type != REDIS_REPLY_NIL)
? ? {
? ? ?? ?printf("Error: GET error!\n");
? ? ?? ?freeReplyObject(reply);
? ? ? ? return -1;
? ? }
?? ?
?? ?if(reply->type == REDIS_REPLY_STRING)
?? ?{
?? ??? ?printf("Error: The key has existed.\n");
?? ??? ?freeReplyObject(reply);
?? ??? ?return -1;
?? ?}
?? ?freeReplyObject(reply);
?? ?
?? ?
?? ?/*insert*/
?? ?reply = NULL;
?? ?if(ex != 0 )
?? ?{
?? ??? ?reply = (redisReply *)redisCommand(c, "SET %s %s EX %d", key, value, ex);?? ?
?? ?}
?? ?else
?? ?{
?? ??? ?reply = (redisReply *)redisCommand(c, "SET %s %s", key, value); /*without ex*/
?? ?}
?? ?
?? ?if( reply == NULL)
?? ?{
?? ? ?printf("Error: SET error!\n");
?? ? ?return -1;
?? ?}
?? ?if( !(reply->type == REDIS_REPLY_STATUS && memcmp(reply->str, "OK", 2) == 0) )
? ? {
? ? ?? ?printf("Error: SET error!\n");
? ? ?? ?freeReplyObject(reply);
? ? ? ? return -1;
? ? }
?? ?
?? ?
?? ?freeReplyObject(reply);
?? ?return 0;
}
?
/********************************
?* redis delete
?* return 0 if success;?
?* return NOT_FOUND if not found
?* return -1 if failed
?*******************************/
int redis_delete(redisContext *c, char *key)
{
?? ?redisReply *reply = NULL;
?? ?
?? ?reply = (redisReply *)redisCommand(c, "DEL %s", key);
?? ?if(reply == NULL || reply->type != REDIS_REPLY_INTEGER)
?? ?{
?? ??? ?printf("Error: The key doesn't exist.\n");
?? ??? ?freeReplyObject(reply);
?? ??? ?return -1;
?? ?}
?? ?
?? ?freeReplyObject(reply);
?? ?
?? ?if(reply->integer == 0)
?? ??? ?return NOT_FOUND;
?? ?else
?? ??? ?return (reply->integer - 1);?
?? ?
}
?
/********************************
?* redis delete
?* return 0 if success;?
?* return NOT_FOUND if not found
?* return -1 if failed
?*******************************/
int redis_update(redisContext *c, char *key, char *value, int ex)
{
?? ?redisReply *reply = NULL;
?? ?
?? ?if(ex < 0)
?? ?{
?? ??? ?printf("Error: Invalid input argument ex[%d]", ex);
?? ??? ?return -1;
?? ?}
?? ?
?? ?/*test if the key has been existed*/
?? ?reply = (redisReply *)redisCommand(c, "GET %s", key);
?? ?if( reply == NULL)
?? ?{
?? ? printf("Error: GET error!\n");
?? ? return -1;
?? ?}
?? ?if(reply->type != REDIS_REPLY_STRING && reply->type != REDIS_REPLY_NIL)
? ? {
? ? ?? ?printf("Error: GET error!\n");
? ? ?? ?freeReplyObject(reply);
? ? ? ? return -1;
? ? }
?? ?
?? ?if(reply->type == REDIS_REPLY_NIL)
?? ?{
?? ??? ?printf("The key doesn't exist.\n");
?? ??? ?freeReplyObject(reply);
?? ??? ?return NOT_FOUND;
?? ?}
?? ?freeReplyObject(reply);
?? ?
?? ?/*update*/
?? ?reply = NULL;
?? ?if(ex != 0)
?? ?{
?? ??? ?reply = (redisReply *)redisCommand(c, "SET %s %s EX %d", key, value, ex);?? ?
?? ?}
?? ?else
?? ?{
?? ??? ?reply = (redisReply *)redisCommand(c, "SET %s %s", key, value); /*without ex*/
?? ?}
?? ?
?? ?if( reply == NULL)
?? ?{
?? ? ?printf("Error: SET error!\n");
?? ? ?return -1;
?? ?}
?? ?if( !(reply->type == REDIS_REPLY_STATUS && memcmp(reply->str, "OK", 2) == 0) )
? ? {
? ? ?? ?printf("Error: SET error!\n");
? ? ?? ?freeReplyObject(reply);
? ? ? ? return -1;
? ? }
?? ?
?? ?
?? ?freeReplyObject(reply);
?? ?return 0;
}
?
/*redis disconnect*/
void redis_disconnect(redisContext *c)
{
?? ?redisFree(c);
}
2. redisTest.c?
測試程序如下:
/********************************
?* redis opration test
?* created time: 2018-01-01
?* created by poetteaes
?*******************************/
?
# include "hiredis/hiredis.h"
# include <stdlib.h>
# include <string.h>
# include "redisopr.h"
?
# define EX_TIME 3600
# define NOT_FOUND -1403
?
?
int main(void)
{
?? ?int ret;
?? ?char ip[] = "127.0.0.1";
?? ?unsigned int port = 6379;
?? ?redisContext *c = NULL;
?? ?char key[128] = {0};
?? ?char value[128] = {0};
?? ?
?? ?memcpy(key, "key1", 4);
?? ?memcpy(value, "value1", 6);
?? ?
?? ?/*connect*/
?? ?c = redis_connect(ip, port, NULL);
?? ?if(c == NULL || c->err != 0)
?? ?{
?? ??? ?printf("Error: redis_connect() error!\n");
?? ??? ?if(c != NULL )
?? ??? ??? ?redis_disconnect(c);
?? ??? ?return -1;
?? ?}
?? ?else
?? ?{
?? ??? ?printf("redis connected.\n\n");
?? ?}
?? ?
?? ?
?? ?/*insert*/
?? ?ret = redis_insert(c, key, value, EX_TIME);
?? ?if(ret < 0)
?? ?{
?? ??? ?printf("Error: redis_insert() error!\n\n");
?? ??? ?//redis_disconnect(c);
?? ??? ?//return -1;
?? ?}
?? ?else
?? ?{
?? ??? ?printf("\nredis_insert() success.\n");
?? ?}
?? ?
?
?? ?/*update*/
?? ?ret = redis_update(c, key, "value1update", EX_TIME);
?? ?if(ret != 0 && ret != NOT_FOUND)
?? ?{
?? ??? ?printf("\nError: redis_update() error!\n");
?? ??? ?//redis_disconnect(c);
?? ??? ?//return -1;
?? ?}
?? ?else if(ret == NOT_FOUND)
?? ?{
?? ??? ?printf("key is not found!\n");
?? ?}
?? ?else
?? ?{
?? ??? ?printf("\nredis_update() success.\n\n");
?? ?}
?
?? ?
?? ?/*select*/
?? ?ret = redis_select(c, key, value);
?? ?if(ret < 0 && ret!=NOT_FOUND)
?? ?{
?? ??? ?printf("\nError: redis_select() error!\n");
?? ??? ?//redis_disconnect(c);
?? ??? ?//return -1;
?? ?}
?? ?else if(ret == NOT_FOUND)
?? ?{
?? ??? ?printf("key is not found!\n");
?? ?}
?? ?else
?? ?{
?? ??? ?printf("\nredis_select() success.\n");
?? ??? ?printf("value[%s]\n\n",value);
?? ?}*/
?? ?
?
?? ?/*delete*/
?? ?ret = redis_delete(c, key);
?? ?if(ret < 0 && ret!=NOT_FOUND)
?? ?{
?? ??? ?printf("\nError: redis_delete() error!\n");
?? ??? ?//redis_disconnect(c);
?? ??? ?//return -1;
?? ?}
?? ?else if(ret == NOT_FOUND)
?? ?{
?? ??? ?printf("key is not found!\n");
?? ?}
?? ?else
?? ?{
?? ??? ?printf("\nredis_delete() success.\n");
?? ?}
?
?? ?
?? ?/*disconnect*/
?? ?redis_disconnect(c);
?? ?printf("\nredis disconnected.\n");
?? ?
?? ?return 0;
}
編譯:
gcc redisopr.c redistest.c -o redistest-L/usr/local/lib/ -lhiredis
如果出現:error while loading shared libraries: libhiredis.so.0.14: cannot open shared object file: No such file or directory
那就表示系統不知道xxx.so 放在哪個目錄下。
這個時候就要在/etc/ld.so.conf中加入xxx.so所在的目錄。
一般而言,有很多so檔會在/usr/local/lib這個目錄下
在/etc/ld.so.conf中加入/usr/local/lib這一行后,執行 /sbin/ldconfig 命令來更新生效。??
接下來可以成功執行了
?
運行結果:
包裝的接口支持對插入時主鍵重復(報錯)以及更新和查找時鍵不存在的情況進行了判斷。例如鍵已經存在的情況下執行測試程序,結果如下:
四、接口的實際應用
這里只采用對一個鍵進行操作的API來包裝接口,實際上利用hiredis的API,也可以對多個鍵操作。但是一個鍵操作包裝出來的增刪改查在一些應用場景下已足矣,因為多個鍵實際上可以拼成一個鍵,比如定長格式的字符串或者JSON字符串,或者分割符字符串,同樣結果的字符串也可以拼接,作為拼接key的拼接value,實際使用的時候,對結果再進行解析即可。
再回到開頭說的應用場景,設想一個oracle數據庫表,為訂單表,主鍵為date和orderid,非主鍵字段為price何goodsname,交易發生時數據存儲到oracle數據庫,同時也存儲在redis內存數據庫中(超時清理),客戶查詢時候先查redis內存數據庫,查不到再查oracle數據庫。盡管主鍵和查詢結果都有兩個字段,但是完全可以用|分割符將主鍵的兩個字段進行拼接(當然也可以使用JSON等格式封裝),作為redis的鍵,同樣結果字段也可以這樣拼接,這樣表中的每一行數據都可以在redis中由單一的key和value表示。插入redis數據庫的時候,把date和orderid用|拼接成鍵,price何goodsname用|拼接成value,然后調用上述包裝的redis_insert函數進行插入;查詢redis數據庫的時候,把查詢字段date和orderid用|拼接為鍵,使用包裝的redis_select去查詢,得到拼裝的value結果,從中解析出price何goodsname記得到了各個結果字段。
?
總結
以上是生活随笔為你收集整理的C/C++编程操作Redis数据库,hiredis包装redis数据库操作接口及测试(增删改查与连接)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: C 配置文件
- 下一篇: IAR 窗口重置默认配置