hiredis
Hiredis是一个开源C库函数,提供了基本的操作redis 函数, 如数据库连接、发送命令、释放资源等等
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;
}