XMPP协议详解三:即时消息

IM

XMPP协议的前身是Jabber协议,Jabber项目的初始目标是建立一个开放的即时消息平台。它的核心提供了网络上从一地方到另一个地方的快速路由信息的能力。

举例

我们假定suke从skh.whu.edu.cn服务器上的账户给在skh.whu.edu.cn服务器端的妹妹发送一个消息。

  1. 她的客户端通过client-to-server XML流推送消息节到skh.whu.edu.cn服务器。
  2. skh.whu.edu.cn服务器在节上贴上from地址的邮戳,并为了得到节应该如何被处理去检查to地址。得知这个消息节被绑定到skh.whu.edu.cn服务器,
  3. skh.whu.edu.cn服务器立即通过server-to-server XML流路由消息到skh.whu.edu.cn(无中间服务器跳跃)。
  4. 基于接收到的消息节,skh.whu.edu.cn服务器检查suke的妹妹是否在线;如果在线,服务器立即通过server-to-client XML流传递消息到她的在线设备中的一个或多个。
  5. 消息很快就从suke传递到了她的妹妹。

例子分析

从例子中我们可以得出:

  1. 客户端和服务器是按事件驱动的,并在任何他们接收到一个进来的节的时候,采取适当的行为。
  2. XMPP服务器不会存储一个消息,并等待客户去轮询消息;相反,他们一接收到消息就把它传递出去。
  3. 所有实体(特别是服务器)应该是能意识到出席的,因为在线使接收者服务器和接收者设备之间快速传递成为可能。
  4. 快速并且精确的对DNS查找、域名解析、长时间TCP连接、连接超时和网络冲突的处理对整个系统的成功运行是至关重要的。

出席消息类型

出席几种类型的XMPP消息,基本上是通过type属性的值来进行区分的:

normal

这种消息类型立即被传递或被服务器离线存储,并被客户端以任何聊天或群聊对话外的独立消息来处理。这个类型是默认值。

chat

chat消息类型在“聊天会话”中通常在相对短的时间内以突发消息发送。即时消息客户端在一对一的对话界面中显示这些信息。

groupchat

XMPP服务器通常将路由类型为groupchat的消息路由到一个拥有多个聊天室的专门组件或模块,并且这个组件产生一个向外的消息给房间的每一个人。

headline

headline消息通常不被离线存储,因为他们是临时性的。另外,XMPP服务器经常传递headline类型的消息到所有和账户关联的在线设备(至少priority值为非负的)。

error

error类型的消息是为了应答先前发送的消息而被发送出去的,以指示和先前消息有关的错误发送(接收人不出席,此时不能发送消息等)。

出席消息类型总结

chat和normal消息被接收人的服务器以一种特定的方式处理:如果消息是投递到账户的bare JID(user@domain.tld),服务器立即把消息传递给当前与账户关联的优先级最高的资源。如果仅有一个在线资源,决定就容易,但是如果有多个在线资源,接收人的服务器传递消息给出席优先级最大值的那个资源。
尽管XMPP技术能在实时数据传递中占据优势,但是几乎所有的XMPP服务器都支持“离线消息”,当服务器接收传给这个JabberID的一个normal或chat消息,而目的接收人不在线。当用户下次登录的时候,这些消息会自动的发送给接收人的客户端。当接收人的服务器传递出了离线的消息,它也将使用定义在延迟传递[XEP-0203]中的协议扩展添加一个小的扩展通知。这可以使接收客户端准确的对在用户界面中接收到的消息排序。

聊天会话

XMPP的聊天会话没有进行正式谈判,而是自然地进行。发起对话的实体给应答者的bare JID发送一个消息,并且消息被发起者的服务器使用发起者的full JID贴上邮戳。当应答者发送一个回复时,接收人的服务器也会将应答者的full JID贴到回复的消息上。在这时,发起者知道了应答者的full JID,并且应答者知道了发起者的full JID,然后将相互的XMPP资源标识进行“锁定”。当发送接下来的消息时,两者都相互发送节到full JID,除非收到另外一个人的出席变化为止(这可能触发重新发送一个消息到bare JID)。

聊天状态通告

在聊天中加入不知道聊天对象的状态会让人误以为对方已经离线或不想搭理人,未免让人心中有所失落。比如,你和你的女友聊天,你问:“亲爱的,今天都干啥了”,你的女友很兴奋,她有很多话要告诉你,由于打字的时间太久让你觉得她不爱你....好嘛,这样的聊天是不是很虐心....
然后,办法还是有的,你需要在你的IM系统中得到聊天状态的通告,定义聊天状态通告[XEP-0085]

聊天状态

starting

某人开始一个对话,但是你还没有参与进来

active

你正参与在对话中,当前你没有组织你的消息,而是在关注

composing

你正在组织一个消息

paused

