基于XMPP的即时通信系统的建立(四)— 协议详解

Presence

在XMPP协议中,我们使用presence来获取用户是否已经上线以及是否可以通信的状态。

为了能够知道自己联系人的状态以及让联系人知道自己的状态,用户上线后需要订阅联系人的状态,联系人也同样需要订阅用户的状态。

通过下面的消息订阅联系人的状态:

<presence from="alice@wonderland.lit" to="sister@realworld.lit" type="subscribe"/>

当联系人接收/拒绝订阅时,会发送消息的消息体(sucribed/unsubscribe)回应。

通常客户端是自动回应这些消息的,当我们订阅了联系人的状态之后,也会受到联系人的状态变更信息。

还可以通过嵌入<show/>(chat/away/xa/dnd)和<status/>元素表示更加丰富的信息。

<presence>
    <show>away</show>
    <status>Having a spot of tea</status>
</presence>

需要注意的是presence节是占用带宽最多的节,任何对该节内容的扩展都需要慎重。

另外,presence还提供priority属性用来标识资源的优先级(-127-128),负数优先级的资源将无法接收消息,除非显式指定,这个特性通常是由服务器实现的。

另外,我们可以通过发送directed presence到其他用户,来避免订阅对方信息,非常适合应用于网络而上的简短交流。

可以通过发送<presence type=”unavailable” />发送下线通知,服务器会完成消息保存,联系人通知等一系列操作。

presence也有其富文本形式,可以包含更多信息,但不建议在presence中使用(资源和带宽)。Publish-Subscribe[XEP-0060]和Personal Eventing Protocal[XEP-0163]提供了类似功能,但presence是针对整个花名册广播的。

服务器返回的花名册中还可以包含更加丰富的信息,包括用户组以及订阅情况。

用户对花名册的修改也可以通过发送IQ-set(rost-push)同步到服务器及其他客户端上(用户可能有手机/pad等)。这种只推送变更的机制可以简化客户端编程并节省流量。

Instant Messaging

消息发送流程

  1. user1向user2发送消息,user1位于domain1,user2位于domain2
  2. user1的client1向server1发送消息节,server1设置from属性
  3. server1投递消息给server2(直接通信)
  4. server2收到消息并检查user2是否在线并投递
  5. normal

message消息类型

独立消息,将会马上投递或者缓存,是默认消息类型

  • chat

  用户聊天,通过session建立,通常处理一系列消息

  • groupchat

  多人聊天,通常此类型会指定一个组件或者模块处理多人聊天,该模块会为每个参与者发送消息

  • headline

  全体通知,不会缓存,立即投递

  • error

  错误信息节,反馈错误信息

延时投递

如果用户不在线,则消息被缓存,用户登录后,消息推送给用户,并携带消息原始产生时间,用于客户端对消息进行排序。

可以参考Delayed Delivery[XEP-0203]

Chat session

chat session用于用户频繁交流的情况,这类似于现实情况中的聊天,其建立过程为:双方用户在消息交互中知道了对方的Full JID,因此可以直接通信,响应的机制称为chat session。

状态通知

类似于QQ的“正在输入”功能,让交互双方了解即时状态。

该功能扩展由XEP-0085定义。如果用户不希望对方看到自己的状态,可以选择不响应<active/>节。

已经定义好的状态有:Starting,Active,Composing,Paused,Inactive,Gone

 

消息类似于:

<message from=you@yourdomain.tld/work to=daughter@yourdomain.tld type="chat">
    <body>Hi honey!</body>
    <active xmlns="http://jabber.org/protocol/chatstates"/>
</message>

格式化消息

可以在message消息中加入XHTML用于富文本展示。

协议可以在[XEP-0071]中找到。

<message from=you@yourdomain.tld/home to=friend@theirdomain.tld 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>                

很容易注意到,消息中包含一个不包含格式的文本,以及XHTML格式的文本,是因为考虑到客户端如果不支持HTML,也可以正常展示。

HTML节中不可以包含HEAD中的信息,也不能包含脚本(安全性考虑)。

vCards

也就是虚拟名片,用户在聊天时可以通过虚拟名片查看相关信息。

参考[XEP-0054]

虚拟名片服务通过发送IQ请求查看和更新,但需要注意的是,更新虚拟名片时需要发送完整的信息而不是只有要更新的部分。

查询请求:

<iq from=mouse@wonderland.lit/pool id="pw91nf84" to=alice@wonderland.lit type="get">
    <vCard xmlns="vcard-temp"/>
</iq>

返回的消息:

<iq from=alice@wonderland.lit id="pw91nf84" to=mouse@wonderland.lit/pool type="result">
    <vCard xmlns="vcard-temp">
        <N>
            <GIVEN>Alice</GIVEN>
        </N>
    <URL>http://wonderland.lit/~alice/</URL>
    <PHOTO>
        <EXTVAL>http://www.cs.cmu.edu/~rgs/alice03a.gif</EXTVAL>
    </PHOTO>
    </vCard>
</iq>

更新名片:

<iq from=alice@wonderland.lit/pda id="w0s1nd97" to=alice@wonderland.lit type="set">
    <vCard xmlns="vcard-temp">
        <N>
            <GIVEN>Alice</GIVEN>
        </N>
        <URL>http://wonderland.lit/~alice/</URL>
        <PHOTO>           
<EXTVAL>http://www.cs.cmu.edu/~rgs/alice03a.gif</EXTVAL> </PHOTO> <EMAIL> <USERID>alice@wonderland.lit</USERID> </EMAIL> </vCard> </iq>

阻塞和过滤通信

类似于QQ可黑名单机制,可以对某人隐身,不看某人的消息或屏蔽某个域的消息等等;当然也包括对名单的修改。

该功能可支持的粒度非常细,参考标准[XEP-0016],[XEP-0191]。

更多消息扩展

  • Extended Stanza Addressing[XEP-0033]

  发送一条消息给多个接受者而不通过聊天室

  • Advanced Message Processing[XEP-0079]

  控制消息过期,避免消息被本地存储以及延时投递等

  • Message Receipt[XEP-00184]

  客户端层面确认消息是否已经送达

  • Message Archiving[XEP-0136]

  在服务器上存储消息,而不是在客户端机器上存储

Service Discovery

我们需要知道系统中有哪些实体,以及该实体支持哪些服务,为了完成这些操作,引入了实体发现和服务发现的概念。

Items and Info

XMPP 服务发现协议[XEP-0030]定义了两个基本的发现方法。首先发现disco#items,disco#info。

  • 向服务器发送发现实体请求
<iq from=hatter@wonderland.lit/home id="xl391n47" to="wonderland.lit" type="get">
    <query xmlns="http://jabber.org/protocol/disco#items"/>
</iq>
  • 服务器响应发回实体列表
<iq from="wonderland.lit" id="xl391n47" to=hatter@wonderland.lit/home type="result">
    <query xmlns="http://jabber.org/protocol/disco#items">
        <item jid="conference.wonderland.lit"/>
        <item jid="notify.wonderland.lit"/>
    </query>
</iq>
  • 查询实体支持的功能
<iq from=hatter@wonderland.lit/home id="gq02kb71" to="conference.wonderland.lit" type="get">
    <query xmlns="http://jabber.org/protocol/disco#info"/>
</iq>
  • 返回实体支持功能结果列表
<iq from="conference.wonderland.lit" id="gq02kb71" to=hatter@wonderland.lit/home type="result">
    <query xmlns="http://jabber.org/protocol/disco#info">
        <identity category="conference" type="text" name="Chatrooms"/>
        <feature var="http://jabber.org/protocol/muc"/>
        <feature var="jabber:iq:register"/>
        <feature var="vcard-temp"/>
    </query>
</iq>

Using Service Discovery with Servers and Services

服务发现同样适用iq-get的disco#items/disco#info这两个查询操作,只是将查询是服务而非实体。

具体的查询步骤较为复杂。

Using Service Discovery with Clients

Explicit Service Discovery

这种场景应用于用户的花名册向用户返回是否在线的信息,这种判断是否在线的信息带有Full JID,因此可以通过disco#info/disco#item来查询。

但是这种查询可能返回某个Full JID携带的全部信息,导致数据量过大,因此引入了下面的方式。

Entity Capabilites: Service Discovery Shorthand

是对上面方法的改进,通过将实体支持的特性HASH为一组特征吗,客户端接收该特征码后与本地存储进行比较,如果已有该特征码,则可以获得支持的特性列表;如果客户端没有缓冲该特征码,则重新发送disco#info消息获取,并缓存。

采用此种方法可以节省响应的资源。

并且通过presence节就可以获取客户端支持的功能了。

Data Forms

类似于HTML的表单,有工作流的特征,可以实现用户验证码输入和确认等功能。

由[XEP-0004]定义。

Multi-Party Interaction

MCU 基础

  1. 多人聊天最初被称为groupchat,后来的迭代版本改进为Multi-User Chat(UMC)[XEP-0045]。
  2. MCU的基本思想是用户加入到一个聊天室,而聊天室会组播消息,聊天室起到消息反射器的作用
  3. 聊天室有如下特征

3.1    消息在所有的参与者中共享

3.2    所有的参与者都有一个room roster

3.3    参与者都使用其nickname标识,而不是实际的JabberID

3.4    房间共享参与信息

