本文由逍遥子撰写,转发请标注原址:
http://write.blog.csdn.net/postedit/21462255
一、 Mosquito的核心功能分析
3.1、订阅树
Mosquitto通过订阅树的方式来管理所有的topic以及客户端的订阅关系,它首先将所有的topic按照/分割并组织成一棵树结构,从根节点到树中的每个节点即组成该节点所对应的一个topic,每个topic都保存一个订阅列表,该订阅列表中保存了所有订阅当前topic的客户端信息。例如有如下订阅关系:
客户端a1,a2,a3订阅了topic:A1/B1/C1
客户端b1,b2订阅了topic:A2/B2/C2
客户端c1,c2订阅了topic:A1/B1/C3
客户端d1订阅了topic:A2/B3
则在mosquitto程序中需要先将topic按照/进行分割,然后将分割后的topic片段组织成订阅树,上述订阅树的示意图为图3-1:
图3-1 订阅树示意图
Mosquitto程序在实现中根据topic消息的性质将订阅树分为两颗子树:业务子树和系统子树;mosquitto程序中将topic分为两种类型来处理:系统topic和业务topic,前者主要用于发布和维护mosquitto内部的系统消息,后者的topic是用户订阅的业务topic,做这种区分的原因是因为这两种的类型的topic性质和实现方式上有许多差别,这种差别主要体现在以下4点:
1)生存周期不同,系统topic无论是否有用户订阅都会存在与订阅树中,而业务topic必须有客户端订阅才能存在(除非其消息字段retain设置为1);
2)创建方式不同,系统topic在消息发布时进行创建,业务topic即可以在订阅时创建也可以在消息发布时创建(此时需要该消息retain字段设置为1);
3)消息保存方式不同,凡是发布到系统topic的消息都会被保存下来,业务消息将直接挂到订阅列表的各context的消息队列中,如果没有连接订阅或未设置其retain字段,消息将不会被保存下来;
消息的retain字段是否被设置在函数mqtt3_handle_publish进行检查,在该函数中有如下代码:
retain = (header & 0x01);
该代码可获取消息头部的第一个bit位,在mqtt3.1协议中,该为用于表示消息的类型是否为retain。
订阅树在程序中的采用孩子—兄弟链表法来表示。其主要涉及的数据结构是:
struct _mosquitto_subhier
struct _mosquitto_subleaf
3.1.1、订阅树的搭建
1、创建订阅树
mosquitto程序启动时将创建订阅树,该过程将创建三个节点:订阅树总根节点、业务子树根节点和系统子树根节点,这两个子树根节点作为订阅树总根节点的两个子节点,其中订阅树总根节点和业务子树的根节点中topic成员的值为空字符串,而系统子树根节点中保存的值为“$SYS”,如图3-2所示。
图3-2 订阅树的创建
订阅树的创建主要在文件database.c中mqtt3_db_open函数里实现。订阅树中节点的数据结构为struct _mosquitto_subhier,订阅树采用“孩子—兄弟”链表法保存。
2、搭建订阅树
在订阅树中,系统子树与业务子树的搭建过程不一样,系统子树是在系统消息发布时创建,而业务子树创建过程即可以在消息发布时创建也可以在客户端订阅时才创建。
1)系统子树搭建过程
Mosquitto中,系统子树在发布系统消息时,自动检测topic片段是否存在,如果不存在则在系统topic上创建节点以搭建订阅树。例如,mosquitto程序启动时,将首先向系统topic:$SYS/broker/version发送一条版本消息“mosquittoversion 1.2”,此时订阅树的系统子树只有一个根节点,如图3.2所示,其搭建过程如下:
(1)将topic按照”/”分成topic片段,系统Topic:$SYS/broker/version将被分割为$SYS、broker、version三部分。
(2)根据第一个topic片段“$SYS”遍历订阅树的子节点找到系统子树的根节点。
(3)根据topic下一个片段“broker”查找系统子树;此时系统子树中不存在topic片段“broker”的节点,则为订阅树产生一个节点,其数据结构为:struct_mosquitto_subhier。此时订阅树由图3-2变为图3-3所示:
图3-3添加broker节点之后的订阅树
上述过程在函数mqtt3_db_messages_queue中调用函数_sub_add来完成。
(4)依此处理topic剩下的片段,在系统子树中添加topic片段“version”,该过程通过递归调用函数_sub_add来完成。添加完“version”片段之后的订阅树如图3-4所示。
图3-4添加version之后的订阅树
系统子树搭建过程中,所用到的函数调用关系如下图3-5所示
图3-5 系统子树搭建的函数调用关系
2) 业务子树搭建过程
业务子树的搭建分为两种类型:一种在订阅时创建;一种是在消息发布时创建,这种方式与系统topic的创建过程一样,因此下面的内容将主要描述前一种方式。
在mosquitto程序运行期间,收到一条客户端的订阅请求后将调用函数mqtt3_sub_add将该客户端挂到对应的业务子树节点的订阅列表中,此时,如果所订阅树中不存在客户端所订阅的topic,则会自动为之添加相应的节点,此过程即为订阅树的业务子树搭建过程。例如,在mosquitto程序启动时(此时订阅树如图3-2所示)客户端订阅了topic:year/month/events,业务子树的搭建过程为:
(1)将topic按照”/”分成topic片段,topic:year/month/event将被分割成:year、month、events三个topic片段;此过程在函数mqtt3_sub_add中完成。
(2)遍历订阅树的第一级子节点,以查找业务子树的根节点,找到业务子树的根节点之后进行后续的处理。此过程在函数mqtt3_sub_add中完成。
(3)依此处理三级topic片段:year、month、events,此过程与系统topic的添加过程相似,如果订阅树中不某个存在topic片段,则为订阅树添加此节点,添加完成之后的订阅树如图3-6:本条功能在函数_sub_add中完成。
图3-6 添加业务topic之后的订阅树
业务子树搭建过程中,所用到的函数调用关系如下图3-7所示
图3-7 业务子树搭建的函数调用关系