你开始组织一个消息,但由于某个原因而停止组织消息

inactive

你一段时间里没有参与这个对话

gone

你参与的这个对话已经结束

对话期间状态变化

在composing状态组织完一个消息后,状态变为active,等待消息的回复。


对话期间状态变化

消息状态变化代码实现

母亲-女儿对话

<message from="suke@skh.whu.edu.cn" 
         to="daughter@skh.whu.edu.cn"
         type="chat">
    <body>Hi honey!</body>
    <active xmlns="http://jabber.org/protocol/chatstates"/>
</message>

通过添加<active/>元素到你的消息中,指示了你在对话中处于活跃状态。女儿开始进行回复,这样她的客户端发送给你一个聊天状态更新,通过添加一个<composing/>元素到一个空的消息里:

<message from="daughter@skh.whu.edu.cn" 
         to="suke@skh.whu.edu.cn"
         type="chat">
    <composing xmlns="http://jabber.org/protocol/chatstates"/>
</message>

通告之后不久,实际的消息到达,再次使她成为对话中的活跃参与者:

<message from="daughter@skh.whu.edu.cn" 
         to="suke@skh.whu.edu.cn"
         type="chat">
    <body>Hi</body>
    <active xmlns="http://jabber.org/protocol/chatstates"/>
</message>

这个对话持续一会儿,直到你问她关于奶奶的事:

<message from="suke@skh.whu.edu.cn" 
         to="daughter@skh.whu.edu.cn"
         type="chat">
    <body>Did you visit grandma this afternoon? What did she tell you?</body>
    <active xmlns="http://jabber.org/protocol/chatstates"/>
</message>
<message from="daughter@skh.whu.edu.cn" 
         to="suke@skh.whu.edu.cn" 
         type="chat">
    <composing xmlns="http://jabber.org/protocol/chatstates"/>
</message>

这里她突然停下码字去接电话,几秒钟后,她的客户端给你发送一个<paused/>的通告:

<message from="daughter@skh.whu.edu.cn" 
         to="suke@skh.whu.edu.cn"
         type="chat">
    <paused xmlns="http://jabber.org/protocol/chatstates"/>
</message>

一会儿后,她又恢复回答:

<message from="daughter@skh.whu.edu.cn"  
         to="suke@skh.whu.edu.cn"
         type="chat">
    <composing xmlns="http://jabber.org/protocol/chatstates"/>
</message>

最后,跳到对话的结尾,她发送再见并关闭了聊天窗口:

<message from="daughter@skh.whu.edu.cn"  
         to="suke@skh.whu.edu.cn"
         type="chat">
    <body>Everything was fine. I have to go do my homework now.</body>
    <active xmlns="http://jabber.org/protocol/chatstates"/>
</message>
<message from="daughter@skh.whu.edu.cn"
         to="suke@skh.whu.edu.cn"
         type="chat">
    <gone xmlns="http://jabber.org/protocol/chatstates"/>
</message>

你可以配置你的客户端,使它只能发送基本的聊天状态信息(例如,你是是否是active或composing),并且不发送关于更多状态的更多信息,例如paused、inactive或gone。这些基本信息只会显露你是否正在组织回答,并且留下一迹象表明你是否已经离开你的IM客户端或重新考虑谈话并关闭对话。

格式化消息

格式化消息是指XMPP能够让你自定义消息的外观或表达,如字体大小,颜色,强调等定义在XHTML-IM[XEP-0071]

<message from="suke@skh.whu.edu.cn" 
         to="beta@skh.whu.edu.cn"
         type="chat">
  <body>I love this movie I saw last night, it's awesome!</body>
  <html xmlns="http://jabber.org/protocol/xhtml-im">
    <body xmlns="http://www.w3.org/1999/xhtml">
        <p>
        I <em>love</em>, this new movie I saw last night, 
        it's <strong>awesome</strong>!
        </p>
    </body>
  </html>
</message>

你也可以用CSS格式文本。这样让你能够包括许多流行的风格格式,包括颜色、字体、文字大小、字体粗细(例如,粗体)和字体风格(例如,斜体)、字体边缘、文本对齐(例如,居中)、和文本装饰(例如,下划线)。
XHTML-IM子集也提供了对核心HTML表达的特征的一些支持,包括编号和无序列表、超链接和图像
名单中缺少的是一些更高级的HTML要素如表格和多媒体对象、以及在HTML文档中以<HEAD>标签的任何事物如脚本。这是有意为之的,因为有些恶意代码可能会使用这些要素(XMPP的设计者总是绞尽脑汁的思考安全性!)。相反,XHTML-IM关注的是HTML要素的一个简单子集,可以用在rapid-fire聊天对话的内容中并进行轻量级的表达。即使这样,XMPP客户端仍然要对来自未知实体的XHTML格式消息倍加小心,因为即使图像引用也可能引入安全漏洞。一种组织措施就是只接收来自你名册中的人的XHTML-IM格式消息。

