Jade Dungeon

XMPP协议基础

XMPP协议与其他协议一样,定义了主机之间交换信息的格式。而在XMPP协议中,信息是以 XML文本的形式组织的。

比如一个典型的XMPP消息表示为如下的XML文本:

<message type='chat' 
	to='elizabeth@jitt.lit' from='darcy@pemberley.lit/dance' >
	<body>What think you of books?</body>
</message>

在这一章节中,首先介绍XMPP协议中的几个基本概念。

基本元素

JID

在XMPP协议中,JID代表了一个唯一的标识。JID是Jabber Identifiers的缩写,之所以 会取这个名字是因为XMPP协议源自于1999年启动的Jabber项目。

JID的格式:local@domain/resource

可以看到整个JID由local、domain、resource三个部分组成,其中:

  • domain相当于用户注册XMPP服务器所在的域。
  • local作为用户的唯一标识。
  • resource代表着一个接入点。

通过local@domain可以表示一个唯一的用户,比如bob@jabber.org代表在 jabber.org注册的用户bob。如果用户Bob以bob@jabber.org这个账号在一个具体的 客户端上登录时则需要一个resource来标识这个接入点。这个resource可以随意定义, 但是不能跟同一用户的其他resource重复。\cite{plain:xmppJsJq}

举例来说,就像图\ref{fig:ch01.jid.png}所演示的那样。 用户在自己的电脑上以pidgin程序作为客户端程序登录bob@jabber.org,这时就需要 一个唯一的resource来标识这个连接。一般来说客户端会生成一个随机字符串来用为 当前连接的resource,当然用户也可以自己定义。在这里假设用户定义resource为 pc,这样本次连接的JID为:bob@jabber.org/pc

JID的概念

然后用户需要出门,这时在不断开电脑连接的同时又在手机上以bob@jabber.org登录 时同样也需要一个resource来标识这个连接,这里假设定义为mob。这样本次连接的JID 为:bob@jabber.org/mob

现在用户已经有两个连接在线,分别为:

  • bob@jabber.org/pc
  • bob@jabber.org/mob

如果有消息是发给bob@jabber.org的话,这两个连接都会收到这个消息。消息的发送者 也可以通过更加具体的声明bob@jabber.org/mob来指定只把消息发到Bob的手机上 。同样地,如果Bob在手机上回复了消息,那么对方也可以根据JID bob@jabber.org/mob来确定消息就是从Bob的手机上发过来的。

STANZAS

stanzas就是XMPP协议所传输的消息片段,它是以XML文本的形式出现的。比如:

	<message type='chat' to='darcy@pemberley.lit' 
		from='elizabaeth@jitt.lit/ballroom'>
		<body>I cannot talk of books in a ball-room; my head 
		is always full of something else.</body>
	</message>

把多个片段连接起来看一个完整的会话过程,看起来也是一个XML:

<stream:stream>

	<iq type='get'>
		<query xmlns='jabber:iq:roster'/>
	</iq>
	
	<presence/>
	
	<message type='chat'
		to='darcy@pemberley.lit' 
		from='elizabaeth@jitt.lit/ballroom'>
		<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>

常见片段

以下介绍一些常用的XMPP消息片段。

Presence

presenece片段用来控制用户可见状态。其内部字段有:

  • show:presenece片段中JID所代表的登录点的状态。如:onlineawayunavailable等。
  • status:描述。给其他用户看的描述信息。
  • priority:优先级,没有完整JID的目标应该发给多个resource上登录中的哪一个。 范围:-128127,默认为0。如果为负值,就表示不希望收到明确指定给自己的 消息。如果自己有一个resource上专门运行了一个自动机器人服务的话,优先级可以用 负值。

通过presence消息可以改变当前在线状态。比如通知服务器当前客户端上线:

<presence/>

通知服务器当前客户端下线:

<presence type='unavailable'/>

声明当前客户端的状态与显示给联系人的信息:

<presence>
	<show>away</show>
	<status>at the ball</status>
</presence>

presence节点的priority子节点声明了当前客户端的优先级:

<!-- 优先级 -->
<presence>
	<priority>10</priority>
</presence>

Message

message代表了要发送给目标用户的一条消息。如:

<message from='bingley@dowl.lit/drawing_room'
	to='darcy@pemberley.lit'
	type='chat'>
	<body>Come, Darcy, I must have you dance.</body>
	<thread>4fd61b376fbc4950b9433f031a5595ab</thread>
</message>

还可以群发消息给一个组的成员,消息的接收者需要是一个XMPP的聊天室群组,并且把 类型(type)设置为groupchat。例如:

<message from='bennets@chat.meryton.lit/mrs.bennet'
	to='mr.bennet@jitt.lit/study'
	type='groupchat'>
	<body>We have had a most delightful evening, 
		a most excellent ball.</body>
</message>

其中type属性是可选的,但推荐提供。其值可以为:

  • chat:一对一。
  • error:说明出错,地址不对、域名错误。
  • normal:范围扩大出一对一的消息,很少见这种情况。
  • groupchat:群聊。
  • headline:一般是自动消息。

IQ

iq用来向服务器请求资源,如联系人列表等。相当于HTTP中的GET、POST等动作。

比如从服务器请求当前账号的联系人列表:

<iq from='bob@jitt.lit/ggs' type='get' id='roster1'>
	<query xmlns='jabber:iq:roster'/>
