Tigase插件 – packets是如何被session manager和plugins处理的

别来无恙 提交于 2019-12-04 00:58:47

本文翻译自 – http://www.tigase.org/content/how-packets-are-processed-sm-and-plugins
理解插件是如何工作的对开发插件是非常重要的,在不同的场景下由不同类型的插件来负责处理packet。在开始正式的编码工作之前,建议你现阅读一下下面的文档。

什么是IQ(储天行注:这一个部分是在翻译时额外添加的,原文中没有)

在开始之前,先介绍一下什么是IQ。这个东西出现在后面的很多地方。Google了一下,IQ的意思是Info/Query:它是一种请求和应答机制,和http有一些类似的地方。 IQ的语意允许一个实体向另一个实体发送请求,并从另一个实体获取应答。请求和应答当中的数据在IQ元素的第一级子节点(命名空间的声明)当中被定义,请求方实体可以通过id标签来跟踪交互过程。如此一来,IQ交互的数据交换模式就类似于“get/result” 或者 “set/result”(在某些情况下,也可能是get/error和set/error)。如有困惑请参考XMPP官方英文文档:http://xmpp.org/rfcs/rfc3920.html#stanzas-semantics-iq

Requesting                 Responding
  Entity                     Entity
----------                 ----------
    |                           |
    | <iq type='get' id='1'>    |
    | ------------------------> |
    |                           |
    | <iq type='result' id='1'> |
    | <------------------------ |
    |                           |
    | <iq type='set' id='2'>    |
    | ------------------------> |
    |                           |
    | <iq type='error' id='2'>  |
    | <------------------------ |
    |                           |

为了能够确保这种方式被执行,有如下规则需要遵守:

  1. 每一个IQ stanza都必须含有“id”标签和值
  2. 每一个IQ stanza都必须含有“type”标签和值,且值必须为如下的几种:
    • get — 是一个请求信息
    • set — 提供了所需要的数据,设置了新的值或者替换某些已经存在的值
    • result — 一个对应get或set并且执行成功的应答
    • error — 之前发送的一个get或set发生了处理或者传输错误
  3. 如果一个实体接收到了“set”或“get” IQ请求,那么它必须回复一个“result”或“error”应答,并且应答的id标签值必须与请求的id标签值保持一致。
  4. 如果一个实体接收到了“result”或“error” stanza,那么它一定不能再返回“result”或“error”应答;但是发出请求的实体可以可以继续发送下一个请求。
  5. 一个“get” 或 “set”类型的 IQ stanza必须包含并只包含一个子元素指明特定请求或应答的语义。
  6. 一个“result”类型的 IQ stanza必须包含零或一个子元素。
  7. 一个“error”类型的IQ stanza应该包含有两个子元素。第一个子元素包含着与之发生错误相对应的“get”或“set”,第二个子元素是<error/>元素。

plugin简介

插件是一段负责处理特定XMPP stanza的代码。有专门负责处理消息的插件,有专门负责处理在线状态的插件,有专门负责处理iq通讯录的插件,也有专门负责处理版本的插件。

插件通过xmlns和元素名来声明所有它“感兴趣”的那些在特定命名空间下的特定XML元素名,所以你可以创建一个对那些包含caps元素的packet“感兴趣”的插件。

对于那些没有插件“感兴趣”的stanza元素,它们会被直接传递到消息的目标地址。相反的,也有一些特定的stanza可能会被多个插件“感兴趣”,在这种情况下,多个插件可能会被多个线程同时进行处理,所以无法确保哪个插件先进行处理哪个插件后进行处理。

每一个stanza都会被session manager一步一步得进行处理,看看下面的图:

stanza在SM中的处理

就像图片里面展示的那样,stanza在session manager里面分四步进行处理:

  1. 预处理 – 所有已加载的预处理器都会接收stanza并进行处理。它们在session manager的内部线程里被调用,插件自己本身没有消息队列。需要注意的是:由于它们在session manager的内部线程中被调用,算法的执行效率会直接限制SM的执行最小时间(多多少少都会拖慢SM的执行速度)。设置预处理器的目的是为了阻止packet完成后续处理。如果预处理器返回的结果是true,那么这个packet就会被阻塞,被阻塞的packet不再进行后续处理。
  2. 处理 – 如果packet没有被预处理器阻塞,那么它会进入这一步进行处理。它会被插入到那些对它的特定元素“感兴趣”的处理器队列中。每一个处理器都拥有一个固定长度的处理队列,并在独立的线程里被调用。
  3. 投递处理 – 如果一个stanza没有被任何一个处理器处理,那么它就进入了这一步进行处理。内建的最后一个投递处理器会对它执行默认处理。通常默认处理是直接把这个packet发送到目的地地址。大多数情况这个packet是<message/>。
  4. 过滤 – 最后,如果上面的任何一个步骤产生了输出或result packet,那么这个输出或result packet会进入这一步进行处理,每一个过滤器都可能产生“阻塞”或“允许通过”的结果。

需要提醒两点:

  • 有两个地方或两种方法能够阻塞/过滤packet。一个地方是在“处理”前被“预处理”过程阻塞;另一个在“处理”完毕后,处理结果被“过滤”。
  • session manager和处理器的执行方式像是一个packet消费者。packet先被处理,处理一旦结束,它就被销毁了。所以在把一个packet发送到处理器之前,必须对packet进行复制,设置好所有的属性然后把它作为结果输出。当然处理器也可以输出任意多个packet作为结果,上面四个步骤当中的任何一个步骤都可以产生结果packet。看看下图:

用户发送packet

如果packet p1要向服务器外部发送(比如要发送给另外一台服务器的一个用户,或者另外一个组件 – MUC/PubSub/transport之类的),那么服务器中的第一个处理器必须为p1创建一个备份p2,并且正确设置好所有的所有的属性和目的地地址。当p1被SM在处理过程中销毁,最终服务器中的插件还能够产生一个新的packet。

如果是组件向用户发送也是一样:


组件向用户发送

在来自组件的packet被销毁之前,服务器中的一个插件必须做好备份,并最终把备份投递给用户。当然了投递操作在packet没有被任何插件处理时会作为默认行为被执行。

这样设计的原因是:输入packet-p1会在SM中被多个插件在多个线程中同时处理,所以packet的值在处理过程中一定不能被改变。

最明显的处理过程是当一个用户向服务器发送一个指令并希望从服务器获得一个应答:

用户向服务器发送一个请求并获取应答

这种设计产生惊人的结果。如果你看完下面两个用户之间的交互,你会发现,packet在投递到目的地之前被拷贝了两次:



一个用户向另一个用户发送packet

就像图片所展示的那样,packet被SM处理了两次。第一次是作为用户A的传出packet进行处理,第二次是作为用户B的传入packet进行处理。

这样做的目的是为了首先确定用户A有权限发送packet,然后是确定用户B有权限接收数据。如果用户B不在线,那么离线消息处理器会把packet保存到数据库当中。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!