eJabberd 的花名册和出席 (1)

什么是花名册和出席

XMPP的RFC6120 Extensible Messaging and Presence Protocol (XMPP): Core 非常详细的介绍了XMPP的核心协议。核心协议虽然非常好的完成了数据流交换机制,以及建立了非常强大的可扩展体系,但是这还不能称为即时通信系统。因此XMPP在RFC6121 Extensible Messaging and Presence Protocol (XMPP):Instant Messaging and Presence中详细的介绍了,花名册,出席和端到端消息交换。

花名册

作为成熟的即时通讯系统,就必然有联系人的管理和存储机制,花名册就是XMPP用来进行管理和存储一个用户的好友们的机制。

出席

XMPP为了让用户知道自己的联系人什么时候在线和可进行通讯。XMPP建立了一种通知机制,当用户的联系人在线状态发生变化的时候,服务器会自动向用户发送通知,也就是出席机制。

花名册的使用

ver 属性

ver属性是一个标识名册信息的特定版本的字符串。它的值必须仅由服务器生成并且必须由客户端不透明地处理。服务器可以使用任何适当的方法来生成该版本ID, 类似名册数据的哈希值或一个严格递增的序列号。

因为数据同步天生就存在很多不确定性,因此在RFC6121中就做出了明确规定,一切以服务器为准,这样就解决了同步操作中的冲突问题。

名册管理

XMPP中名册管理是通过IQ处理来完成的。在逻辑上并没有复杂的操作,只是常见的增删改查,唯一的增加的就是XMPP的名册管理多出了一个服务器推送名册的功能。这是因为XMPP支持多客户端(resource)同时登录的原因。

名册获取

客户端会给服务器发送出下面的XML用来获取名册 XML <iq from='[email protected]/balcony' id='bv1bs71f' type='get'> <query xmlns='jabber:iq:roster'/> </iq> 而服务器必须做出应答,即便是用户的名册为空的时候也不应返回error,而是返回空的result XML <iq id='bv1bs71f' to='[email protected]/chamber' type='result'> <query xmlns='jabber:iq:roster' ver='ver7'> <item jid='[email protected]'/> <item jid='[email protected]'/> </query> </iq> <iq id='bv1bs71f' to='[email protected]/chamber' type='result'> <query xmlns='jabber:iq:roster' ver='ver9'/> </iq> 只有,且仅有名册服务不存在的时候,才应返回error应答 XML <iq id='bv1bs71f' to='[email protected]/chamber' type='error'> <error type='cancel'> <item-not-found xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/> </error> </iq>

名册修改

名册的修改是通过IQ的set操作完成的,所有的增加和修改都需要包含联系人的全部信息,因为XMPP不存在局部修改这个花名册中联系人功能 XML <iq from='[email protected]/balcony' id='ph1xaz53' type='set'> <query xmlns='jabber:iq:roster'> <item jid='[email protected]' name='Nurse'> <group>Servants</group> </item> </query> </iq> 而删除操作是复用了subscription,关于subscription属性会在第二部分关于出席里面进行详细介绍。在这个删除操作中会给subscription属性赋值为remove。 XML <iq from='[email protected]/balcony' id='hm4hs97y' type='set'> <query xmlns='jabber:iq:roster'> <item jid='[email protected]' subscription='remove'/> </query> </iq> 需要注意的是,因为RFC3921,并没有定义花名册是带有版本的。因此会在流打开的时候通过下面的XML来协商特性。 XML <ver xmlns='urn:xmpp:features:rosterver'/> 如果一个服务器支持带版本的名册,任何对花名册进行修改的操作都会引起版本的变动。

eJabberd中的花名册实现

mod_roster

eJabberd对花名册的管理是通过插件机制来完成,核心流程都是由mod_roster来完成的。eJabberd的花名册存储方式有多种方式,有Mnesia,Riak,LDAP和ODBC,并且这些存储引擎可以通过配置文件动态指定。

