redis真的是对c++太不友好了,官方指定文件只能使用同步模式,对于异步模式的编译设置一点都不带提的。

hiredis的异步实现是通过事件来分发redis发送过来的消息的,hiredis可以使用libae、libev、libuv和libevent中的任何一个实现事件的分发,网上大部分案例都是选用libevent。而libevent我下载编译完成后,加入到工程中,hiredis还是各种报错找不到文件,怀疑可能是我的hiredis编译的时候没有将libevent对应的依赖文件一块进行编译,所以会出现很多问题,而且libevent应该是对linux比较友好,window还是差劲!

尝试后发现可以使用 adapters/ae.h,下面介绍如何将所依赖文件一块编译到hiredis静态库中。

redis官网对windows的最新支持为 Redis3.2.100,下载完成后解压,然后将 redis\src 文件夹下的 Win32_Interop文件夹、adlist.c  ae.h  ae.c  ae_wsiocp.c  zmalloc.c  config.h 直接拷贝到deps\hiredis下,然后打开vs2013,将这几个文件添加到hiredis解决方案下,此时进行编译会提示一堆找不到文件,挨个打开文件将所需文件路径改为当前文件路径。然后重新进行编译,此时生成的hiredis.lib将会包含ae事件驱动所需函数文件,加入工程即可正常使用redis异步模式。

下面附发布订阅模式源码:

#ifndef REDIS_SUB_CLIENT_H  
#define REDIS_SUB_CLIENT_H  

extern "C"
{
#include <stdlib.h>
#include "hiredis\hiredis.h"
#include "hiredis\async.h"
#include "hiredis\adapters\ae.h"
}
#include <string>  
#include <vector> 
#include <boost\thread.hpp>
#include <boost\thread\condition.hpp>
#include <boost\thread\mutex.hpp>
#include <boost\thread\condition.hpp>
#include <boost\signals2\signal.hpp>
#include <boost\signals2.hpp>
#include <iostream>
using namespace std;

class CRedisSubClient
{
public:

	CRedisSubClient();
	~CRedisSubClient();

	bool init();   //初始化,事件对象,信号量  
	bool uninit();  //释放对象
	bool connect();  //连接服务器
	bool disconnect(); //断开服务器

	bool ConnectBack();//连接状态返回

	bool subscribe(const string &channel_name);	//订阅频道 

	bool RedisAsyncCommand(redisCallbackFn *Callback, void *privdata, const char *format, ...);

private:
	// 下面三个回调函数供redis服务调用  
	// 连接回调  
	static void connect_callback(const redisAsyncContext *redis_context,
		int status);

	// 断开连接的回调  
	static void disconnect_callback(const redisAsyncContext *redis_context,
		int status);

	// 执行命令回调  
	static void command_callback(redisAsyncContext *redis_context,void *reply, void *privdata);

	// 事件分发线程函数  
	static void event_thread(void *data);
	void event_proc();


private:
	// ae事件对象  
	aeEventLoop *loop;
	
	// 事件线程ID  
	boost::thread _event_handle;

	// hiredis异步对象  
	redisAsyncContext *_redis_context;

	//信号量控制
	int _status;	
};

#endif
#include <stddef.h>  
#include <assert.h>  
#include <string.h>  
#include "RedisSubClient.h"

using namespace std;

CRedisSubClient::CRedisSubClient() :loop(0), _redis_context(0), _status(-1)
{
}

CRedisSubClient::~CRedisSubClient()
{
}

bool CRedisSubClient::init()
{
	loop = aeCreateEventLoop(64*1024);    // 创建ae对象
	if (NULL == loop)
	{
		printf("Create redis event failed.\n");
		return false;
	}
	return true;
}

bool CRedisSubClient::uninit()
{
	aeStop(loop);
	return true;
}

bool CRedisSubClient::connect()
{
	_redis_context = redisAsyncConnect("127.0.0.1", 6379);    // 异步连接到redis服务器上,使用6380端口

	if (NULL == _redis_context)
	{
		printf(": Connect redis failed.\n");
		return false;
	}

	if (_redis_context->err)
	{
		printf("Connect redis error: %d, %s\n",_redis_context->err, _redis_context->errstr);    // 输出错误信息  
		return false;
	}

	//此变量hiredis 未使用 可以用作连接回调参数
	_redis_context->data = (void*)this;

	// 将事件绑定到redis context上,使redis的回调跟事件关联 
	redisAeAttach(loop, _redis_context);    

	// 设置连接回调,当异步调用连接后,服务器处理连接请求结束后调用,通知调用者连接的状态  
	redisAsyncSetConnectCallback(_redis_context,&CRedisSubClient::connect_callback);

	// 设置断开连接回调,当服务器断开连接后,通知调用者连接断开,调用者可以利用这个函数实现重连  
	redisAsyncSetDisconnectCallback(_redis_context,&CRedisSubClient::disconnect_callback);

	redisAsyncCommand(_redis_context, NULL, NULL, "AUTH %s", "test123", 7);

	// 启动事件线程  
	_event_handle = boost::thread(CRedisSubClient::event_thread, this);
	if (!_event_handle.joinable())
	{
		printf("Create event thread failed.\n");
		disconnect();
		return false;
	}
	return true;
}

