hiredis包装redis数据库操作接口及测试(增删改查与连接)

前两篇文章(redis安装配置与测试、redis的数据类型)总计了redis的一些基本知识,这里要进行一下redis的实践,先介绍一个实际应用场景,对于客户频繁需要查询的信息,可以将其放在redis内存数据库中,相当于一个缓存,每次查的时候先去redis内存数据库中去查询,如果查询不到再去oracle数据库中查询,这样提高了效率。

本文,使用redis的C与语操作接口hiredis包装了redis数据库的增删改查以及连接和断开连接接口,可以用在实际应用中。

(一)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);
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.c

/********************************
 * redis opration
 * created time: 2018-01-01
 * created by poetteaes
 *******************************/

# include "hiredis.h"
# include 
# include 

# 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.h"
# include 
# include 

# define EX_TIME 3600
# define NOT_FOUND -1403

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);

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;
}

3. makefile

CC = gcc
CFLAG = -g -c  -Wall

INCLUDE_PATH=-I/usr/local/include/hiredis/
LIB_PATH=-L/usr/local/lib -lhiredis  -L/media/sf_share/git/project/redis

all:  libredisopr.so redisTest


%.o:%.c
	$(CC) $(CFLAG) -fPIC   $(INCLUDE_PATH) $(LIB_PATH) -c $*.c
	
libredisopr.so:redisOpr.o
		$(CC) -o $@ $< -fPIC -shared  $(INCLUDE_PATH) $(LIB_PATH)
	
redisTest:redisTest.o
		$(CC) -o $@ $<  $(INCLUDE_PATH) $(LIB_PATH)  -lredisopr
		
clean:
	rm -f *.o

redisOpr编译成libredisopr.so动态库,redisTest引用动态库中的增删改查等函数。测试结果如下:

hiredis包装redis数据库操作接口及测试(增删改查与连接)_第1张图片

包装的接口支持对插入时主键重复(报错)以及更新和查找时键不存在的情况进行了判断。例如键已经存在的情况下执行测试程序,结果如下:

hiredis包装redis数据库操作接口及测试(增删改查与连接)_第2张图片

(四)接口的实际应用

这里只采用对一个键进行操作的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语言,redis)