3.5    参与者不仅限于人,也可以是服务等

  1. 聊天室有其自己的JID,且该JID是服务器的一个组件,因此具有不同的域,如服务器的域称为:wonderland.lit,组件的域为conference.wonderland.lit,实现MCU需要相应的组件,服务器根据域的不同将消息路由到对应的组件上处理。
  2. 加入聊天流程

5.1    用户发送presence消息

5.2    聊天室向成员广播该presence

5.3    聊天室向用户发送成员的presence

5.4    聊天室向用户发送一些历史消息好让用户参与讨论,消息数目可配置,且消息带有时间戳

<delay xmlns="urn:xmpp:delay" stamp="2008-11-07T18:42:03Z"/>

5.5    之后的聊天消息不再携带时间戳

5.6    聊天室之间的消息往来,消息类型为groupchat

  1. 如果用户向聊天室的成员发送消息,消息类型为chat,但消息实际上使用用户发送给聊天室的,聊天室会改写from/to字段为实际接受者的JID。
  2. 如果离开聊天室则会发送退席消息

<presence from=dormouse@wonderland.lit/sleepspace to=teaparty@conference.wonderland.lit/Dormouse type="unavailable"/>

成员管理

群组中有多重角色,不同的角色拥有不同的权限,可以将用户临时踢出,或加入黑名单等。

具体有:outcast,visitor,participant,member,moderator,admin,owner。

另外房间也有不同的类型,有指定名单的,有临时的,有隐藏的,有固定的等。

昵称

用户可以设置其在聊天室内的昵称,参考In-Band Registration[XEP-0077]。

配置

多人聊天可以进行配置,有非常多的可配置项,列出如下。

配置项

作用

allowinvites

是否允许普通成员邀请

changesubject

是否非管理员能够更改聊天室主题

enablelogging

是否开启记录归档

getmemberlist

是否能够获取成员列表

lang

语言

maxuser

最大参与者数量

membersonly

是否仅会员可加入(适用于member类型聊天室)

persistentroom

房间是否为永久(所有成员退出也不会删除)

presencebroadcast

是否广播presence消息,对大room有用

publicroom

该room是否可被发现

roomadmin

设置room管理员

roomdesc

设置room描述

roomname

设置room名称

roomowner

设置room 所有者

whois

控制是否匿名等

数据传输

除了文字之外,MCU还可以传输地理信息,文件和进行远程调用等。

Publish/Subscribe

实际上就是消息系统中的推模式,主要分三个步骤完成:首先订阅一个主题;其次发布一个消息;最后消息被推送到订阅客户端。[XEP-0060]

Subscriptions

订阅者需要订阅一个源,发布者也将消息发布到这个源。

流程:

  1. 用户发送订阅请求
  2. 服务器响应该订阅请求

如果成功则可以接收消息了,如果失败,则有可能是要求更多的配置信息

  1. 订阅者对订阅进行配置(可选)
  2. 订阅者请求配置项
  3. 源返回订阅配置表单
  4. 用户解除订阅
  5. 服务器返回解除订阅响应

Publishing and Receiving Notifications

主要分为两个部分。

发布方发布消息到源;服务器将源的消息通过message的形式投递给订阅方。

需要注意的是:发布方只负责将消息推送至源,投递的逻辑由订阅方的服务器完成。

Payloads

用户可以选择是否在通知中携带payloads,如通知中包含头像信息时,只需要metadata,无需具体的数据。

是否启用payload可以通过配置项deliver_payloads来实现。

Items

是否保存items也是可以配置的,可以通过persist_items配置。

保存/不保存数据的node分别称为persistent nodes/transient node。

Discovering Nodes

nodes及其服务可以通过disco#info/disco#item来查询

返回的结果可以通过以上两个命令进一步查询,直到找到想要订阅的node。

Personal Eventing

如果用户想订阅某个好友的动态,可以使用用户的JID作为datanode,相应的简化协议称为PEP[XEP-0163](Personal Eventing Protocal)。

用户可以应用PEP协议,在自己的Presence消息中包含自己的爱好信息,服务器收到该presence后,将会根据此爱好向用户推送好友的动态。

包括User Tune,User Location,User Activity,User Mood,User Nickname,User Avatar等。

Design Decision

  1. 尽量不要修改XMPP的核心协议,而是应该试着通过新的namespace去扩展它
  2. 由于presence占用了大约90%的数据流量,需要控制presence节中的数据
  3. 需要尽量简化XMPP客户端的设计,尤其是需要减少对服务器端的压力
  4. 尽量重用已有协议,而不是重新设计一个
posted @ 2015-12-29 16:08  纪玉奇  阅读(1197)  评论(0编辑  收藏  举报