XMPP eJabberd 的 IQ 处理

DavidAlphaFox · 发布于 2017年11月28日 · 316 次阅读
84794b

XMPP的IQ

什么是IQ机制

XMPP将信息系统抽象的非常好,将信息直接划分成了下面三大类:

  1. 纯粹的消息"推送"机制,将实体推送信息到另一个实体, 类似发生在email系统里的通讯一样。
  2. 特定的"广播"或"发布-订阅"机制, 这里多个实体接收关于他们订阅的一个实体的信息。
  3. 是一个"请求-应答"机制, 类似某些情况下的超文本传输协议HTTP。

从上面的分类就可以看出来,是一种双向的,异步的,一对一或一对多的通信机制。是一种单向的,一对多广播机制。至此可以看出,剩下的就是请求应答机制了。

IQ机制怎么工作的

XMPP中规定IQ消息必须满足下面的条件

  1. 必须有id属性,以便于实现跟踪和请求应答机制
  2. 必须有type属性,以便于告知操作类型,并且必须是下面相应的值
    • get 用于请求信息, 查询需要什么数据以完成更多操作, 等等。
    • set 用于完成某个操作提供需要的数据, 设置新值, 取代旧值, 等等。
    • result 用于对成功的get或set请求的应答。
    • error 用于报告关于处理或递送一个get或set请求时发生的错误。
  3. 接收到类型为get或set的IQ请求的实体必须返回一个类型为result或error的IQ应答. 该应答必须保留请求中的id属性(或为空,如果生成的节没有包含id属性)。
  4. 接收到类型为result或error节的实体不能发送更多的类型为"result"或"error"的IQ应答来应答; 但是 请求实体可以发送另一个请求(例如, 使用类型为set的IQ对之前在get/result对中查询到的信息提供特定的信息)。
  5. 类型为get或set的IQ节必须严格地包含一个子元素, 用来定义特定请求。
  6. 类型为result的IQ节必须包含零或一个子元素。
  7. 类型为error的IQ节可以包含相关的get或set子元素并且必须包含一个子元素。

IQ的价值

至此,可以看出IQ机制是在XMPP当中提供了一个完整的RPC方案,可以是同步的,也可以是异步的,这样就可以非常方便的去扩展XMPP了。

eJabberd是如何处理IQ请求的

eJabberd中的IQ是如何触发的

在上一篇介绍 eJabberd的路由系统 中就提到,当消息路由到ejabberd_sm模块时,且JID的resource部分为空了,就有可能触发IQ。在ejabberd_sm中IQ的handler都保存在sm_iqtable这个命名的ETS中。当ejabberd_c2s进程在这张ETS中找到相应的IQ处理模块的时候,如果处理模块没有额外参数的情况下,直接就使用模块对应函数处理,但是当模块有额外参数的时候,就会使用gen_iq_handler:handle来进行相应处理。细节可以看下面的代码

process_iq(From, To, Packet) ->
    IQ = jlib:iq_query_info(Packet),
    case IQ of
        #iq{xmlns = XMLNS} ->
            Host = To#jid.lserver,
            %% IQ处理器的NS部分是全局唯一性的
            %% 所以在查找的时候,只有一条记录
            case ets:lookup(sm_iqtable, {XMLNS, Host}) of
                [{_, Module, Function}] ->
                    ResIQ = Module:Function(From, To, IQ),
                    if
                        ResIQ /= ignore ->
                            ejabberd_router:route(To, From,
                                                  jlib:iq_to_xml(ResIQ));
                        true ->
                            ok
                    end;
                [{_, Module, Function, Opts}] ->
                    gen_iq_handler:handle(Host, Module, Function, Opts,
                                          From, To, IQ);
                [] ->
                    Err = jlib:make_error_reply(
                            Packet, ?ERR_SERVICE_UNAVAILABLE),
                    ejabberd_router:route(To, From, Err)
            end;
        reply ->
            ok;
        _ ->
            Err = jlib:make_error_reply(Packet, ?ERR_BAD_REQUEST),
            ejabberd_router:route(To, From, Err),
            ok
    end.

gen_iq_handler模块

eJabberd将IQ处理方案划分成下面四类:

  1. no_queue 无队列
  2. one_queue 一个队列
  3. {queues, N} 多个队列负载均衡
  4. parallel 并发处理

为了方便管理,eJabberd统一使用gen_iq_handler来进行注册,当然也保留了不使用gen_iq_handler来注册IQ处理机制的方式。当不使用gen_iq_handler注册IQ处理机制的时候,相当于gen_iq_handler的no_queue模式,IQ处理都会在ejabberd_c2s进程中直接执行,唯一的差别是,gen_iq_handler会默认进行异常捕获。

gen_iq_handler是如何注册IQ处理模块的

eJabberd中的IQ处理模块都会被注册到ejabberd_sm进程所拥有的sm_iqtable这个ETS中,整个过程都是依赖ejabberd_sm这个gen_server的消息有序性来保证事务的。当不小心注册了一个同名的IQ,最后写入ETS的模块会被触发。

gen_iq_handler在注册IQ模块的时候,会检测上面所说的处理方案,如果是one_queue或 {queues, N} 就会通过ejabberd_iq_sup这个supervisor产生一个或多个gen_iq_handler进程,并将这些进程的Pid作为附加参数注册到sm_iqtable中。

gen_iq_handler是如何处理IQ

当使用gen_iq_handler:handle来触发IQ处理的时候,会出现下面几种情况。

  1. no_queue 直接使用调用者的ejabberd_c2s 进程来处理,默认捕获异常
  2. one_queue 使用注册时候产生的gen_iq_handler来进行排队处理
  3. {queues, N} 使用注册时候产生的多个gen_iq_handler中的任意一个(随机负载均衡)来进行处理
  4. parallel 使用spawn生成一个无关联进程进行处理

总结

XMPP的IQ机制给XMPP带来了非常强的扩展能力,eJabberd中又将IQ处理方案进行了细化。我们甚至可以将eJabberd服务器当作一个强力的RPC业务处理服务器,是不是很有意思。

共收到 0 条回复
84794b DavidAlphaFox 超级总线 eJabberd 中提及了此贴 12月05日 21:16
需要 登录/注册 后方可回复, 如果你还没有账号请点击这里 注册