redis异步实现订阅和发布(C++)

发布订阅模式

订阅、取消订阅和发布实现了发布/订阅消息范式,发布者不是计划发送消息给特定的订阅者。而是发布的消息分到不同的频道,不需要知道什么样的订阅者订阅。
订阅者对一个或多个频道感兴趣,只需接收感兴趣的消息,不需要知道什么样的发布者发布的。
示例使用libevent库、boost库、 hiredis库等,因为是基于异步的发送和接收,需要回调函数来返回确认相关的信息。

  1. 发布者模块
// redis_publisher.h封装了hiredis,实现消息发布给redis的功能。
#ifndef REDIS_PUBLISHER_H
#define REDIS_PUBLISHER_H

#include <stdlib.h>
#include <hiredis/async.h>
#include <hiredis/adapters/libevent.h>
#include <string>
#include <vector>
#include <unistd.h>
#include <pthread.h>
#include <semaphore.h>
#include <boost/tr1/functional.hpp>

class CRedisPublisher
{
public:    
    CRedisPublisher();
    ~CRedisPublisher();
 // 初识化
    bool init();
// 反初始化
    bool uninit();
// 连接
    bool connect();
// 断开连接
    bool disconnect();
    
// 推送
    bool publish(const std::string &channel_name, 
        const std::string &message);

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:
     // libevent事件对象
    event_base *_event_base;
//  事件线程ID
    pthread_t _event_thread;
//  事件线程的信号量
    sem_t _event_sem;
//  hiredis异步对象
    redisAsyncContext *_redis_context;
};
#endif
#include <stddef.h>
#include <assert.h>
#include <string.h>
#include "redis_publisher.h"

CRedisPublisher::CRedisPublisher():_event_base(0), _event_thread(0),
_redis_context(0)
{
}

CRedisPublisher::~CRedisPublisher()
{
}

bool CRedisPublisher::init()
{
    // initialize the event
    _event_base = event_base_new();    // 创建libevent对象
    if (NULL == _event_base)
    {
        printf(": Create redis event failed.\n");
        return false;
    }

    memset(&_event_sem, 0, sizeof(_event_sem));
    int ret = sem_init(&_event_sem, 0, 0);
    if (ret != 0)
    {
        printf(": Init sem failed.\n");
        return false;
    }

    return true;
}

bool CRedisPublisher::uninit()
{
    _event_base = NULL;

    sem_destroy(&_event_sem);   
    return true;
}

bool CRedisPublisher::connect()
{
    // connect redis
    _redis_context = redisAsyncConnect("127.0.0.1", 6379);    // 异步连接到redis服务器上,使用默认端口
    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;
    }

    // attach the event
    redisLibeventAttach(_redis_context, _event_base);    // 将事件绑定到redis context上,使设置给redis的回调跟事件关联
    // 创建事件处理线程
    int ret = pthread_create(&_event_thread, 0, &CRedisPublisher::event_thread, this);
    if (ret != 0)
    {
        printf(": create event thread failed.\n");
        disconnect();
        return false;
    }
    // 设置连接回调,当异步调用连接后,服务器处理连接请求结束后调用,通知调用者连接的状态
    redisAsyncSetConnectCallback(_redis_context, 
        &CRedisPublisher::connect_callback);
    // 设置断开连接回调,当服务器断开连接后,通知调用者连接断开,调用者可以利用这个函数实现重连
    redisAsyncSetDisconnectCallback(_redis_context,
        &CRedisPublisher::disconnect_callback);

    // 启动事件线程
    sem_post(&_event_sem);
    return true;
}
bool CRedisPublisher::disconnect()
{
    if (_redis_context)
    {
        redisAsyncDisconnect(_redis_context);
        redisAsyncFree(_redis_context);
        _redis_context = NULL;
    }

    return true;
}

bool CRedisPublisher::publish(const std::string &channel_name,
    const std::string &message)
{
    int ret = redisAsyncCommand(_redis_context, 
        &CRedisPublisher::command_callback, this, "PUBLISH %s %s", 
        channel_name.c_str(), message.c_str());
    if (REDIS_ERR == ret)
    {
        printf("Publish command failed: %d\n", ret);
        return false;
    }

    return true;
}

void CRedisPublisher::connect_callback(const redisAsyncContext *redis_context,
    int status)
{
    if (status != REDIS_OK)
    {
        printf(": Error: %s\n", redis_context->errstr);
    }
    else
    {
        printf(": Redis connected!\n");
    }
}

