hiredis源码分析与简单封装

  • Post author:
  • Post category:其他




hiredis

Hiredis是一个开源C库函数,提供了基本的操作redis 函数, 如数据库连接、发送命令、释放资源等等

hiredis



1、hiredis net

hiredis 本身就是做了跨平台的代码,c语言打造可以执行在多种平台上,看看他的net块做了些什么

#include "fmacros.h"
#include <sys/types.h>
#ifdef _WIN32
#ifndef FD_SETSIZE
#define FD_SETSIZE 16000
#endif
#else
#include <sys/socket.h>
#include <sys/select.h>
#include <sys/un.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <netdb.h>
#endif
#include <fcntl.h>
#include <string.h>
#include <errno.h>
#include <stdarg.h>
#include <stdio.h>
#ifndef _WIN32
#include <poll.h>
#endif
#include <limits.h>

#include "net.h"
#include "sds.h"
#ifdef _WIN32
#include "../src/win32fixes.h"
#endif

看他的头文件,分析一下,linux下是使用poll 和 epoll方式的,windows下使用select 和IOCP 异步方式,对iocp的封装在win32_iocp.h 和win32_iocp.c 里面对于客户端来说,poll方式是可以了,比select方式效率还是要高,不过poll在windows和linux操作系统上还是可以使用封装来通用的。所以里面还有一个asycn 封装模块,异步回调。c语言写的东西确实简单易懂。



2、线程锁和互斥

来看一下他对加锁方面的写法,实际上,在windows上使用了api互斥变量,在linux上使用pthread,通用做法,这对做数据库系统客户端来说是很有帮助的,简化编程,在windows上直接调用锁api方式效率更高。

#define pthread_mutex_t CRITICAL_SECTION
#define pthread_attr_t ssize_t

#define pthread_mutex_init(a,b) (InitializeCriticalSectionAndSpinCount((a), 0x80000400),0)
#define pthread_mutex_destroy(a) DeleteCriticalSection((a))
#define pthread_mutex_lock EnterCriticalSection
#define pthread_mutex_unlock LeaveCriticalSection

#define pthread_equal(t1, t2) ((t1) == (t2))

#define pthread_attr_init(x) (*(x) = 0)
#define pthread_attr_getstacksize(x, y) (*(y) = *(x))
#define pthread_attr_setstacksize(x, y) (*(x) = y)

#define pthread_t u_int



3、hash

typedef struct dictEntry {
    void *key;
    void *val;
    struct dictEntry *next;
} dictEntry;

typedef struct dictType {
    unsigned int (*hashFunction)(const void *key);
    void *(*keyDup)(void *privdata, const void *key);
    void *(*valDup)(void *privdata, const void *obj);
    int (*keyCompare)(void *privdata, const void *key1, const void *key2);
    void (*keyDestructor)(void *privdata, void *key);
    void (*valDestructor)(void *privdata, void *obj);
} dictType;



3.1 hash 函数

/* Generic hash function (a popular one from Bernstein).
 * I tested a few and this was the best. */
static unsigned int dictGenHashFunction(const unsigned char *buf, int len) {
    unsigned int hash = 5381;

    while (len--)
        hash = ((hash << 5) + hash) + (*buf++); /* hash * 33 + c */
    return hash;
}

作者说明这种hash函数已经测试了很多,在hiredis环境下是最合适的。选取hash函数是一个单元测试的过程,毕竟内存是有限的,这个hash方式依然是选取素数方式加法hash。



3.2 hash 表增加

/* Add an element to the target hash table */
static int dictAdd(dict *ht, void *key, void *val) {
    int index;
    dictEntry *entry;

    /* Get the index of the new element, or -1 if
     * the element already exists. */
    if ((index = _dictKeyIndex(ht, key)) == -1)
        return DICT_ERR;

    /* Allocates the memory and stores key */
    entry = malloc(sizeof(*entry));
    entry->next = ht->table[index];
    ht->table[index] = entry;

    /* Set the hash entry fields. */
    dictSetHashKey(ht, entry, key);
    dictSetHashVal(ht, entry, val);
    ht->used++;
    return DICT_OK;
}

这个对于使用c语言的程序员最熟悉不过了,实际上就是hash-》链表,寻址,检测冲突,增加,挂链表。整个hash表操作增删改查,400多行代码,精简,效率好。



4、 链表

typedef struct listNode {
    struct listNode *prev;
    struct listNode *next;
    void *value;
} listNode;

typedef struct listIter {
    listNode *next;
    int direction;
} listIter;

typedef struct list {
    listNode *head;
    listNode *tail;
    void *(*dup)(void *ptr);
    void (*free)(void *ptr);
    int (*match)(void *ptr, void *key);
    unsigned long len;
} list;