mod_roster在初始化过程中会将自己加入Hook中,并构建对应的存储后端。最重要的是,mod_roster对花名册的IQ处理默认是使用单队列(一个Erlang进程)来序列处理。 Erlang start(Host, Opts) -> %% 默认情况是使用一个队列完成所有处理的 IQDisc = gen_mod:get_opt(iqdisc, Opts, one_queue), TrackedFuns = [read_roster_version, write_roster_version, get_roster, get_roster_by_jid_t, get_subscription_lists, roster_subscribe_t, get_roster_by_jid_with_groups_t, update_roster_t, del_roster_t, read_subscription_and_groups ], %% 启动后端模块 gen_mod:start_backend_module(?MODULE, Opts, TrackedFuns), mod_roster_backend:init(Host, Opts), %% 注册roster相关的hook请求 ejabberd_hooks:add(roster_get, Host, ?MODULE, get_user_roster, 50), ejabberd_hooks:add(roster_in_subscription, Host, ?MODULE, in_subscription, 50), ejabberd_hooks:add(roster_out_subscription, Host, ?MODULE, out_subscription, 50), ejabberd_hooks:add(roster_get_subscription_lists, Host, ?MODULE, get_subscription_lists, 50), ejabberd_hooks:add(roster_get_jid_info, Host, ?MODULE, get_jid_info, 50), ejabberd_hooks:add(remove_user, Host, ?MODULE, remove_user, 50), ejabberd_hooks:add(anonymous_purge_hook, Host, ?MODULE, remove_user, 50), ejabberd_hooks:add(roster_get_versioning_feature, Host, ?MODULE, get_versioning_feature, 50), %% 添加roster的IQ处理流程 gen_iq_handler:add_iq_handler(ejabberd_sm, Host, ?NS_ROSTER, ?MODULE, process_iq, IQDisc). 对花名册的处理主要都是由mod_roster:process_iq完成的,process_iq封装了process_iq_get和process_iq_set,get主要负责花名册的查询工作。 Erlang process_iq_get(From, To, #iq{sub_el = SubEl} = IQ) -> LServer = From#jid.lserver, try %% 请求带版本了 AttrVer = xml:get_tag_attr(<<"ver">>, SubEl), %% 是否准许请求带版本 VersioningEnabled = roster_versioning_enabled(LServer), %% 得到DB中的版本 VersionOnDb = roster_version_on_db(LServer), %% 根据版本获取IQ {ItemsToSend, VersionToSend} = get_user_roster_based_on_version(AttrVer, VersioningEnabled, VersionOnDb, From, To), IQ#iq{type = result, sub_el = create_sub_el(ItemsToSend, VersionToSend)} catch _:_ -> IQ#iq{type = error, sub_el = [SubEl, ?ERR_INTERNAL_SERVER_ERROR]} end. get_user_roster_based_on_version的时候会去判断是否需要发送版本,以及花名册数据。在get_user_roster_based_on_version的时候,会直接使用roster_get这个hook进行数据获取,这样就给开发者一个机会,可以在不用修改mod_roster的情况下,客户端要获取的花名册的过程中对花名册内部条目的修改。

而process_iq_set的操作,更多是依赖数据库事务的遍历写入操作。 Erlang process_iq_set(#jid{lserver = LServer} = From, To, #iq{sub_el = SubEl} = IQ) -> #xmlel{children = Els} = SubEl, %% 使用roster_set来过滤所有的消息 ejabberd_hooks:run(roster_set, LServer, [From, To, SubEl]), %% 逐条处理消息 lists:foreach(fun(El) -> process_item_set(From, To, El) end, Els), IQ#iq{type = result, sub_el = []}. 此处需要重点注意的是,process_item_set这个函数,每修改一个花名册条目,就会增加花名册版本一次。例如说一共处理10个item,初始版本是1,处理完后的版本就是11了。

总结

eJabberd非常完整的实现了花名册部分,并且在多个关键点给出了Hook,让开发者可以非常容易的不修改mod_roster的情况下就能对花名册的一些功能进行扩展。后面的文章将会介绍出席,以及XMPP的出席和花名册机制的缺陷。

Power by Vultr and AiWiki
Copyright © 2019 David Fox All Rights Reserved.