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已经在上面代码中进行修复。