踏上doubango学习之路!!
Posted 阿冬专栏
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了踏上doubango学习之路!!相关的知识,希望对你有一定的参考价值。
一,
学习原因:
需要储备点自己的知识了。由于目前笔者所做的是IMS,还是学习点开源的比较好。
查了一下开源的方案。里面相对Doubango靠谱的多,但它是一个IMS的开源方案,距离RCS5.1还有一段路要走。
因此就有了学习并开发doubango到RCS5.1想法。
选取doubango的原因:网上介绍Doubango的文章很多,这里就不多说了,一个字就是“靠谱”。
需要翻墙下载代码,编译也需要做点修改。我是做android开发的,编译时也费了点功夫,脚本环境啥的都需要配置。有一点事肯定的,在windows下你按照说明肯定是编不出来的。遇到错误百度或者检查下脚本就行了,总之是能折腾出来的。
Doubango是跨平台的,所有主流系统都有实现。在android中的实现是IMSDroid,他使用了android-ngn-stack作为协议栈。android-ngn-stack是包装了doubango的适配层(语言贫乏,找不到合适的词)。
tinyWRAPJNI.java是JNI的接口,里面列出了所有Doubango的对外接口。在org.doubango.tinyWRAP这个包里,还有很多结构体,在Doubango中都有对应。
Doubango最新的版本是2.0,以前的1.0已经弃用。建议只看2.0版本。
在目录下可以看到如下目录:
bindings
documentation
tinyDAV
tinyDEMO
tinyHTTP
tinyIPSec
tinyMEDIA
tinySAK
tinySIP
...
其中binding是用来binding到不同平台上用的。里面_common是通用的部分,csharp/java/objc/perl/etc则是对应不同平台的绑定。
documentation放的是一些文档,虽然简单,但还是很有用,建议先看看这些文档。
tiny***是各个功能模块,根据名字能大概判断出来干啥的,其中tinySAK中SAK是Swiss Arm Knigf,也就是一个工具箱。这个很重要,建议看完doc再把这个目录下的各个文件看一遍.
二,
Doubango代码学习(二):well-defined objects
虽刚开始看Doubango源码时还是让我郁闷不已,不过还好,渐渐适应了。刚开始最让我头疼的就是well-defined objects
由于Doubango是用纯C开发的,没有面向对象功能,就加上了这么一个东西。当然这套机制还是很不错的,毕竟纯C搞大型项目还是需要有些技巧的。
well-defined objects是用来模拟面向对象功能的,其中主要就是内存和指针的管理的,毕竟现实的malloc和free太容易出问题了。
源代码在tsk_object.c中:
先看一个结构体
typedef struct tsk_object_def_s
//! The size of the object.
tsk_size_t size;
//! Pointer to the constructor.
tsk_object_t* (* constructor) (tsk_object_t *, va_list *);
//! Pointer to the destructor.
tsk_object_t* (* destructor) (tsk_object_t *);
//! Pointer to the comparator.
int (* comparator) (const tsk_object_t *, const tsk_object_t *);
tsk_object_def_t;
这个定义了一个类对象的结构体,其中size是对象大小,constructor是对象构造函数,destructor是对象销毁函数,comparator是对象比较函数。
同java一样,对于每个类(object)都有一个实际的".class"对象存在,这里也是一样。tsk_object_def_t相当于".class"的结构体。
如对于tsip_stack_t类,存在一个类对象tsip_stack_def_t
static const tsk_object_def_t tsip_stack_def_s =
sizeof(tsip_stack_t),
tsip_stack_ctor,
tsip_stack_dtor,
tsk_null,
;
const tsk_object_def_t *tsip_stack_def_t = &tsip_stack_def_s;
构造stack对象时,需要用到类对象,方法如下:
stack = tsk_object_new(tsip_stack_def_t).
tsk_obj_new函数就不贴了,大致就是分配一块size大小的内存,然后调用构造函数初始化,再把引用置成1.
当用完一个对象时,避免使用tsk_object_delete来释放,应当使用tsk_object_unref。把引用值减一时,会检查引用是否为0,如果为0就自动释放掉内存。
当别人传进来一个对象时,如果你要异步地使用它,最好是tsk_object_ref一下,把引用值+1。当使用完以后,再unref一下。
还有就是模拟继承的方法,这个最好能不用就不用,因为我感觉模拟的不好,会带来很多弊端。
总结一下:
当你需要一个新类时,你要先生成一个类对象,如下:
static const tsk_object_def_t tsip_XXX_def_s =
sizeof(tsip_XXX_t), //大小
tsip_XXX_ctor, //构造函数
tsip_XXX_dtor, //销毁函数
tsk_XXX_cmp, //比较函数
;
然后定义你的类tsip_XXX_t
typedef struct tsip_XXX_s
TSK_DECLARE_OBJECT;//这个必须带,而且必须放到第一位。
... //定义你自己的成员.
tsip_XXX_t;
这样你的类就定义完成了,当想生成一个对象时,要调用tsk_object_new来生成。
当想销毁一个对象时要调用tsk_object_unref.
因为没有自动处理引用,当你需要保存一个对象是,调用tsk_object_ref来增加引用。一旦你增加了引用,必须在对应的unref处理,来保证不会有内存泄露。
当你比较两个对象是否相等时,调用tsk_object_cmp来比较.
Doubango代码学习(三):fsm
状态机是事务处理中必不可少,在tsk_fsm.h/c里就实现了状态机机制,而且在各个协议层都会用到大量的状态机。只有学好了状态机,才能真正了解Doubango的运行机制,才能对Doubango修改和定制。
要定义一个状态机,首先需要有状态(state),还有事件(action),还有对事件的处理(exec)。下面是状态机的结构:
typedef struct tsk_fsm_s
TSK_DECLARE_OBJECT;
unsigned debug:1;
tsk_fsm_state_id current;
tsk_fsm_state_id term;
tsk_fsm_entries_L_t* entries;
tsk_fsm_onterminated_f callback_term;
const void* callback_data;
TSK_DECLARE_SAFEOBJ;
tsk_fsm_t;
TSK_DECLARE_OBJECT学了前一批就知道是啥了,略过。current和term分别是起始状态和结束状态;callback_term是状态机结束时调用的清理函数;callback_data是状态机构造时保存的数据,待到状态机结束时用的;entries是个list,里面对应的是每个状态对每个事件的处理,结构体如下:
typedef struct tsk_fsm_entry_s
TSK_DECLARE_OBJECT;
tsk_fsm_state_id from;
tsk_fsm_action_id action;
tsk_fsm_cond cond;
tsk_fsm_state_id to;
tsk_fsm_exec exec;
const char* desc;
tsk_fsm_entry_t;
其中from是当前状态,action是输入的动作,cond是个返回boolean的函数,如果调用cond返回false,则说明当前action无效,状态机不做任何变化;如果是true则把状态切换到to,然后执行exec。可以阅读以下tsk_fsm_act函数,里面很简单就不贴上了。
总结一下FSM的用法:
1,调用tsk_fsm_create创建一个状态机,两个参数分别是起始状态(current)和结束状态(term)。
2,调用tsk_fsm_set_callback_terminated来设置callback_term和callback_data.
3,调用tsk_fsm_set来初始化fsm。这一步设置fsm_entrys,查看一下TSK_FSM_ADD宏还有调用的地方,看看如何使用的。
4, 至此状态机初始化完成,之后调用tsk_fsm_act来为fsm输入事件和数据。
5,其他还有一些辅助函数和宏,检查一下tsk_fsm.h吧。
Doubango代码学习(四):ragel state和message parser
使用SIP不可避免遇到SIP的解析。 Doubango使用了Ragel来解析的,效率当然是高,但是代码实在是晦涩难懂。我曾经看了tsip_message_parser_execute一整天的时间,单步跟踪一步一步的看了好几遍都没有看出是怎么回事。
一般遇到这种怎么也看不懂的情况,可以肯定的是你走错了路,走到了死胡同。事实却是如此,这个代码是机器生产的。由于个人专业知识的贫瘠,从来没有听说过Ragel这个东西。简单的说就是一种有限状态机语言,专门处理各种解析用的,然后可以把它的语言翻译成主流的各种语言。
对于SIP的解析,就不要看翻译过的代码了,要看对应的“.rl”文件,如"./ragel/tsip_parser_message.rl"。
由于目前没有需要,就打算暂时忽略掉。如果有需要再继续研究Ragel。
Doubango代码学习(五):runnable
代码在tsk_runnable.h(c)中。
有一个tsk_runnable_t的结构体,如下:
typedef struct tsk_runnable_s
TSK_DECLARE_OBJECT;
const tsk_object_def_t *objdef;
tsk_thread_handle_t* h_thread[1];
tsk_runnable_func_run run;
tsk_thread_id_t id_thread; // no way to get this value from "h_thread" on WINXP
tsk_semaphore_handle_t *semaphore;
tsk_bool_t running;
tsk_bool_t started;
tsk_bool_t initialized;
/** whether the enqueued data are important or not.
* if yes, the thread will not be joined until all data in the queue have been consumed.
* default value: tsk_false
*/
tsk_bool_t important;
int32_t priority;
tsk_list_t *objects;
tsk_runnable_t;
可以看出,它是一个类,但这个类没有实例化过,全部是作为基类用的。可以理解为虚类(当然,这里面也没有这个概念)。派生类有tsip_stack_t,tnet_transport_t, tdav_runnable_video_t等。继承直接在第一句包含TSK_DECLARE_RUNNABLE就行了。
tsk_runnable_t顾名思义,就是可自行的。当你需要一个线程执行你的任务时,这就是一个比较好的选择。
总结一下用法:
1,编写一个结构体继承tsk_runnable_t,方法就是把TSK_DECLARE_RUNNABLE放到结构体的第一句就行了。
2,由于TSK_DECLARE_RUNNABLE继承于OBJECT,因此你也需要按照之前的OBJECT章节描述的,来定义你的类对象(包括构造函数,销毁函数和比较函数)。
3,用tsk_object_new来生成你的对象。
4,tsk_runnable_start来执行你的对象。注意要先设置好run函数。一般都是用来事件处理,run函数读取事件,然后处理。循环处理代码夹在TSK_RUNNABLE_RUN_BEGIN和TSK_RUNNABLE_RUN_END之间,用TSK_RUNNABLE_POP_FIRST来取出消息。消息生产者用TSK_RUNNABLE_ENQUEUE_OBJECT来发送消息。
5,tsk_runnable_stop来停止你的对象。
6, 还有一些辅助的宏,注意他们的用法,如TSK_RUNNABLE_RUN_BEGIN, TSK_RUNNABLE_RUN_END,TSK_RUNNABLE_ENQUEUE_OBJECT等。
Doubango代码学习(六):timer
一个系统肯定少不了timer,在doubango中关于timer的声明(实现)是在tsk_timer.h(c)中。
总结一下timer的用法:
1,调用tsk_timer_mgr_global_ref获取timer mgr的引用。注意当使用完时一定要tsk_timer_mgr_global_unref,避免内存泄露。
2,调用tsk_timer_mgr_global_start来启动timer mgr。注意这个是可以多次调用无副作用的。
3,tsk_timer_mgr_global_schedule来启动一个timer,参数看字面意思即可理解。返回一个timer_id,如果启动多个timer使用相同的callback函数或者有需要cancel的情况,则要保存这个id。
4,tsk_timer_manager_cancel用来取消一个timer。
以上是使用global timer mgr的方法,对于一般情况足够了,但如果你需要自己的timer mgr也是可以的,调用对应的接口可以create,start,schedule和stock等操作。
下面来看一下timer的实现:
typedef struct tsk_timer_manager_s
TSK_DECLARE_RUNNABLE;
void* mainThreadId[1];
tsk_condwait_handle_t *condwait;
tsk_mutex_handle_t *mutex;
tsk_semaphore_handle_t *sem;
tsk_timers_L_t *timers;
tsk_timer_manager_t;
这是tm的结构体,可以看出是一个Runnable,按照上一篇讲的,它是可以执行的。timer mgr start函数中确实执行了它。
run函数代码就不贴了。在run函数里又启动了一个线程(引用保存在mainThreadId里)。然后接受event(timer),调用callback函数。它说接受的event都是来自于刚刚启动的那个线程。
在__tsk_timer_manager_mainthread函数中有个while循环,第一句就是tsk_semaphore_decrement(manager->sem);,可以看出这是一个生产者-消费者的问题,生产者是tsk_timer_manager_schedule。在之后的语句中先取出第一个timer,然后跟当前时间相比,如果已经超时,这发送消息到run函数。如果没有超时则调用tsk_condwait_timedwait阻塞对应的时间。
此时如果再有新的timer加入(调用schedule函数)。则先把timer按照升序插入到队列中,然后调用tsk_semaphore_increment和tsk_condwait_signal(manager->condwait);,前者用来增加信号量,后者则结束掉刚才的阻塞。这样run里的while循环可以继续执行。。。
如果到超时之前一直没有新的timer,则tsk_condwait_timedwait会超时,然后继续执行。。。
以上是关于踏上doubango学习之路!!的主要内容,如果未能解决你的问题,请参考以下文章