/* Functions implemented as macros */
#define listLength(l) ((l)->len)
#define listFirst(l) ((l)->head)
#define listLast(l) ((l)->tail)
#define listPrevNode(n) ((n)->prev)
#define listNextNode(n) ((n)->next)
#define listNodeValue(n) ((n)->value)

#define listSetDupMethod(l,m) ((l)->dup = (m))
#define listSetFreeMethod(l,m) ((l)->free = (m))
#define listSetMatchMethod(l,m) ((l)->match = (m))

#define listGetDupMethod(l) ((l)->dup)
#define listGetFree(l) ((l)->free)
#define listGetMatchMethod(l) ((l)->match)

/* Prototypes */
list *listCreate(void);
void listRelease(list *list);
list *listAddNodeHead(list *list, void *value);
list *listAddNodeTail(list *list, void *value);
list *listInsertNode(list *list, listNode *old_node, void *value, int after);
void listDelNode(list *list, listNode *node);
listIter *listGetIterator(list *list, int direction);
listNode *listNext(listIter *iter);
void listReleaseIterator(listIter *iter);
list *listDup(list *orig);
listNode *listSearchKey(list *list, void *key);
listNode *listIndex(list *list, long index);
void listRewind(list *list, listIter *li);
void listRewindTail(list *list, listIter *li);
void listRotate(list *list);

链表加尾

list *listAddNodeTail(list *list, void *value)
{
    listNode *node;

    if ((node = zmalloc(sizeof(*node))) == NULL)
        return NULL;
    node->value = value;
    if (list->len == 0) {
        list->head = list->tail = node;
        node->prev = node->next = NULL;
    } else {
        node->prev = list->tail;
        node->next = NULL;
        list->tail->next = node;
        list->tail = node;
    }
    list->len++;
    return list;
}

链表操作简直就是数据结构课程基础课,如果读者是年轻的程序员,无论是使用java,c,c++,还是其他语言的使用者,这个模块可以作为通用学习模块。



5、ae

ae是hiredis的一个事件消息loop模块,被封装在ae.h 和ae.cpp 中

下面是一个文件创建消息的eventloop

int aeCreateFileEvent(aeEventLoop *eventLoop, int fd, int mask,
        aeFileProc *proc, void *clientData)
{
    aeFileEvent *fe;
    if (fd >= eventLoop->setsize) {
        errno = ERANGE;
        return AE_ERR;
    }
    fe = &eventLoop->events[fd];

    if (aeApiAddEvent(eventLoop, fd, mask) == -1)
        return AE_ERR;
    fe->mask |= mask;
    if (mask & AE_READABLE) fe->rfileProc = proc;
    if (mask & AE_WRITABLE) fe->wfileProc = proc;
    fe->clientData = clientData;
    if (fd > eventLoop->maxfd)
        eventLoop->maxfd = fd;
    return AE_OK;
}

还有iocp的事件,epoll事件,时间事件等等。



总结

总的来说,对于做一个数据库系统来说,无论是客户端还是服务器端,都有很多需要我们做的,做一个精品需要我们在细节上下更多的功夫。



封装

简单用c++ 封装一下,更容易使用

#ifndef _REDIS_CLIENT_H_
#define _REDIS_CLIENT_H_
#include "../hiredis/hiredis.h"
#include <string>
#include <iostream>
using namespace std;

class Redis
{
public:
	Redis();
	~Redis();
public:
	int Connect(const char * ip, int port);
	void disConnect();
public:
	void setString(const string & key, const string & value);
	void setString(const string & key, const int & value);
	void setString(const string & key, const float & value);
	bool SetBinary(const string & key, void* pData, int dataLen);
	bool GetBinary(const string & key, void*pData, int datalen);
private:
	void setString(const string & data);
public:
	void getString(const string & key, string & value);
	void getString(const string & key, int & value);
	void getString(const string & key, float & value);
private:
	void getString(const string & key);
private:
	void freeReply();
	bool isError();
private:

	redisContext * _context = NULL;
	redisReply * _reply = NULL;
	string _ip;
	int _port;
};
#include "redisclient.h"

#include <string.h>
#include <stdlib.h>
#include <sstream>
#include <iostream>

using std::cout;
using std::endl;
using std::stringstream;
#define SETSTRING(key, value) \
stringstream ss;\
ss << "SET " << key << " " << value;\
string s;\
getline(ss, s);\
setString(s);

