这一次,主要分析client subscribe 某个topic 的处理流程。

由protocol开始

是的,还是要从protocol开始,至于为什么,之前就说过了。

subscribe 类型的packet的处理是:

 %% 直接过滤掉topic 为空的情况
process(?SUBSCRIBE_PACKET(PacketId, []), State) ->
send(?SUBACK_PACKET(PacketId, []), State); process(?SUBSCRIBE_PACKET(PacketId, TopicTable), State = #proto_state{session = Session}) ->
%% 组装client 信息
Client = client(State),
%% 检查ACL
...
%% session 为clientid 对应的session pid
%% TopicTable 为 [{TopicName, QoS}]
emqttd_session:subscribe(Session, PacketId, TopicTable)
...
;

1、过滤掉topictable 为空的情况

2、组装必要的client 信息,完成ACL检查

3、获取clientid 对应的session pid,并调用emqttd_session:subscribe/3 函数

emqttd_session 模块处理

emqttd_session:subscribe/3 只是一个接口函数,实际的处理逻辑是在emqttd_session 模块的handle_cast callback 中实现。

-spec(subscribe(pid(), mqtt_packet_id(), [{binary(), mqtt_qos()}]) -> ok).
subscribe(SessPid, PacketId, TopicTable) ->
From = self(), %%这里的self 是client process id
AckFun = fun(GrantedQos) ->
From ! {suback, PacketId, GrantedQos}
end,
gen_server2:cast(SessPid, {subscribe, TopicTable, AckFun}).

接口函数的定义如上。

handle_cast callback 的实现如下:

handle_cast({subscribe, TopicTable0, AckFun},
Session = #session{client_id = ClientId,
                   %% subscription 是dict
subscriptions = Subscriptions}) ->
%% rewrite topic name 对topic name 做一些处理
Subscriptions1 = lists:foldl(
fun({Topic, Qos}, SubDict) ->
case dict:find(Topic, SubDict) of
{ok, Qos} ->
%% 已经存在,并且QoS 未更新,所以什么都不需要做
SubDict;
{ok, OldQos} ->
%% 已经存在,但是QoS 更新,所以,需要更新一下
emqttd_server:update_subscription(ClientId, Topic, OldQos, Qos),
dict:store(Topic, Qos, SubDict);
error ->
%% 不存在,直接添加
emqttd:subscribe(ClientId, Topic, Qos),
dict:store(Topic, Qos, SubDict)
end
end, Subscriptions, TopicTable),

更新subscribe

更新subscribe,也就是调用emqttd_server:update_subscription/4 。

emqttd_server 也是由pool 组织的gen_server进程,主要作用是subscription 的增删改查,subscription 信息是保存在 subscription mnesia table 中的,subscription mnesia table的字段信息如下:

-record(mqtt_subscription,
{subid :: binary() | atom(),
topic :: binary(),
qos = 0 :: 0 | 1 | 2
}).

其中,subid 即为subscriber id,也就是clientid,topic 即为topic的名称。

而,update subscription 的逻辑:

 %% 外部接口
update_subscription(ClientId, Topic, OldQos, NewQos) ->
call(server(self()), {update_subscription, ClientId, Topic, ?QOS_I(OldQos), ?QOS_I(NewQos)}). handle_call({update_subscription, ClientId, Topic, OldQos, NewQos}, _From, State) ->
if_subsciption(State, fun() ->
OldSub = #mqtt_subscription{subid = ClientId, topic = Topic, qos = OldQos},
NewSub = #mqtt_subscription{subid = ClientId, topic = Topic, qos = NewQos},
%% 使用事物
mnesia:transaction(fun update_subscription_/2, [OldSub, NewSub]),
set_subscription_stats()
end), ok(State); update_subscription_(OldSub, NewSub) ->
%% 删除旧的 subscription
mnesia:delete_object(subscription, OldSub, write),
%% 写入新的 subscription
mnesia:write(subscription, NewSub, write).

因为 subscription mnesia table的类型为bag,也就是一个clientid 可能会和多个topic 相对应,所以,不能依据key 进行delete,必须使用delete_object的方式。

