一、概述
XMPP全称为可扩展通讯和表示协议,是一种基于标准通用标记语言的子集XML的协议,它继承了在XML环境中灵活的发展性。 因此,基于XMPP的应用具有超强的可扩展性。
优势:
- 方便扩展
- 成熟的通信协议
- 可快速部署,支持100万的以上在线用户
- 成熟稳定
- XMPP的扩展协议Jingle使得其支持语音和视频
二、基本网络结构
XMPP中定义了三个角色,客户端,服务器,网关。通信能够在这三者的任意两个之间双向发生。服务器同时承担了客户端信息记录,连接管理和信息的路由功能。网关承担着与异构即时通信系统的互联互通,异构系统可以包括SMS(短信),MSN,ICQ等。基本的网络形式是单客户端通过TCP/IP)连接到单服务器,然后在之上传输XML。
示例-客户端:
<?xmlversion='1.0'?>
<stream:stream
to='example_com'
xmlns='jabber:client'
xmlns:stream='http_etherx_jabber_org/streams'
version='1.0'>
<messagefrom='juliet_example_com'
to='romeo_example_net'
xml:lang='zh-cn'>
<body>你好,可以交个朋友吗?</body>
</message>
</stream:stream>
示例-服务端:
<?xmlversion='1.0'?>
<stream:stream
from='example_com'
id='someid'
xmlns='jabber:client'
xmlns:stream='http_etherx_jabber_org/streams'
version='1.0'>
<message from='romeo_example_net'
to='juliet_example_com'
xml:lang='zh-cn'>
<body>当然可以</body>
</message>
</stream:stream>
三、XML使用和说明
XMPP的协议,首先以<stream></stream>标签作为根节点,流的生命周期:标签的开头即是流的开始,标签的关闭即是流的结束。
在stream标签中,数据通过其内部的子标签(message、presence、iq等)当做数据实体,通过子标签可以定义大量的数据。
基本节点
1. presence
presence节点本身表示通知服务器自己的状态,用来控制和表示实体的在线状态,包含:离线(away)、在线()、离开、不能打扰等复杂状态,另外,还能白用来创建和结束在线状态的监听/订阅;
参考文档
在节点中,type字段是可选,为空则表示只是通知服务器自己在线。如果不为空, 则表示自己不在线的原因
Presence.Type.unavailable
- signals that the entity is no longer available for communication.Presence.Type.subscribe
-- the sender wishes to subscribe to the recipient's presence.Presence.Type.subscribed
-- the sender has allowed the recipient to receive their presence.Presence.Type.unsubscribe
-- the sender is unsubscribing from another entity's presence.Presence.Type.unsubcribed
-- the subscription request has been denied or a previously-granted subscription has been cancelled.Presence.Type.probe
-- a request for an entity's current presence; SHOULD be generated only by a server on behalf of a user.Presence.Type.error
-- an error has occurred regarding processing or delivery of a previously-sent presence stanza.
1.1.子属性
Show
用于指定实体或特定资源的特定可用性状态,在presence中只能存在一个。数据值只能是以下之一:
- away 表示用户暂时离开
- chat 表示用户正在聊天
- dnd 表示用户处于请忽打扰=Do Not Disturb
- xa 表示用户处于长时间离线=eXtended Away,不在线
<presence>
<show>away</show> <!--离线-->
</presence>
1.2. Status
针对可用性的描述,通常和Show元素一起使用(例如:在会议中)。
<presence>
<show>away</show> <!--离线-->
<status>atthe ball</status> <!--标签用于显示额外信息-->
</presence>
1.3. Priority
用于指定资源的优先级。值在-128到127之间。
2. message
讲限定消息推送到聊天对话或多用户聊天室的消息实体。
2.1 主要属性
属性 | 说明 |
---|---|
from | 发送信息自身的Full ID。 |
to | 设置信息接收方的Bare JID,通常在第一次发送方无法得知接收方的Full JID,通过服务器中转路由根据Base JID映射接收方的Full JID,尽量在to属性中包含对方的完成Full JID,减少服务器的接入定位。 |
id | 该属性仅用于接收实体发送给初始化实体 XML流的头。这个属性是一个由接收实体创建的具有唯一性的ID,一个初始实体和接收实体之间的会话ID,并且它在接收方的应用程序中必须是唯一的。注意:这个流 ID 必须是足够安全的,所以它必须是不可预知的和不可重复的。它不应该在有 'id'属性出现在初始实体发送给接收实体的 XML流的头中;无论如何,如果'id'属性出现在初始化流中,接收实体应该忽略它。 |
xml:lang | 用于实现国际化的属性,表示发送的XML字符所使用的语言。 |
version | 代表当前XML实体的版本 |
type | 聊天类型 |
JID的结构:在XMPP网络上,每一个实体都有一个JID标识,JID是一组排列好的元素,包括域名(domain identifier),节点名(node identifier),和资源名(resource identifier)。如:jid = [ node "@" ] domain [ "/" resource ]
- node:是对用户的抽象,既可以代表一个真实的用户,也能表示一个虚拟用户如一个聊天室等。
- domain:表达了客户所连接的服务器,在实践中通常表示一个特定的集群,由同一domain来表示。
- resource:它通常表示一个特定的会话,连接。对于服务器和和其他客户端来说,资源名是不透明的。
2.2 type
聊天类型:
- chat -- 在一对一聊天对话的上下文中发送聊天信息,存在历史记录。
- error -- 表示通知发送者错误的原因,一般由服务端发送给客户端。
- groupchat -- 用于多人聊天室的消息发送,存在历史记录。
- headline -- 提供发送指定客户端或广播内容(系统通知、警告、实时数据更新)所采用的类型。具有很高的实时性,预期是不需要收到回复的信息。
- normal -- 是独立于一对一消息和多人聊天之外的消息,预期是有信息回复的,但是没有历史记录的产生,一般用于系统强制用户确认或取消等。
2.3 子标签
- <subject/>
- <body/>
- <thread/>
- <delay/>
2.3.1 subject
表示一个消息体的主题内容,通常在聊天窗口标题处。除了xml:lang之外,不会有其他属性。该元素可以有多个(每个lang不能重复,只能存在一个),方便不同语言不通的主题 。
<message
to='romeo@example.net'
from='juliet@example.com/balcony'
type='chat'
xml:lang='en'>
<subject>I implore you!</subject>
<subject
xml:lang='cz'>Úpěnlivě prosim!</subject>
<body>Wherefore art thou, Romeo?</body>
<body xml:lang='cz'>PročeŽ jsi ty, Romeo?</body>
</message>
2.3.2 body
指定消息的文本内容。除了xml:lang之外,不会有其他属性。该元素可以有多个(每个lang不能重复,只能存在一个),方便不同语言不通的显示 。
<message
to='romeo@example.net'
from='juliet@example.com/balcony'
type='chat'
xml:lang='en'>
<body>Wherefore art thou, Romeo?</body>
<body xml:lang='cz'>PročeŽ jsi ty, Romeo?</body>
</message>
2.3.3 thread
用于跟踪一个会话,该元素主要用于客户端实现消息展示(例如:消息历史查询时,每次会话折叠显示消息),每次会话会产生一个唯一的thread.id,xmpp推荐采用uuid算法。不同时间段的聊天内容,可能是基于多个会话,在查询聊天记录的时候,可以根据会话ID进行折叠显示。
<message
to='juliet@example.com/balcony'
from='romeo@example.net/orchard'
type='chat'
xml:lang='en'>
<body>Neither, fair saint, if either thee dislike.</body>
<thread>e0ffe42b28561960c6b12b944a092794b9683a38</thread>
</message>
2.3.4 delay
表示该消失是一个离线消息。 <delay>子元素的from记录了延迟消息的最后来源方,如上例中from为capulet.com指接收离线消息人连接的服务器,离线消息最终由该服务器发出
stamp属性记录了离线消息的存储时间,客户端实现应显示该时间而非接收到的时间。
<message from='romeo@montague.net/orchard' to='juliet@capulet.com'>
<body>
收到,请回复信息。
</body>
<delay xmlns='urn:xmpp:delay'
from='capulet.com'
stamp='2002-09-10T23:08:25Z'>Offline Storage</delay>
</message>
3. IQ
iq节点主要是用于Info/Query模式的消息请求,类似于HTTP的get/post请求,可以发出get以及set请求,期望有返回值(有result,error两种回应)。
type属性值:
- Get: 获取当前域值
- Set: 设置替换get查询的值
- Result: 说明成功相应了先前的查询
- Error: 查询或相应时候出现了错误
<iq to='example.com'
type='set'
id='sess_1'>
<session xmlns='urn:ietf:params:xml:ns:xmpp-session'/>
</iq>
4. 通信流程
使用Stream元素,用来表示客户端-服务器-客户端的通信建立,在建立通信时,需要保证以下两点:
在创建会话或者发送信息之前,必须完成流的认证。
在完成流的认证之后,客户端必须将资源绑定到流,如客户端地址<user@domain/resource>
服务端向客户端通知会话建立功能;
<stream:stream
xmlns='jabber:client'
xmlns:stream='http://etherx.jabber.org/streams'
id='c2s_345'
from='example.com'
version='1.0'>
<stream:features>
<bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'/>
<session xmlns='urn:ietf:params:xml:ns:xmpp-session'/>
</stream:features>
- 1、客户端向服务端发起会话请求,并指定一个绑定的资源名:
<iq to='example.com'
type='set'
id='sess_1'>
<session xmlns='urn:ietf:params:xml:ns:xmpp-session'/>
<bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'>
<resource>pc-win-someone</resource>
</bind>
</iq>
- 2、服务端响应资源绑定请求,并返回绑定后的Full JID名:
<iq from='example.com'
type='result'
id='sess_1'>
<bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'>
<jid>somenode@example.com/pc-win-someone-server-gen-random-string</jid>
</bind>
</iq>
在会话创建有几种错误可能:
- 2.1、服务内部错误 error:type="wait"
- 2.2、账户错误或者账户不允许会话创建 error:type="auth"
- 2.3、会话冲突 error:type="cancel"
资源绑定:
- 标识客户端的平台
- 服务器为每个客户端生成随机值,生成唯一后缀,用于区分不同的客户端连接
- 相同账户的多点登录(多个终端登录),通过resource区分同一用户的不同接入点,方便策略的执行
完整数据流
1: 客户端初始化流给服务器:
<stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' to='example.com' version='1.0'>
2: 服务器发送一个流标签给客户端作为应答:
<stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' id='c2s_123' from='example.com' version='1.0'>
3: 服务端发送TLS流特征说明(包括验证机制和任何其他流特性):
<stream:features>
<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'>
<required/>
</starttls>
<mechanisms xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>
<mechanism>DIGEST-MD5</mechanism>
<mechanism>PLAIN</mechanism>
</mechanisms>
</stream:features>
4: 客户端发送 TLS握手给服务器:
<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>
5: 服务器通知客户端可以继续进行:
<proceed xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>
(或者): 服务器通知客户端 TLS 握手失败并关闭流和TCP连接:
<failure xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>
</stream:stream>
6: 客户端和服务器尝试通过已有的TCP连接完成 TLS 握手. resume是否允许恢复会话
<enabled xmlns='urn:xmpp:sm:3' id='some-long-sm-id' resume='true'/>
7: 如果 TLS 握手成功, 客户端初始化一个新的流给服务器:
<stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' to='example.com' version='1.0'>
(或者): 如果 TLS 握手不成功, 服务器关闭 TCP 连接.
8: 服务器发送一个加密流初始化给客户端,其中包括任何可用的流特性:
<stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' from='example.com' id='c2s_234' version='1.0'>
8.1:服务端发送SASL特征说明,mechanism支出了服务支持的认证机制,有关SASL认证机制[RFC4422]
<stream:features>
<mechanisms xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>
<mechanism>DIGEST-MD5</mechanism>
<mechanism>PLAIN</mechanism>
<mechanism>EXTERNAL</mechanism>
</mechanisms>
</stream:features>
9: 客户端继续 SASL 握手
<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='PLAIN'>AGp1bGlldAByMG0zMG15cjBtMzA=</auth>
5. 多用户文本聊天协议(Multi User Chat)
XMPP在其XEP-0045扩展中定义了一个用于多用户文本会议(群聊)的协议,类似于聊天室、QQ群等。由于它作为一个标准协议在定义模型上力求完备,涵盖了现实中的绝大部分IM产品模型,而现实中的IM产品基本都只实现了XMPP定义的模型中的一个子集。
XMPP定义的一些基本概念:
- 房间:房间的JID标识room@service,room标识房间的名称或者ID,service是服务器地址
- 访客: 访客JID<room@service/nick>,nick是访客在房间的昵称
- 岗位:表达了用户和房间的长期关系。XMPP定义的岗位有:所有者(owner)、管理者(admin)、成员(member)、排斥者(outcast)
- 角色:表达了用户和房间的临时联系,它只存在与一次访问期间。角色:主持人(moderator)、与会者(paticipant)、游客(visitor)
XMPP MUC协议扩展定义了一个广泛的用例集合,下面提取一些典型的核心场景来简要分析说明并辅助实现。
- MUC服务发现
主要用于客户端向服务器咨询是否支持MUC,协议交互细节详见:MUC Discovering - 新建房间
从房间创建的视角来看,本质上有2种类型的房间:
instant room 临时房间(类似于临时会话),适用于那些临时选取多个用户进行会话的场景
reserverd room 永久房间(类似于固定群) - 销毁房间
销毁房间通常仅限于房间的所有者,临时房间通常是在房间所有用户都离开后自动销毁 - 加入房间
加入房间可以有2种方式,申请和邀请 - 发言
在房间内发言方式从使用场景的角度看通常有3种:
- 向房间内所有人发言,发言者发送一个消息类型为groupchat的消息,由房间服务转发给所有与会者。
- 向部分人发言,这个场景发言者实际创建了一个临时房间,在该临时房间内进行群发。
- 向某一个人发送似有消息,这个场景退化为了一对一的单独聊天。
- 退出房间
主动退出、管理员(主持人)踢出房间