XMPP eJabberd 的花名册和出席 (1)

DavidAlphaFox · 发布于 2017年12月21日 · 450 次阅读
84794b

什么是花名册和出席

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用来获取名册

<iq from='[email protected]/balcony'
       id='bv1bs71f'
       type='get'>
    <query xmlns='jabber:iq:roster'/>
  </iq>

而服务器必须做出应答,即便是用户的名册为空的时候也不应返回error,而是返回空的result

 <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应答

<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不存在局部修改这个花名册中联系人功能

<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。

<iq from='[email protected]/balcony'
       id='hm4hs97y'
       type='set'>
     <query xmlns='jabber:iq:roster'>
       <item jid='[email protected]'
             subscription='remove'/>
     </query>
   </iq>

需要注意的是,因为RFC3921,并没有定义花名册是带有版本的。因此会在流打开的时候通过下面的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进程)来序列处理。

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主要负责花名册的查询工作。

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的操作,更多是依赖数据库事务的遍历写入操作。

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的出席和花名册机制的缺陷。

共收到 0 条回复
84794b DavidAlphaFox eJabberd 的花名册和出席 (2) 中提及了此贴 12月28日 11:16
84794b DavidAlphaFox eJabberd 的花名册和出席 (3) 中提及了此贴 01月08日 15:16
需要 登录/注册 后方可回复, 如果你还没有账号请点击这里 注册