创建subscribe

create subscribe的处理略微有些绕,不知道是作者有意而为之,还是其他什么原因。

首先,create subscribe的入口函数在emqttd module中,

-spec(subscribe(binary(), binary(), mqtt_qos()) -> {ok, mqtt_qos()}).
subscribe(ClientId, Topic, Qos) ->
emqttd_server:subscribe(ClientId, Topic, Qos).

在此调用emqttd_server:subscribe/3 函数,并请求emqttd_server 进程,emqttd_server 进程调用handle_call callback 函数,处理请求。

 %% 外部接口
-spec(subscribe(binary(), binary(), mqtt_qos()) -> ok).
subscribe(ClientId, Topic, Qos) ->
%% 这里的self 是emqttd_session 进程,这个调用是在emqttd_session
%% module 中的 handle_cast callback 发起的
From = self(),
call(server(From), {subscribe, From, ClientId, Topic, ?QOS_I(Qos)}). handle_call({subscribe, SubPid, ClientId, Topic, Qos}, _From, State) ->
%% call pubsub process
pubsub_subscribe_(SubPid, Topic),
if_subsciption(State, fun() ->
%% 将subscription 信息写入到 subscription mnesia table 中
add_subscription_(ClientId, Topic, Qos),
set_subscription_stats()
end),
%% monitor session pid,当起DOWN 之后,去掉subscribe 并移除相关信息
ok(monitor_subscriber_(ClientId, SubPid, State)); %% @private
%% @doc Call pubsub to subscribe
pubsub_subscribe_(SubPid, Topic) ->
case ets:match(subscribed, {SubPid, Topic}) of
[] ->
emqttd_pubsub:async_subscribe(Topic, SubPid),
ets:insert(subscribed, {SubPid, Topic});
[_] ->
false
end.

L26处,用了subscribed ets table,记录session pid subscribe 的所有topic,这样在 session pid DOWN的时候,就可以移除所有的topic 中session pid 相关的信息了。

而,emqttd_pubsub 同样是由pool 组织的gen_server 进程。

 %% 外部接口,发起请求
-spec(async_subscribe(binary(), pid()) -> ok).
async_subscribe(Topic, SubPid) when is_binary(Topic) ->
cast(pick(Topic), {subscribe, Topic, SubPid}). handle_cast({subscribe, Topic, SubPid}, State) ->
%% 实际的处理函数
add_subscriber_(Topic, SubPid),
{noreply, setstats(State)}; add_subscriber_(Topic, SubPid) ->
%% 检查该Topic 是否已经存在
%% 若不存在,则先增加{Topic,Node}信息,为多node 场景服务
...
ets:insert(subscriber, {Topic, SubPid}). %% 这里的subscriber 是一张ets table,接下来的publish 主要就是用的这张表 ********

至此,subscribe 操作的处理逻辑就ok了。

总结

应该也不需要,只是这代码贴的有点多了。(示意图待补)

