Source code analysis and simple encapsulation of hiredis

Posted by Griven on Sun, 23 Jan 2022 04:38:29 +0100

hiredis

Hiredis is an open source C library function that provides basic redis operation functions, such as database connection, sending commands, releasing resources, etc

1,hiredis net

hiredis itself is a cross platform code. It is built in c language and can be executed on a variety of platforms. Let's see what his net block has done

#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

Look at his header file and analyze it. Under linux, poll and epoll are used. Under windows, select and IOCP are used asynchronously. IOCP is encapsulated in win32_iocp.h and Win32_ iocp. In c, for the client, the poll mode is OK, which is more efficient than the select mode. However, the poll can still be used in windows and linux operating systems. So there is also an asycn encapsulation module, asynchronous callback. What is written in c language is really easy to understand.

2. Thread lock and mutex

Let's take a look at his writing method on locking. In fact, api mutually exclusive variables are used on windows and pthread is used on linux. This is a common practice, which is very helpful for database system clients. It simplifies programming and calls locking api directly on windows, which is more efficient.

#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 function

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

The author explains that this hash function has been tested a lot and is the most suitable in the hiredis environment. Selecting hash function is a unit test process. After all, the memory is limited. This hash method is still to select prime to add hash.

3.2 addition of hash table

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

This is most familiar to programmers using c language. In fact, it is hash - "linked list, addressing, conflict detection, addition, linked list. The whole hash table operation, addition, deletion, modification and query, more than 400 lines of code, simplification and good efficiency.

4. Linked list

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

Tail of linked 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;
}

Linked list operation is simply a basic course of data structure course. If the reader is a young programmer, whether using java, c, c + +, or users of other languages, this module can be used as a general learning module.

5,ae

ae is an event message loop module of hiredis, which is encapsulated in ae H and ae In CPP
Here is an eventloop for creating messages from a file

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

There are iocp events, epoll events, time events and so on.

summary

In general, for a database system, whether it is the client or the server, there are many things we need to do. To make a high-quality product, we need to make more efforts in details.

encapsulation

Simply encapsulate it with c + +, which is easier to use

#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) //Binary mode. If the line is broken, try to connect
{
//	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) //The value was not found
		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;
}

Encapsulation call

It's very simple. Just give it a try. The most important thing is not to use it. Emphasizing open source software is not to emphasize its use. It's its open source spirit and craftsman spirit. If the source code is well written, it's open source and pleasing to the eye. Looking at the code is a kind of enjoyment.

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

Topics: C Redis data structure db