void CRedisPublisher::disconnect_callback(
    const redisAsyncContext *redis_context, int status)
{
    if (status != REDIS_OK)
    {
        // 这里异常退出,可以尝试重连
        printf(": Error: %s\n", redis_context->errstr);
    }
}
// 消息接收回调函数
void CRedisPublisher::command_callback(redisAsyncContext *redis_context,
    void *reply, void *privdata)
{
    printf("command callback.\n");
    // 这里不执行任何操作
}

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

    CRedisPublisher *self_this = reinterpret_cast<CRedisPublisher *>(data);
    return self_this->event_proc();
}

void *CRedisPublisher::event_proc()
{
   	sem_wait(&_event_sem);
    //  开启事件分发,event_base_dispatch会阻塞
    event_base_dispatch(_event_base);
    
    return NULL;
}
  1. 接受者模块
#ifndef REDIS_SUBSCRIBER_H
#define REDIS_SUBSCRIBER_H

#include <stdlib.h>
#include <hiredis/async.h>
#include <hiredis/adapters/libevent.h>
#include <string>
#include <vector>
#include <unistd.h>
#include <pthread.h>
#include <semaphore.h>
#include <boost/tr1/functional.hpp>

class CRedisSubscriber
{
public:
    typedef std::tr1::function<void (const char*,const char *, int)>NotifyMessageFn;// 回调函数对象类型,当接收到消息后调用回调把消息发送出去

    CRedisSubscriber();
    ~CRedisSubscriber();

    bool init(const NotifyMessageFn &fn);    // 传入回调对象
    bool uninit();
    bool connect();
    bool disconnect();

    // 可以多次调用,订阅多个频道
    bool subscribe(const std::string &channel_name);

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:
    // libevent事件对象
    event_base *_event_base;
    // 事件线程ID
    pthread_t _event_thread;
    // 事件线程的信号量
    sem_t _event_sem;
    // hiredis异步对象
    redisAsyncContext *_redis_context;

    // 通知外层的回调函数对象
    NotifyMessageFn _notify_message_fn;
};
#endif

#include <stddef.h>
#include <assert.h>
#include <string.h>
#include "redis_subscriber.h"

CRedisSubscriber::CRedisSubscriber():_event_base(0), _event_thread(0),
_redis_context(0)
{
}

CRedisSubscriber::~CRedisSubscriber()
{
}

bool CRedisSubscriber::init(const NotifyMessageFn &fn)
{
    // initialize the event
    _notify_message_fn = fn;
    _event_base = event_base_new();    // 创建libevent对象
    if (NULL == _event_base)
    {
        printf(": Create redis event failed.\n");
        return false;
    }

    memset(&_event_sem, 0, sizeof(_event_sem));
    int ret = sem_init(&_event_sem, 0, 0);
    if (ret != 0)
    {
        printf(": Init sem failed.\n");
        return false;
    }

    return true;
}

bool CRedisSubscriber::uninit()
{
    _event_base = NULL;

    sem_destroy(&_event_sem);   
    return true;
}

bool CRedisSubscriber::connect()
{
    // connect redis
    _redis_context = redisAsyncConnect("127.0.0.1", 6379);    // 异步连接到redis服务器上,使用默认端口
    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;
    }

    // attach the event
    redisLibeventAttach(_redis_context, _event_base);    // 将事件绑定到redis context上,使设置给redis的回调跟事件关联

    // 创建事件处理线程
    int ret = pthread_create(&_event_thread, 0, &CRedisSubscriber::event_thread, this);
    if (ret != 0)
    {
        printf(": create event thread failed.\n");
        disconnect();
        return false;
    }

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

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

    // 启动事件线程
    sem_post(&_event_sem);
    return true;
}

bool CRedisSubscriber::disconnect()
{
    if (_redis_context)
    {
        redisAsyncDisconnect(_redis_context);
        redisAsyncFree(_redis_context);
        _redis_context = NULL;
    }

    return true;
}

