基于XMPP的即时通信系统的建立 — XMPP IQ详解
XMPP详解
XMPP(eXtensible Messaging and Presence Protocol,可扩展消息处理和现场协议)是一种在两个地点间传递小型结构化数据的协议。在此基础上,XMPP协议已经被用来构建大规模即时通信系统、游戏平台、协作空间及语音和视频会议系统。
XMPP由几个小的构造块组成,并在此基础上扩展出了更多的构造块。XMPP中有众多系统:发布-订阅服务、多人聊天、表单检索与处理、服务发现、实时数据传输、隐私处理及远程过程调用等。
大多数社交媒体(Facebook及Twitter)也采用了XMPP协议。
什么是XMPP
与其他协议一样,XMPP定义了在两个或者更多通信实体间传递数据所采用的格式。对于XMPP,实体通常是指客户端服务器,但是其也允许客户端与客户端或服务器端与服务器端的通信。
在XMPP上交换的是XML数据,采用这种格式,使XMPP协议获得了极大的可扩展性,因为使用XML可以方便的新增功能并保证前后向兼容。使用XML较二进制协议占用更大的带宽,但获得的优势是具有了几乎无限的可扩展性。
用户可以向XMPP Standards Foundation注册协议扩展。
在XMPP中,XML数据被组织为了一对流,每个流分别对应通信的一个方向。每个XML流均由一个开始元素、后跟XMPP节和其他顶级元素,然后是一个结束元素组成。每个XMPP节(可带有子元素及属性)均是该流的一级子元素。在XMPP连接末尾,这两个流形成了一对有效的XMPP文档。
XMPP节构成了该协议的核心部分,而XMPP应用程序则关注如何发送和响应各种类型的节。节可能包含网络上其他实体的信息、类似于电子邮件的个人消息或为计算机处理而设计的结构化数据。
<message to=’elizabeth@longbourn.lit’ from=’darcy@pemberley.lit/dance’ type=’chat’> <body>what think you of books</body> </message>
在一个典型的XMPP会话中,一个上述的节将会从darcy的XMPP客户端发送到她的XMPP服务器,她的服务器将会注意到该节的目的地是某个远程服务器上的一个实体,因此与该远程服务器先建立XMPP连接,并将消息转发该处。
这种通信网络与电子邮件类似,但与电子邮件服务器不同的是,XMPP间的服务器可以直接通信,而不需要借助中间服务器。
这种直接通信避免了垃圾信息的干扰,且还支持通过TLS(Transport Layer Security,传输层安全)来加密通信并通过SASL(Simple Authentication and Security Layers,简单身份验证与安全层)实现身份验证机制。
XMPP使用传递短小信息来设计的,而非针对大型数据块,但XMPP能够用来协商并建立可在端点间传递大型数据块的带内或者带外传输。
XMPP历史
最初各大公司发布的ICQ均基于其公司运营的专有协议和网络(ICQ,Yahoo Pager)。
开发人员企图互通这些IM的努力由于协议的闭源特性失败了。
1991年1月Jeremie Miller发布了Jabber项目,Jabber是基于XML的去中心化的即时通信协议,同时也是一个叫jabberd的服务器实现。
2000年5月,Jabber的核心协议稳定,jabberd也正式发布了。
2001年,JSF(Jabber Software Foundation,Jabber软件基金会)成立,并开始围绕jabber协议做规范性工作。
2002年12月,JSF向IETF提交了核心规范,并成立了一个IETF小组。
2004年10月,这个标准化进程产生了Jabber协议的改进版,改名为XMPP,其文档成为RFC标准,编号分别为3920、3921、3922和3923。
最初开发人员向JSF提交的扩展称为JEP(Jabber Extension Proposal,Jabber扩展提议)。最终随着Jabber过度到XMPP,JSF更名为XSF(XMPP Standard Foundation,XMPP标准基金会)和XEP(XMPP Extension Proposal,XMPP扩展提议)。
2005年XMPP技术开始大规模部署,例如Google Talk。
先进有大约300个XEP,并有数十种客户端和服务端实现,开源及商用均有,实际上,任何编程语言都可以找到这样一个库来加速XMPP开发进程。
XMPP网络
任何XMPP网络都是由若干角色组成,可以分为服务器端、客户端、组件和服务器插件。
XMPP网络与WWW网络及EMAIL网络不同,XMPP服务器之间寻址只会跳一次,而EMAIL协议则会有多个中转服务器,XMPP保存完整的列表。
服务器
XMPP服务器是任何XMPP网络的通信系统,服务器的任务就是为XMPP节提供路由。无论这些节是从内部的一个用户发往另外一个用户还是本地用户发送给服务器。
一组能够相互通信的XMPP服务器构成了XMPP网络。
XMPP服务器总是允许用户连接到自己,但是也可以编写直接使用服务器-服务器协议的应用和程序,来减轻路由消耗。
Ejabberd、Openfire和Tigase是三种能够运行在Windows,Mac OS X和Linux的开源服务器。
M-Link和Jabber XCP是商用产品。
客户端
大多数XMPP实体均是客户端,通过客户端-服务器协议连接到XMPP服务器。
客户端必须向某个地方的XMPP服务器进行身份验证。服务器会将该客户端发送的所有节路由到合适的目的地。
服务器还负责管理客户端会话的其他几个方面,包括花名册及裸地址。
组件
不仅仅是客户端能够连接到XMPP服务器,大多数服务器还支持外部服务器组件。这些组件通过添加某种新服务来增强服务器的行为。这些组件在服务器内有各自的身份和地址,但运行在外部并通过组件协议通信。
组件协议(XEP-0114)可以让开发人员以一种服务器不可知的方式创建服务器扩展,例如多人聊天服务。
组件也需要向XMPP服务器进行身份验证,但要较客户端的完全SASL验证简单,例如口令。
每个组件编程服务器内部一个可单独寻址的实体,在外界看类似于一个子服务器。除了基本节之外,XMPP服务器不会代替已连接组件来管理其他节的路由。
服务器还允许组件在内部自行路由或管理节,因而更为灵活。
插件
许多XMPP服务器还支持使用插件进行扩展,但插件深入到服务器内部,有较高的效率以及最低的通用性。
插件一般是绑定特定类型的服务器的。
XMPP寻址
XMPP网络上的每个实体都有一个或多个地址(称为JID,jabber identifier)。通常类似于:
darcy@pemberley.lit和elizabeth@longbourn.lit就是两个JID。
JID由三个部分组成,节点、域和资源,域是必须的,其他两个部分是可选的。
域是实体(服务器、组件或插件)可解析的DNS名称。仅由域组成的JID是有效地址,表示服务器地址。指向域的节将由服务器自身处理,并可能被路由到某个组件或插件。
本地部分通常用来识别域中的一个特定用户,位于@前。本地部分也可以用来识别其他对象,如某个聊天室。
JID的资源部分通常会标识一个特定客户端的XMPP连接。对于XMPP客户端而言,每个连接均被指派一个资源。如darcy@perberley.lit想要连接他的书法和图书馆则可以通过
darcy@perberley.lit/study和darcy@perberley.lit/library来寻址,这样避免了用户在打开多个链接时消息无法找到正确的处理器。主要注意的是,资源部分是区分大小写的。
JID划分为两种类型:
- 裸JID
完整JID去除资源部分的地址,客户端的裸JID有些特殊,这是因为服务器自己将处理发往客户端的裸JID节。裸JID可以视为寻址用户的账户,而不是客户端。
- 完整JID
最为具体的地址
XMPP节
核心XMPP工具集由三个基本节组成,分别为<presence>、<message>和<iq>
XMPP流由两份XML文档组成,通信的每个方向均有一个文档,这些文档有一个根元素<stream:stream>,<stream:stream>的子元素由可路由的节以及与流相关的顶级子元素构成。
<stream:stream> <iq type=’get’> <query xmlns=’jabber:iq:roster’ /> //请求自己的花名册 </iq> <presence/> //通知服务器她已在线并可以访问 <message to=’darcy@pemberley.lit’ from=’elizabaeth@longbourn.lit/ballroom’ type=’chat’> <body> I cannot talk of books in a ball-room; my head is always full of something else. </body> //发送消息 </message> <presence type=’unavailable’> // 声明自己不可访问并关闭 </stream:stream>
通用属性
from/to/type/id
from的属性并非由客户端提供,而是服务端进行的标记。
presence节
presence提供网络实体的可访问性。用户发出presence节,表明自己上线,这样可以会有更大的概率与别人通信(人们更愿意与在线的人交流),但是我们也不用担心任何人都可以看到自己的在线状态,除非我们订阅了该用户的状态,订阅之后,用户的状态信息会自动发送到订阅者处。
实际上,XMPP的presence节是一个简单的专用的发布-订阅方法。
在IM中,presence体现在花名册(roster)中,花名册保存有JID列表以及用户与这些JID的订阅关系,一旦上线,用户发送presence节,剩下的就由服务器处理了(通知自己在线,以及获取联系人的状态信息)
message节
用于从一个实体向另外一个实体发送消息,并可以传输任何类型的结构化信息,不保证传输可靠性
message是一个非常基础的推模型,message通常用于IM,groupchat,警告和通知等。
message的type有如下几种:
- normal
类似于email,发出后不等待回应
- chat
用于两个实体间的实时通信
- groupchat
多用户聊天室中使用
- headline
用于发送警告或通知
- error
发送错误信息
<message from=madhatter@wonderland.lit/foo to=alice@wonderland.lit type="chat"> <body>Who are you?</body> <subject>Query</subject> </message>
除了type之外,典型的message节中还包含from、to或者id属性(用于目的追踪)。
to中的JID为消息的接受者,from是发送者的JID,但是from属性并非由客户端提供,而是发送者的服务端提供的,以避免地址模仿。
message节中也可以包含未在XMPP协议中定义的负载,可以用于扩展。
IQ节
表示Info/Query,为XMPP通信提供请求及响应机制,类似于GET/POST/PUT方法。
IQ只能包含一个payload,并且定义了需要由服务器处理的请求或者动作。相对于message来说,IQ具有更好的可靠性,因其要求收到回应。
IQ中包含有id属性,用于识别服务器发回的响应。
- get
用于请求信息,类似于HTTP Get
- set
提供信息或请求,类似于HTTP POST/PUT
- result
响应请求,类似于HTTP 200
- error
错误信息
例子
- 发送获取花名册请求
<iq from=alice@wonderland.lit/pda id="rr82a1z7" to=alice@wonderland.lit type="get"> <query xmlns="jabber:iq:roster"/> </iq>
- 服务器返回花名册
<iq from=alice@wonderland.lit id="rr82a1z7" to=alice@wonderland.lit/pda type="result"> <query xmlns="jabber:iq:roster"> <item jid="whiterabbit@wonderland.lit"/> <item jid="lory@wonderland.lit"/> <item jid="mouse@wonderland.lit"/> <item jid="sister@realworld.lit"/> </query> </iq>
- 用户新增一个联系人
<iq from=alice@wonderland.lit/pda id="ru761vd7" to=alice@wonderland.lit type="set"> <query xmlns="jabber:iq:roster"> <item jid="madhatter@wonderland.lit"/> </query> </iq>
- 服务器响应
<iq from=alice@wonderland.lit id="ru761vd7" to=alice@wonderland.lit/pda type="result"/>
error节
具有明确的结构,通常包含原节内容,通用错误信息以及应用程序特有的错误条件和信息(可选)
可扩展
XMPP协议是基于XML的协议,因此其天生提供了很好的可扩展性。我们可以用XMPP传递各种信息,包括链接、位置信息,Web Service等。
连接生命周期
发送XMPP节通常需要建立一个经过身份验证的XMPP会话,包括连接、流的建立、身份验证以及断开连接。
连接
在发送任何节之前,需要建立XMPP流,在XMPP流存在之前,必须建立通往XMPP服务器的连接。
当XMPP客户端或者服务器连接到另外一个XMPP服务器时,首先要查询SRV记录,该记录保存有特定域的服务器列表。查询应答中可以包含多条SRV记录,这样就可以在多个服务器中建立负载均衡连接。
如果没有找到合适的SRV记录,那么程序将试图直接连接到指定域。
流的建立
一旦建立通过给定XMPP服务器的连接,XMPP流就启动了
向服务器发送<stream:stream>,就可以打开XMPP流,服务器发送响应流的起始标记<stream:stream>进行响应
建立XMPP流之后就可以来回发送各种元素
服务器发送<stream:feature>元素,列举XMPP流中支持所有功能,大多数与可用的加密和身份验证选型有关
身份验证
XMPP允许进行TLS(Transport Layer Security,传输层安全)加密,而且大多数客户端默认使用该功能。
一旦服务器通告TLS支持后,客户端就会启动TLS连接并将当前套接字升级为加密套接字而不断开连接。一旦TLS加密确立,就会创建一对新的XMPP流。
XMPP中的身份验证使用SASL(Simple Authentication and Security Layers,简单身份验证与安全层)协议并支持多种身份验证机制(取决于服务器)。
一旦完成身份验证,客户端必须为连接绑定一个资源并启动一个会话,通过<bind>和<session>元素发送。
当两台服务器相互连接时,身份验证步骤稍稍不同。
连接断开
当用户结束XMPP会话后,他们终止会话并断开连接,最优雅的方式是首先发送无效出席信息,然后关闭<stream:stream>元素。
<presence type=’unavailable’> </steam:stream>