emqtt 3 (我要subscribe 这个topic)的更多相关文章

  1. emqtt 1 (初初初初稿)

    第一篇,先简单分析一下整个emqtt 的大致结构,包括两个部分: 1.message packet 类型 2.message 流向 message packet 类型 P1:mqtt_packet 的 ...

  2. dojo.publish 和 dojo.subscribe

    原文链接:http://www.cnblogs.com/didi/archive/2010/06/13/1757894.html //dojo.publish 和 dojo.subscribe  :d ...

  3. javascript设计模式——Publish/Subscribe

    推荐阅读http://dev.housetrip.com/2014/09/15/decoupling-javascript-apps-using-pub-sub-pattern/ 我们先引出问题的所在 ...

  4. ActiveMQ——activemq的详细说明,queue、topic的区别(精选)

    JMS中定义了两种消息模型:点对点(point to point, queue)和发布/订阅(publish/subscribe,topic).主要区别就是是否能重复消费. 点对点:Queue,不可重 ...

  5. 观察者模式(Observer)和发布(Publish/订阅模式(Subscribe)的区别

    观察者模式(Observer)和发布(Publish/订阅模式(Subscribe)的区别 在翻阅资料的时候,有人把观察者(Observer)模式等同于发布(Publish)/订阅(Subscribe ...

  6. dojo topic 发布与订阅 小例子可以参考下

    <!DOCTYPE html><html> <head> <title></title></head> <body> ...

  7. 【RocketMQ】同一个项目中,同一个topic,可以存在多个消费者么?

    一.问题答案 是不可以的 而且后注册的会替换前注册的,MqConsumer2会替换MqConsumer,并且只结束tag-2的消息 /** * @date 2019/05/28 */ @Compone ...

  8. MQTT研究之EMQ:【CoAP协议应用开发】

    本博文的重点是尝试CoAP协议的应用开发,其中包含CoAP协议中一个重要的开源工具libcoap的安装和遇到的问题调研.当然,为了很好的将EMQ的CoAP协议网关用起来,也调研了下EMQ体系下,CoA ...

  9. 现代DOJO(翻译)

    http://dojotoolkit.org/documentation/tutorials/1.10/modern_dojo/index.html 你可能已经不用doio一段时间了,或者你一直想保持 ...

随机推荐

  1. JavaWeb -- Struts1 动态bean, 动态校验, 动态生成javascript 表单校验

    1. 动态formbean. 表单 JSP: <%@ page language="java" import="java.util.*" pageEnco ...

  2. JVM 深入浅出

    jvm 相信大家都有认知 在面试中也是经常遇见的希望接下来的这篇文章可以对你们带来帮助 java 的好处 : java这门语言之所以受广大的码农所喜爱是因为它不像C语言需要程序员自己手动分配内存空间 ...

  3. shell read 命令 (转)

    read命令 -p(提示语句) -n(字符个数) -t(等待时间) -s(不回显) 1.基本读取read命令接收标准输入(键盘)的输入,或其他文件描述符的输入(后面在说).得到输入后,read命令将数 ...

  4. 第一章 Burp Suite 安装和环境配置

    Burp Suite是一个集成化的渗透测试工具,它集合了多种渗透测试组件,使我们自动化地或手工地能更好的完成对web应用的渗透测试和攻击.在渗透测试中,我们使用Burp Suite将使得测试工作变得更 ...

  5. IOS-将任意对象存进数据库

    model // // HMShop.h // 将任意对象存进数据库 // // Created by apple on 14/11/20. // Copyright (c) 2014年 heima. ...

  6. Django的 CBV和FBV

    FBV CBV 回顾多重继承和Mixin 回到顶部 FBV FBV(function base views) 就是在视图里使用函数处理请求. 在之前django的学习中,我们一直使用的是这种方式,所以 ...

  7. 大马猴队-Alpha阶段项目复审

    队名 优点 缺点 名次 菜鸡互坑队 经典游戏,情怀加分. 刷新的苹果会在蛇身上出现  14 菜鸡互啄 利用python语言实现git版本管理,典型用户清晰. 没有很好地分析用户痛点,没有测试计划  4 ...

  8. hp g6 2328tx 加装ssd 机械硬盘安装到光驱位置 问题小结

    惠普 g6 笔记本从13年入手至今,三年有余,性能啥的开始跟不上了,所有入手了一块ssd,闪迪(sanDisk)加强版 240G的固态硬盘,准备升下级.狗东下午下单,第二天早上7点多到的,把老硬盘换下 ...

  9. dateTimePicker编辑状态下,取值不正确的问题

    当对dateTimePicker进行编辑,回车,调用函数处理dateTimePicker的value值时,其取值结果是你编辑之前的值,而不是你编辑后的值,虽然dateTimePicker.text的值 ...

  10. 3天学习完AngularJS基础内容小结

    简介:AngularJS 是一个 JavaScript 框架.它是一个以 JavaScript 编写的库. 一.AngularJS大致功能模块 二.页面交互变得简单 1.示例:计算价格 <htm ...