vCard

vCard标准(最初是出版在vCard MIME Directory Profile[RFC 2426]中)定义了许多你想要标榜的数据字段,包括你的名字、昵称、地址、电话和传真号、所属公司、电子邮件地址、生日、个人网址、你的头像、甚至还有你的PGP密钥。你可以不用发布任何你不想发布的信息,但是这样做能让人们找出更多关于你的信息,这样可以加快对话。
我们假定在skh.whu.edu.cn的suke发送一个不请自来的消息给beta:

<message from="suke@skh.whu.edu.cn"
         to="beta@skh.whu.edu.cn">
  <body>O Beta do you know the way out of this pool?</body>
</message>

在回复之前,beta也许会通过发送一个IQ-get到JabberID检查suke的vCard:

<iq from="beta@skh.whu.edu.cn"
    id="pw91nf84"
    to="suke@skh.whu.edu.cn"
    type="get">
  <vCard xmlns="vcard-temp"/>
</iq>

由于这个请求是发送到suke的bare JID,suke的服务器代表她进行回复:

<iq from="suke@skh.whu.edu.cn"
    id="pw91nf84"
    to="beta@skh.whu.edu.cn"
    type="result">
  <vCard xmlns="vcard-temp">
    <N>
      <GIVEN>suke</GIVEN>
    </N>
    <URL>http://sku.whu.edu.cn/~suke/</URL>
    <PHOTO>
      <EXTVAL>http://www.cs.whu.edu/~rgs/suke03a.gif</EXTVAL>
    </PHOTO>
  </vCard>
</iq>

因此,beta至少可以在进行聊天之前,浏览suke的个人网址并查看她的图片。自然地,在vCard中的所有数据可能是假的,所以可能会为得到的vCard结果付出代价。但是,在许多情况下,有总比没有好!
为了更新你的vCard,发送一个IQ-set到你的服务器。这里suke添加一个邮件地址并上传整个vCard到她的服务器(是的,不可能只上传变化了的,因为vCard-temp规格没有提供那个特征):

<iq from="suke@skh.whu.edu.cn"
    id="w0s1nd97"
    to="skh.whu.edu.cn"
    type="set">
  <vCard xmlns="vcard-temp">
    <N>
      <GIVEN>suke</GIVEN>
    </N>
    <URL>http://skh.whu.edu.cn/~suke/</URL>
    <PHOTO>
      <EXTVAL>http://www.cs.whu.edu/~rgs/suke03a.gif</EXTVAL>
    </PHOTO>
    <EMAIL><USERID>suke@whu.edu.cn</USERID></EMAIL>
  </vCard>
</iq>

阻止和过滤通讯

简单阻止和过滤通讯

我们假定你想阻止来自BigCompany.com的之前的上司的通讯。如果你的服务器支持简单通讯阻止,就很容易做到这点,只需要发送一个适当的IQ-set:

<iq from="suke@skh.whu.edu.cn/Psi" 
    id="yu4er81v"
    to="suke@skh.whu.edu.cn"
    type="set">
    <block xmlns="urn:XMPP:blocking">
      <item jid="gmz@skh.whu.edu.cn"/>
    </block>
</iq>

现在,阻止gmz@skh.whu.edu.cn确切意味着什么呢?
首先,你需要对你的之前的上司表示成离线。当你对那个JabberID添加了阻止规则,你的服务器发送出一个不可用的出席包,以便你的老板以为你是离线。从那时起,任何时候你更新你的出席(例如,恢复在线),相关的出席节不会被发送到gmz@skh.whu.edu.cn(似乎你永远不再登陆)。
其次,你的服务器需要确认你之前的上司不能用任何方式找出你在线。这意味着你的服务器对进来的IQ-get或IQ-set使用<service-unavailable/>错误应答,忽略进来的任何<message/>消息(或再次返回一个<service-unavailable/>错误),并且放弃任何进来的<presence/>节。
最后,您的服务器需要防止你做一些愚蠢的事情,比如向你之前的上司发送一个消息或IQ请求,这样它会对任何发送给gmz@skh.whu.edu.cn向外的节回复一个<not-acceptable/>错误。
你也可以组织完整的域名。我们假定你已经开始接收到XMPP网络上来自一个流氓服务器的不请自来的消息(也许是spammers.lit)。你可以通过设置规则组织那个域中的任何JabberID的消息:

<iq from="suke@skh.whu.edu.cn"
    id="i3s91xc3"
    to="skh.whu.edu.cn"
    type="set">
  <block xmlns="urn:XMPP:blocking">
    <item jid="spammers.lit"/>
  </block>
</iq>

现在当你恢复你的“阻止列表”,你将看到两个条目:

<iq from="suke@skh.whu.edu.cn/Psi" 
    id="92h1nv8f"
    to="suke@skh.whu.edu.cn"
    type="get">
  <blocklist xmlns="urn:XMPP:blocking"/>
</iq>
<iq from="suke@skh.whu.edu.cn"
    id="92h1nv8f"
    to="suke@skh.whu.edu.cn/Psi"
    type="result">
  <blocklist xmlns="urn:XMPP:blocking">
    <item jid="gmz@skh.whu.edu.cn"/>
    <item jid="spammers.lit"/>
  </blocklist>
</iq>

在简单的通讯阻止中,它也很简单解除封锁的人。简单地发送一个包含在一个<unblock/>元素中的JabberID的IQ-set代替<block/>元素:

<iq from="suke@skh.whu.edu.cn/Psi" 
    id="ng23h57w"
    to="suke@skh.whu.edu.cn"
    type="set">
  <unblock xmlns="urn:XMPP:blocking">
    <item jid="gmz@skh.whu.edu.cn"/>
  </unblock>
</iq>

高级阻止和过滤

有时候你想通过过滤和阻止拥有更多的控制,相对于简单的通讯阻止。例如,当你正在使用移动电话登录到你的IM服务器时,你不想要接收到你200个同事的状态更新,因为这样会占据你非常有限的带宽。在另一方面,你又确实想要接收他们发送给你的偶然消息。而且,你也不想组织所有进来的出席包,因为你想知道你家里人哪一个在线,因此在你开始一个海外旅行之前可以和他们进行聊天。另外,你需要一个finer-grained协议来控制你的交通过滤规则。
XMPP再次发挥出它的作用。然后简单通讯阻止使用一个基本的阻止列表,full-featured隐私协议使用一种更高级的隐私列表。一个隐私列表是一个针对所有交通匹配的规则列表,包括进来的和出去的。如果一个规则匹配一个出去的包,与规则相关的行为将作用于包。例如,考虑如下的隐私列表:

<list name="mylist">
  <item type="jid" value="gmz@skh.whu.edu.cn" action="deny" order="1">
    <iq/>
    <message/>
    <presence-out/>
  </item>
  <item type="group" value="C207" action="deny" order="2">
    <presence-in/>
  </item>
  <item action="allow" order="3"/>
</list>

让我们来看看如何解析这个成我们的语言:
gmz@skh.whu.edu.cn的消息和第一条规则匹配。因此,如果你的服务器接收到一个来自你的之前的上司的IQ或消息节,它会忽略这个节或返回一个错误。
但是,如果你的服务器接收到一个来自你之前的上司的出席节,并且这个节与第一个隐私规则不匹配,那么你的服务器继续让它与第二条规则匹配。由于你不再和你之前的上司一起工作了,所以他不在你名册里的“工作”组。因此,你的服务器继续下一条规则(这个例子中的最后一个规则)。你瞧,进来的出席节匹配最后一个规则,因此你的服务器允许这个节通过。现在你可以看到你之前的上司是否在线,但是他不能和你进行通讯!
特定隐私规则的结合为允许和阻止通讯提供了一个强大的工具,因为你的隐私列表可以包含无限数量的按照任何顺序的隐私规则(通过item元素标识)。每个给定的规则的行为要么是allow,要么是deny,并且处理节的规则类型是基于一个特定的JabberID、名册分组名或者出席订阅状态。最后,节之间要进行匹配,基于他们是否是消息,是否是进来的出席通告(例如,不包括订阅相关的出席节),是否是出去的出席通告、是否是IQ或所有的节(包括订阅相关的节)。在实践中,这些更高级的阻止和允许的方法提供基本的过滤以代替那些简单的阻止(尽管付出更大的复杂度代价)。

更多消息拓展机制

这章提供在XMPP中各种与消息相关的扩展的概览。但这不是全部。下面对一些扩展进行快速浏览。请参考所有细节规格,并且确保在你最喜欢的客户端,服务器或库检查支持,因为其中一些尚未得到广泛实施:

  • 可扩展节地址[XEP-0033]让你在没有使用聊天室的情况下,同时发送单个消息给多个接收者。
  • 高级消息处理[XEP-0079]提供一种控制消息传递的方式;例子包括消息终止和阻止消息因为延迟传递而被脱机存储。
  • 消息回执[XEP-0184] 基于标题做你所期望做的:它提供一个端到端的机制来决定是否目的接收人真正的接收到了这个消息。
  • 消息归档[XEP-0136]定义了一种在服务器上存储消息的技术,而不是将他们存储到你的本地机子上。这在许多情况下是有用的:也许你正在使用的web客户端没有本地存储,你正在使用的-设备有有限的存储空间,或你在不同设备之间经常移动,而你想要所有的历史消息都在一个地方

参考资料:

xmpp系列笔记
xmpp-权威指南(图书)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269

推荐阅读更多精彩内容