</iq>

服务器的响应也是一个iq信息,比如在遇到错误的情况,服务器返回错误:

<iq to='bob@jitt.lit/ggs' type='error' id='roster1'>
	<query xmlns='jabber:iq:roster'/>
	<error type='cancel'>
		<feature-not-implemented 
			xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
	</error>
</iq>

当服务器正确返回联系人列表的时候,iq的type类型为get。看起来是这样:

<iq from='bob@jitt.lit/ggs' type='get' id='roster2'>
	<query xmlns='jabber:iq:roster'/>
</iq>

<iq to='bob@jitt.lit/ggs' type='result' id='roster2'>
	<query xmlns='jabber:iq:roster'>
		<item jid='elizabeth@jitt.lit' name='Elizabeth'/>
		<item jid='bingley@dowl.lit' name='Bingley'/>
	</query>
</iq>

error

前面介绍的三种片段(PresenceMessageIQ)都会可以通过包含error片段 通知对方在当前会话中有错误发生。error片段需要带有type属性声明期待对方采取的 应对方式,其值可能为:

  • cancel:不应该再试。
  • continue:虽然请求格式有错误,但仍可以继续接收该请求。这种情况不常见。
  • modify:要改后才能被接受。
  • auth:需要权限。
  • wait:服务暂时不可用。
<iq from='pubsub@pemberley.lit' to='elizabeth@jitt.lit/sitting_room'
	type='error' id='subscribe1'>
	<pubsub xmlns='http://jabber.org/protocol/pubsub'>
		<subscribe node='latest_books' jid='elizabeth@jitt.lit'/>
	</pubsub>
	<error type='cancel'>
		<not-allowed xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
		<closed-node xmlns='http://jabber.org/protocol/pubsub#errors'/>
		<text xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'>
			You must be on the whitelist to subscribe to this node.
		</text>
	</error>
</iq>

常见属性

还有一些属性在不同的片段中都会出现,以下将分别介绍:

  • id属性:帮助识别响应,避免让服务器反复处理同一个请求。对<iq>来说id属性是必须的。
  • from属性:表示消息的来源,即发送者自己。XMPP中不推荐消息的发送方设置from属性,一般 由服务器设。如果发送者设置的from值不对,服务器可能会拒绝发送该信息。
  • to属性:表示消息要发送的目标。如果客户端到服务器中没有to,则被认为是发给服务器的。
  • type属性:代表了消息的类型。比如在为error,表示此类消息错误。XMPP协议规范建议 不要回应error以避免错误消息在网络上循环。

连接生命周期

整个XMPP的连接生命周期可以简单概括为图\ref{fig:ch02.intro.xmpp.connect.png}所显示的步骤:

XMPP连接生命周期

建立连接

根据域名所在的主机查找SRV(服务),连接提供服务的主机。以普通的套接字建立连接, 这一点不用再做过多描述了。

建立Stream

建立Stream的过程如图\ref{fig:ch02.clientopen.png},客户端会发送一个没有关闭的 XML标签<stream:stream>发给服务器。表示一个XMPP流的开始:

建立Stream

客户端所发出的内容发下:

<?xml version='1.0'?>
<stream:stream xmlns='jabber:client'
	xmlns:stream='http://etherx.jabber.org/streams'
	version='1.0'
	to='pemberley.lit'>

<?xml version='1.0'?>不是XML标签,而是一个XML指令。代表以下的内容是XML文档。 这里可以注意到stream没有关闭标签</stream>,因为如果发出</stream>就表示 stream关闭了。

服务器的收到以后返回响应:

<?xml version='1.0'?>
<stream:stream xmlns='jabber:client'
	xmlns:stream='http://etherx.jabber.org/streams' version='1.0'
	from='pemberley.lit'
	id='893ca401f5ff2ec29499984e9b7e8afc'
	xml:lang='en'>

	<stream:features>
		<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>
		<compression xmlns='http://jabber.org/features/compress'>
			<method>zlib</method>
		</compression>
		<mechanisms xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>
			<mechanism>DIGEST-MD5</mechanism>
			<mechanism>PLAIN</mechanism>
		</mechanisms>
	</stream:features>

<stream:features>说明服务器所支持的验证类型。以上面的例子来说,服务器不仅支持 TLS验证,同时还支持zlib压缩。

验证

在之前「建立Stream」的过程中,服务器返回的features标签内如果包含了starttls就 说明服务器需要建立基于TLS的安全连接。在这种情况下需要丢弃本次建立的连接。 用基于TLS的安全连接的方式重新执行步骤1建立TCP连接,然后执行步骤2建立Stream。

之后会再次根据服务器features标签中mechanisms列表中选择一个SASL验证机制进行 登录验证。在登录成功后就会为当前成功登录的会话绑定一个session。

关于验证的过程会在第三章进行更加详细的描述。

通讯阶段

在验证通过以后,双方可以通过相互发送<message>片段来通话,也可以通过相互发送 <presence>片段来通知所在客户端在线状态的改变。

断开连接

当客户端需要下线时,过程如图\ref{fig:ch02.clientclose.png}:

客户端下线

当前连接的客户端先发送<presence>片段通知服务器准备下线:

	<presence type='unavailable'/>

再发一个关闭stream的标签,通知服务器关闭XMPP的Stream流:

</stream:stream>