Redis::Redis()
{
#ifdef _WIN32
	WSADATA wsaData;
	::WSAStartup(MAKEWORD(2, 2), &wsaData);
#endif
}
Redis::~Redis()
{
	::WSACleanup();
}
int Redis::Connect(const char * ip,int port)
{
	_ip = ip;
	_port = port;
	struct timeval timeout = { 1, 0 }; // 1 seconds
	_context = ::redisConnectWithTimeout(ip, port, timeout);
	//_context = ::redisConnect(ip,port);
	if (_context && _context->err)
	{
		cout << "connect redis error" << endl;
		return -1;
		//exit(EXIT_FAILURE);
	}
	return 0;
	cout << "redis Connect success" << endl;
}
void Redis::disConnect()
{
	::redisFree(_context);
	cout << "redis disConnect success" << endl;
}
bool Redis::SetBinary(const string & key, void *pData, int dataLen) //二进制方式,如果断线,尝试连接
{
//	if (!TryConnet(tryConnet))
//		return false;
	//if (dataLen == -1)
	//	dataLen = ::strlen((LPCSTR)pData);
	freeReply();
	_reply = (redisReply *)::redisCommand(_context, "SET %s %b", key.c_str(), pData, (size_t)dataLen);
	if (!_reply || _reply->type != REDIS_REPLY_STATUS || ::strcmp(_reply->str, "OK"))
	{
		disConnect();
		return false;
	}

	return true;
}
bool Redis::GetBinary(const string & key, void*pData, int datalen)
{
	freeReply();
	_reply = (redisReply *)redisCommand(_context, "GET %s", key.c_str());
	if (!_reply || _reply->type == REDIS_REPLY_ERROR)
	{
		disConnect();
		return nullptr;
	}
	if (_reply->type == REDIS_REPLY_NIL) //未找到该值
		return false;
	if (_reply->len > datalen)
		return false;
	memcpy(pData, _reply->str, _reply->len);
	return true;
}

void Redis::setString(const string & data)
{
	freeReply();
	_reply = (redisReply*)::redisCommand(_context, data.c_str());
	if (!isError())
	{
		if (!(_reply->type == REDIS_REPLY_STATUS && strcmp(_reply->str, "OK") == 0))
		{
			cout << "Failed to execute SET(string)" << endl;
		}
	}
}
void Redis::setString(const string & key, const string & value)
{
	SETSTRING(key, value);
}
void Redis::setString(const string & key, const int & value)
{
	SETSTRING(key, value);
}
void Redis::setString(const string & key, const float & value)
{
	SETSTRING(key, value);
}
void Redis::getString(const string & key)
{
	freeReply();
	_reply = (redisReply*)::redisCommand(_context, "GET %s", key.c_str());
}
void Redis::getString(const string & key, string & value)
{
	getString(key);
	if (!isError() && _reply->type == REDIS_REPLY_STRING)
	{
		value = _reply->str;
	}
}
void Redis::getString(const string & key, int & value)
{
	getString(key);
	if (!isError() && _reply->type == REDIS_REPLY_STRING)
	{
		value = ::atoi(_reply->str);
	}
}
void Redis::getString(const string & key, float & value)
{
	getString(key);
	if (!isError() && _reply->type == REDIS_REPLY_STRING)
	{
		value = ::atof(_reply->str);
	}
}
void Redis::freeReply()
{
	if (_reply)
	{
		::freeReplyObject(_reply);
		_reply = NULL;
	}
}
bool Redis::isError()
{
	if (NULL == _reply)
	{
		freeReply();
		disConnect();
		Connect(_ip.c_str(),_port);
		return true;
	}
	return false;
}



封装调用

很简单,读者试一下就行了,最主要的并不是这个使用,强调开源软件,并不是强调其使用,是其开源的精神和工匠精神,源码写的好的开源,赏心悦目,看代码是一种享受。

#include "redisclient.h"
#include <iostream>
using namespace std;

struct testStruct
{
	int i = 10;
	int j = 20;
	char k[32] = "abcd";
};
#define OUT(x) std::cout<<#x<<" = "<<x<<std::endl; 

//redis: redis.cpp redis.h
//	g++ redis.cpp - o redis - L / usr / local / lib / -lhiredis
//
//	clean :
//	   rm redis.o redis

int main(void)
{

	Redis redis;
	if (redis.Connect("127.0.0.1", 6379) == 0)
	{
		testStruct test;
		redis.setString("qianbo", "test");
		redis.setString("test", "hello!");
		redis.SetBinary("test:abcd",&test, sizeof test);
		string value;
		redis.getString("qianbo", value);
		test.i = 1000;
		test.j = 2000;
		redis.GetBinary("test:abcd", &test, sizeof test);
		cout << value << endl;
		cout << test.i << " " << test.j << " " << test.k << endl;
	}
		//Sleep(10);
	//testStruct* pTest = (testStruct*)reply->str;
	//cout << pTest->i << " " << pTest->j << " " << pTest->k << endl;
	getchar();
	return 0;
}



版权声明:本文为qianbo042311原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。