Command模式,消息驱动与模块化设计

烽驿2009开源实时通信平台 源码获取:svn checkout http://fy2009.googlecode.com/svn/trunk/ fy2009-read-only

 

模块化是复杂系统架构设计所应遵循的基本原则之一,其目的是降低局部复杂度,便于大团队协同开发,有利于系统诊断,维护及功能复用。其中,模块划分基于所谓“高内聚,低耦合”的原则。
在C语言中,通常通过CallBack函数实现模块间耦合,即由一个模块提供CallBack函数并注册给另一模块,由后者在发生适当系统事件时调用,从而实现模块间协作。而在C++中通常采用更体现面向对象特性的所谓Sink对象。Sink对象仍然属于CallBack范畴,但更灵活,结构更清晰。CallBack机制通常采用同步调用,由它实现的模块间协作属于“紧耦合”,往往对系统的线程模型存在严重依赖,大大削弱各模块在不同线程模型中的复用机会。
设计模式中的Command模式常被用来解决CallBack机制存在的缺点,它解耦了程序行为的触发条件和实际执行。在满足程序行为触发条件时,将生成相应的Command对象(而不是象CallBack机制那样马上调用注册的CallBack函数),该对象可经过必要的存储,转发等中间操作,在系统认为时机成熟时通过解读Command对象完成程序行为的真正执行。与CallBack的“紧耦合”相对应,Command模式提供一种“松耦合“机制。用一个导弹发射的例子来比喻,CallBack机制就是那种发射后仍需要载机对目标进行持续雷达照射直到命中目标的导弹; 而Command模式则是那种具备“发射后不管(Fire and Forget)”特性的导弹。因Command对象也象所有其它对象一样可在线程间传递,因此,通过Command模式协作的模块可被用于更丰富的线程模型中。
众所周知,在Windows环境下,消息无处不在。消息在本质上就是Command对象,消息驱动则是Command模式面向通用目的的一种实现。专心于Windows上的应用开发,通常不需要实现自己的消息驱动机制,尽管其总以窗口作为消息接收主体的做法对于服务器应用来说显得不太合理,仅有消息类型,字参数,长参数的消息结构也显得简陋。在Linux下开发就没有这么幸运,如果需要,通常要求由开发者实现或简单或繁杂的消息驱动机制。
本项目实现了可在Windows和Linux之间移植,消息对象可含有动态属性列表。消息隐含的程序逻辑由消息处理器(实现msg_receiver_it接口)实现,并附加到消息上一起被发往目标线程,由后者驱动完成实际逻辑。线程是本项目中消息接收的主体,但消息并非被直接发往某个线程对象,而是发往注册在目标线程TLS(Thread Local Storage)中的msg_proxy_t中,该对象内包含一个写端半加锁(即写操作需加锁,读操作不加锁)的oneway_pipt_t(相关博克:http://blog.csdn.net/DreamFreeLancer/archive/2009/06/01/4231151.aspx),用于接收消息。目标线程则有义务驱动msg_proxy_t的heart_beat_it接口去接收并处理消息。通过设定消息对象的延迟时间,重复次数等可实现Timer事件,并按间隔时间长短,将当前所有Timer分组,每次执行检查时,只检查那些“快到期”的Timer,减少对Timer队列的遍历。目前总共分了12组,第0组放延时或间隔时间小于等于20ms的消息,依次,第一组放(21ms - 40ms], 第二组放(41ms - 80ms], ..., 第十组放(10241ms - 20490ms], 第十一组放(20491ms - 40960ms]。heart_beat在检查消息是否到期时,每次都会检查第零组里的每个消息,第一组间隔10ms查一次,第二组间隔20ms查一次,第三组间隔40ms查一次,直至第十一组间隔10240ms(约10秒)查一次。这样,每组消息在其延时周期中平均被查三次(最少二次,最多四次)。因此,本延时和定时消息的时间精度不会很高,平均理论精度为33%,最小25%,最大则为50%,因此,只能用于时间精度要求相对低的场合。本服务提供以下几类消息:

 

例1,尽快执行的消息

sp_msg_t msg=msg_t::s_create(111,0,0);

msg->set_receiver(sp_msg_rcver_t(new MyMsgReceiver_t(),true));

msg_proxy->post_msg(msg);

该消息将被发往msg_proxy所属的线程,并尽快被MyMsgReceiver执行

 

例2,延迟执行的消息

sp_msg_t msg=msg_t::s_create(222,0,0);

msg->set_utc_interval(1000/get_tick_count_res(user_clock_t::instance()));

msg->set_receiver(sp_msg_rcver_t(new MyMsgReceiver_t(),true));

msg_proxy->post_msg(msg);

该消息将被发往msg_proxy所属的线程,并延迟一秒后被MyMsgReceiver执行

 

例3,重复执行的消息

sp_msg_t msg=msg_t::s_create(222,0,0);

msg->set_repeat(3);

msg->set_utc_interval(1000/get_tick_count_res(user_clock_t::instance()));

msg->set_receiver(sp_msg_rcver_t(new MyMsgReceiver_t(),true));

msg_proxy->post_msg(msg);

该消息将被发往msg_proxy所属的线程,并每隔一秒被MyMsgReceiver执行一次,共执行4次

 

例4,Timer事件

sp_msg_t msg=msg_t::s_create(222,0,0);

msg->set_repeat(-1);

msg->set_utc_interval(1000/get_tick_count_res(user_clock_t::instance()));

msg->set_receiver(sp_msg_rcver_t(new MyMsgReceiver_t(),true));

msg_proxy->post_msg(msg);

该消息将被发往msg_proxy所属的线程,并每隔一秒被MyMsgReceiver执行一次,永不停止。

 

受到实现的限制,上述延时消息和定时消息(Timer)都不会很准确,不适合用作时间精度要求高的场合。最小延时精度或Timer间隔要求不适合小于10ms。为尽可能提高上述延时和定时消息的时间精度,在调用s_tls_instance创建msg_proxy_t时,应指定“not empty”event_slot_t参数,这样当msg_proxy_t的驻留线程在调用其heart_beat返回idle时,可wait该"not empty" event slot, wait超时不会影响立即消息的响应,因为其会立即唤醒event slot, 但会影响延时消息和定时消息的精度,因为它们在延时到期时,并不会主动唤醒event slot, 只能等到wait“自然醒”,为尽量避免驻留线程空转,同时相对确保消息计时精度,可将wait超时设成get_min_delay_interval()返回值(当前所有延时消息或定时消息中最短的间隔时间)的1/2。测试表明,这样可获得较好效果。

另外,你可以通过往指定线程发送类型为MSG_REMOVE_MSG的消息删除已经发往该线程的消息,包括上面的Timer,既可删除指定类型的消息,也可一次删除属于该线程的所有消息。


总之,该消息服务是本项目的“动力系统”,为本项目搭建起异步执行框架。

 

简单原理示意图参:https://p-blog.csdn.net/images/p_blog_csdn_net/dreamfreelancer/EntryImages/20090613/msg_service.JPG

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值