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
。
然后用户需要出门,这时在不断开电脑连接的同时又在手机上以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所代表的登录点的状态。如:online
、away
、unavailable
等。 -
status
:描述。给其他用户看的描述信息。 -
priority
:优先级,没有完整JID的目标应该发给多个resource上登录中的哪一个。 范围:-128
到127
,默认为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
前面介绍的三种片段(Presence
、Message
、IQ
)都会可以通过包含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}所显示的步骤:
建立连接
根据域名所在的主机查找SRV(服务),连接提供服务的主机。以普通的套接字建立连接, 这一点不用再做过多描述了。
建立Stream
建立Stream的过程如图\ref{fig:ch02.clientopen.png},客户端会发送一个没有关闭的
XML标签<stream:stream>
发给服务器。表示一个XMPP流的开始:
客户端所发出的内容发下:
<?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>