bool CRedisSubClient::disconnect()
{
	if (_redis_context)
	{
		redisAsyncDisconnect(_redis_context);
		//redisAsyncFree(_redis_context);
		_redis_context = NULL;
	}
	return true;
}

bool CRedisSubClient::subscribe(const string &channel_name)
{
	int ret = redisAsyncCommand(_redis_context, &CRedisSubClient::command_callback, this, "SUBSCRIBE %s",
		channel_name.c_str()); //订阅一个频道

	if (REDIS_ERR == ret)
	{
		printf("Subscribe command failed: %d\n", ret);
		return false;
	}

	printf("Subscribe success: %s\n", channel_name.c_str());
	return true;
}


//异步命令调用
bool CRedisSubClient::RedisAsyncCommand(redisCallbackFn *Callback, void *privdata, const char *format, ...)
{
	va_list ap;
	va_start(ap, format);
	int ret = redisvAsyncCommand(_redis_context, Callback, privdata, format, ap); //订阅一个频道
	va_end(ap);
	
	if (REDIS_ERR == ret)
	{
		printf("Command failed: %d\n", ret);
		return false;
	}

	printf("Command success!\n");
	return true;
}

//连接状态返回
bool CRedisSubClient::ConnectBack()
{
	if (_status != REDIS_OK)
		return false;
	else
		return true;
}


//连接
void CRedisSubClient::connect_callback(const redisAsyncContext *redis_context,int status)
{
	CRedisSubClient* slef_this = reinterpret_cast<CRedisSubClient*>(redis_context->data);
	slef_this->_status = status;
	return;
}

//断开
void CRedisSubClient::disconnect_callback(const redisAsyncContext *redis_context, int status)
{
	if (status != REDIS_OK)
	{
		printf(": Error: %s\n", redis_context->errstr);
	}
}

// 消息接收回调函数  
void CRedisSubClient::command_callback(redisAsyncContext *redis_context,void *reply, void *privdata)
{
	if (NULL == reply || NULL == privdata) {
		return;
	}

	redisReply *redis_reply = reinterpret_cast<redisReply *>(reply);

	// 订阅接收到的消息是一个带三元素的数组  
	if (redis_reply->type == REDIS_REPLY_ARRAY &&
		redis_reply->elements == 3)
	{
		printf("Recieve message:%s %s %s\n",
			redis_reply->element[0]->str,
			redis_reply->element[1]->str,
			redis_reply->element[2]->str);
	}
}

void CRedisSubClient::event_thread(void *data)
{
	if (NULL == data)
	{
		printf(": Error!\n");
		assert(false);
		return;
	}

	CRedisSubClient *self_this = reinterpret_cast<CRedisSubClient *>(data);
	
	//进行事件处理循环  
	return self_this->event_proc();
}

void CRedisSubClient::event_proc()
{
	//进行事件处理循环  
	aeMain(loop);
	return;
}

测试:

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


void getCallback(redisAsyncContext *c, void *r, void *privdata)
{
	redisReply *reply = (redisReply *)r;
	if (reply == NULL) return;
	printf("Get key: %s\n", reply->str);

	/* Disconnect after receiving the reply to GET */
	//redisAsyncDisconnect(c);
}


void getsubCallback(redisAsyncContext *redis_context, void *reply, void *privdata)
{
	if (NULL == reply) 
		return;

	redisReply *redis_reply = reinterpret_cast<redisReply*>(reply);

	// 订阅接收到的消息是一个带三元素的数组  
	if (redis_reply->type == REDIS_REPLY_ARRAY &&
		redis_reply->elements == 3)
	{
		printf("Recieve message:%s %s %s\n",
			redis_reply->element[0]->str,
			redis_reply->element[1]->str,
			redis_reply->element[2]->str);
	}
}



CRedisSubClient subcriber;

void Unsubscribe()
{
	boost::this_thread::sleep(boost::posix_time::seconds(30));
	bool ret_sub = subcriber.RedisAsyncCommand(getsubCallback, NULL, "UNSUBSCRIBE %s", "Chat");
	if (ret_sub)
	{
		cout << "UNSUBSCRIBE Successful" << endl;
	}
	return;
}


int main(int argc, char *argv[])
{
	
	bool ret_sub = subcriber.init();

	if (!ret_sub)
	{
		cout<<"Init failed"<<endl;
		return 0;
	}

	ret_sub = subcriber.connect();

	if (!ret_sub)
	{
		cout << "Connect failed" << endl;
		return 0;
	}

	//阻塞等待连接返回成功
	while (!subcriber.ConnectBack())
	Sleep(50);
	
	//测试
	ret_sub = subcriber.RedisAsyncCommand(getCallback, NULL, "GET key");

	if (!ret_sub)
	{
		cout << "Get key failed" << endl;
		return 0;
	}

	ret_sub = subcriber.RedisAsyncCommand(getsubCallback, NULL, "SUBSCRIBE %s","Chat");

	boost::thread th(Unsubscribe);

	boost::this_thread::sleep(boost::posix_time::seconds(60));

	ret_sub = subcriber.RedisAsyncCommand(getsubCallback, NULL, "SUBSCRIBE %s", "Chat");
	
	boost::this_thread::sleep(boost::posix_time::seconds(60));

	subcriber.uninit();
	subcriber.disconnect();	
	getchar();
	return 0;

}

源码中部分bug已经在上面代码中进行修复。