把外部收到的消息,转化成内部事件,也就是 data->msg->event 的解码过程,然后再把事件投递至线池的消息队列,由线程池调用其process 方法对事件进行处理,最终调用每个 event 的 handler 方法来处理 event,此时每个 event handler 需要 subscribe 该 event 后才会被调用到。
可以看成是一个中转站,整合了线程池与 libevent 库。将网络接口接收到的数据变为具体事件请求交给线程池处理,线程池处理完后会返回相应的事件响应对象。再由该类对象处理好事件响应对象返回数据给网络接口类,网络接口类再把数据发回给客户端。
封装
class DispatchMsgService
{
protected:
DispatchMsgService();
public:
virtual ~DispatchMsgService();
virtual BOOL open();
virtual void close();
virtual void subscribe(u32 eid, iEventHandler* handler);
virtual void unsubscribe(u32 eid, iEventHandler* handler);
//把事件投递到线程池中进行处理
virtual i32 enqueue(iEvent* ev);
//线程池回调函数
static void svc(void* argv);
//对具体的事件进行分发处理
virtual iEvent* process(const iEvent* ev);
static DispatchMsgService* getInstance();
iEvent* parseEvent(const char* message, u32 len, u32 eid);
void handleAllResponseEvent(NetworkInterface* interface);
protected:
thread_pool_t* tp;
static DispatchMsgService* DMS_;
typedef std::vector<iEventHandler*> T_EventHandlers;
typedef std::map<u32, T_EventHandlers > T_EventHandlersMap;
T_EventHandlersMap subscribers_;
bool svr_exit_;
static std::queue<iEvent*> response_events;//响应的事件
static pthread_mutex_t queue_mutex;//线程互斥锁,锁队列
};
流程
首先 DispatchMsgService 类会创建唯一的一个对象(单例模式),该对象调用 open() 方法启动线程池。再调用 enqueue(iEvent* ev) 函数,他会传入一个 iEvent 对象,组装一个任务节点绑定任务处理函数为DispatchMsgService::svc,之后交给线程池任务队列。线程池会调用DispatchMsgService::svc函数,该函数会调用DispatchMsgService::process(const iEvent* ev)函数,如果此时该对象注册了处理函数,就创建一个UserEventHandler类对象并调用该对象的handler函数进行事件的处理。
1.启动线程池
BOOL DispatchMsgService::open()
{
svr_exit_ = FALSE;
thread_mutex_create(&queue_mutex);
tp = thread_pool_init();
return tp ? TRUE : FALSE;
}
void DispatchMsgService::close()
{
svr_exit_ = TRUE;
thread_pool_destroy(tp);
thread_mutex_destroy(&queue_mutex);
subscribers_.clear();
tp = NULL;
}
2.把事件投递到线程池中
i32 DispatchMsgService::enqueue(iEvent* ev)
{
if (NULL == ev)
{
return -1;
}
thread_task_t* task = thread_task_alloc(0);
task->handler = DispatchMsgService::svc;
task->ctx = ev;
return thread_task_post(tp, task);
//return msg_queue_.enqueue(ev, 0);
}
3.线程池回调
void DispatchMsgService::svc(void* argv)
{
DispatchMsgService* dms = DispatchMsgService::getInstance();
iEvent* ev = (iEvent*)argv;
if (!dms->svr_exit_)
{
LOG_DEBUG("DispatchMsgService::svc ...\n");
iEvent* rsp = dms->process(ev);
if (rsp)
{
rsp->dump(std::cout);
rsp->set_args(ev->get_args());
}
else
{
//生成终止响应事件
rsp = new ExitRspEv();
rsp->set_args(ev->get_args());
}
thread_mutex_lock(&queue_mutex);
response_events.push(rsp);
thread_mutex_unlock(&queue_mutex);
}
}
4.事件分发
iEvent* DispatchMsgService::process(const iEvent* ev)
{
LOG_DEBUG("DispatchMsgService::process -ev: %p\n", ev);
if (NULL == ev)
{
return NULL;
}
u32 eid = ev->get_eid();
LOG_DEBUG("DispatchMsgService::process - eid: %u\n", eid);
if (eid == EEVENTID_UNKOWN)
{
LOG_WARN("DispatchMsgService : unknow evend id %d", eid);
return NULL;
}
//LOG_DEBUG("dispatch ev : %d\n", ev->get_eid());
T_EventHandlersMap::iterator handlers = subscribers_.find(eid);
if (handlers == subscribers_.end()) // 未订阅
{
LOG_WARN("DispatchMsgService : no any event handler subscribed %d", eid);
return NULL;
}
iEvent* rsp = NULL;
// 给订阅的user_event_handler都发一遍
for (auto iter = handlers->second.begin(); iter != handlers->second.end(); iter++)
{
iEventHandler* handler = *iter;
LOG_DEBUG("DispatchMsgService : get handler: %s\n", handler->get_name().c_str());
rsp = handler->handle(ev);//推荐使用vector 或list 返回多个rsp
}
return rsp;
}
相关知识
单例模式是一种常用的软件设计模式,它保证一个类只有一个实例,并且提供了全局访问该实例的方法。在C++中,单例模式的实现方式有多种。
在单例模式中,通常使用一个静态方法或者一个静态变量来保存实例。这个静态方法或者静态变量可以被所有需要访问该实例的对象共享,并且在第一次调用时创建实例。之后每次调用该方法或者访问该变量时,都返回同一个实例。
单例模式的特点:
- 一个类只有一个实例;
- 该实例在程序运行的整个周期内始终存在;
- 该实例可以被全局访问;
应用
- 单例模式可以用于控制资源的访问,例如数据库连接池、线程池等;
- 它还可以用来确保系统中某些组件只有一个实例,例如配置文件管理器、日志记录器等。
实现单例模式的思路:
- 一个类能返回对象一个引用(永远是同一个)和一个获得该实例的方法(必须是静态方法,通常使用getInstance这个名称);
- 当我们调用这个方法时,如果类持有的引用不为空就返回这个引用,如果类保持的引用为空就创建该类的实例并将实例的引用赋予该类保持的引用;
- 同时我们还将该类的构造函数定义为私有方法,这样其他处的代码就无法通过调用该类的构造函数来实例化该类的对象,只有通过该类提供的静态方法来得到该类的唯一实例。
1.饿汉式:单例实例在类装载时就构建,急切初始化。(预先加载法)
public class Test {
private Test() {
}
public static Test instance = new Test();
public Test getInstance() {
return instance;
}
}
优点:没有加锁,执行效率会提高。缺点:类加载时就初始化,浪费内存。
2.懒汉式:单例实例在第一次被使用时构建,延迟初始化。
class Test {
private Test() {
}
public static Test instance = null;
public static Test getInstance() {
if (instance == null) {
//多个线程判断instance都为null时,在执行new操作时多线程会出现重复情况
instance = new Singleton2();
}
return instance;
}
}
优点:第一次调用才初始化,避免内存浪费。
缺点:必须加锁(在“线程安全”部分分享如何加锁)才能保证单例,但加锁会影响效率。
单例模式总结
要求: 在内存中只能生成一个对象
- 将构造函数私有化
- 在类中顶一个静态指针,在类之外初始化为空
- 在类中再顶一个返回值为类对象指针的静态成员函数