bool CRedisSubscriber::subscribe(const std::string &channel_name)
{
    int ret = redisAsyncCommand(_redis_context, 
        &CRedisSubscriber::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;
}

void CRedisSubscriber::connect_callback(const redisAsyncContext *redis_context,
    int status)
{
    if (status != REDIS_OK)
    {
        printf(": Error: %s\n", redis_context->errstr);
    }
    else
    {
        printf(": Redis connected!");
    }
}

void CRedisSubscriber::disconnect_callback(
    const redisAsyncContext *redis_context, int status)
{
    if (status != REDIS_OK)
    {
        // 这里异常退出,可以尝试重连
        printf(": Error: %s\n", redis_context->errstr);
    }
}

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

    // 静态函数中,要使用类的成员变量,把当前的this指针传进来,用this指针间接访问
    CRedisSubscriber *self_this = reinterpret_cast<CRedisSubscriber *>(privdata);
    redisReply *redis_reply = reinterpret_cast<redisReply *>(reply);

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

        // 调用函数对象把消息通知给外层
        self_this->_notify_message_fn(redis_reply->element[1]->str,
            redis_reply->element[2]->str, redis_reply->element[2]->len);
    }
}

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

    CRedisSubscriber *self_this = reinterpret_cast<CRedisSubscriber *>(data);
    return self_this->event_proc();
}

void *CRedisSubscriber::event_proc()
{
    sem_wait(&_event_sem);

    // 开启事件分发,event_base_dispatch会阻塞
    event_base_dispatch(_event_base);

    return NULL;
}
  1. 发布者主程序
#include "redis_publisher.h"

int main(int argc, char *argv[])
{
    CRedisPublisher publisher;

    bool ret = publisher.init();
    if (!ret) 
    {
        printf("Init failed.\n");
        return 0;
    }

    ret = publisher.connect();
    if (!ret)
    {
        printf("connect failed.");
        return 0;
    }

    while (true)
    {
        publisher.publish("test-channel", "Hello redis test1!");
publisher.publish("test-channe2", "test2")
        sleep(1);
    }

    publisher.disconnect();
    publisher.uninit();
    return 0;
}
  1. 订阅者主程序
#include "redis_subscriber.h"

void recieve_message(const char *channel_name,
    const char *message, int len)
{
    printf("Recieve message:\n    channel name: %s\n    message: %s\n",
        channel_name, message);
}

int main(int argc, char *argv[])
{
    CRedisSubscriber subscriber;
    CRedisSubscriber::NotifyMessageFn fn = 
        bind(recieve_message, std::tr1::placeholders::_1,
        std::tr1::placeholders::_2, std::tr1::placeholders::_3);

    bool ret = subscriber.init(fn);
    if (!ret)
    {
        printf("Init failed.\n");
        return 0;
    }

    ret = subscriber.connect();
    if (!ret)
    {
        printf("Connect failed.\n");
        return 0;
    }

    subscriber.subscribe("test-channel");
subscriber.subscribe("test-channe2");

    while (true)
    {
        sleep(1);
    }

    subscriber.disconnect();
    subscriber.uninit();

    return 0;
}
  • 4
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
SSM(Spring+Spring MVC+MyBatis)框架和Redis数据库通常可以结合使用以实现发布订阅功能。 发布订阅模式是一种面向消息的通信方式,多个接收者监听同一个主题,当主题发布消息时,所有接收者都会接收到通知并执行相应的操作。Redis发布订阅功能是Redis提供的一种基于事件驱动的消息通知机制,可以让多个客户端同时接收系统事件的通知。 在SSM框架中,可以使用Spring框架的事件驱动机制来实现发布订阅功能。具体地,可以在Spring框架的配置文件中配置一个事件监听器(Event Listener),该监听器会监听指定的事件。当该事件发生时,监听器会接收到事件通知,并执行相应的业务逻辑。例如,可以将Redis发布消息事件(PUBLISH)作为Spring事件,然后在事件监听器中实现对该事件的处理逻辑。 在Redis中,可以使用PUBLISH命令向指定的频道(Channel)发布消息。多个客户端可以使用SUBSCRIBE命令来订阅指定的频道,当有消息发布到该频道时,所有订阅该频道的客户端都会接收到该消息。 因此,在SSM和Redis实现发布订阅功能的步骤大致可以分为以下几步: 1. 配置Redis连接信息,以便SSM框架能够正确连接到Redis数据库。 2. 在Spring框架的配置文件中定义一个事件监听器,该监听器将监听Redis发布事件。 3. 在监听器中编写事件处理逻辑,例如向所有订阅了该频道的客户端发送指定的消息。 4. 在应用程序中使用PUBLISH命令向指定的频道发布消息,或使用SUBSCRIBE命令订阅指定的频道。 通过以上步骤,便可以在SSM框架和Redis数据库中实现发布订阅功能。该功能可用于实现实时消息推送、事件通知等功能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值