Subscriptions 运行机制
Peter Saint-Andre
Jabber, Inc.
1、介绍
Subscriptions是Jabber中最复杂的部分。服务端和客户端做了大量的工作,以保证用户在不同状态下接收或发送正确的subscription请求,接受或拒绝其他用户的请求,取消订阅,删除花名册条目,等等。本文档着重解释subscription在两层结构中是如何工作的:(1)XML协议要求对特定动作进行响应;(2)user:xml文件的返回状态包括Jabber User(如 订阅者等等)以及联系人(如被订阅者)。
2、Subscription 状态
总共有四种subscription状态:
1、 None——Jabber用户和联系人互相都不订阅对方的presence
2、 To——Jabber用户被联系人的presence订阅,但联系人并不被Jabber用户的presence订阅。
3、 From——联系人接受来自Jabber用户的subscription,但联系人不被Jabber用户的presence所订阅(这样的结果就是Jabber用户在联系人的花名册中“不可见”)。
4、 Both——Jabber用户和联系人被彼此的presence订阅。
当Jabber用户将联系人加入他的花名册中(通常是通过对联系人发送一个subscriptiong请求实现),名义上还存在第五种状态,就是Jabber用户和联系人甚至不知道对方的存在(至少通过每一个用户的花名册显示)。不过,我们对这种状态没有命名。从本文档出发,我将这种状态定义为初始状态。
3、订阅一个联系人
就象上面提到的,subscriptions和以及它们相关联的花名册状态是相当复杂的。所以这里我将把一个Jabber用户与一个联系人之间的交互进行极其详尽的描述,以便你能搞清楚当一个Jabber用户订阅一个联系人的presence,结果引起该Jabber用户对联系人产生了一个subcription这样的过程中,究竟发生了些什么。我们将从Jabber用户对联系人发送一个subscription请求开始。
1、为了让联系人在Jabber用户的花名册上可见,Jabber用户客户端
(1) 发送一个<iq/>包,其jabber:iq:roster名字空间中包含一个type=’set’;
(2) 成功的情况下,从服务器接收到一个<iq/>包,其type=’result’。
(3) 从服务器接收到一个“roster push”,产生一个新的roster条目,其subscription状态置为“none”:
JABBER USER SENDS:
<iq type=’set’>
<query xmlns=’jabber:iq:roster’>
<item
jid=’contact@host’
name=’contact’/>
</query>
</iq>
JABBER USER RECEIVES:
<iq
type=’result’
from=’jabberuser@host/work’
to=’jabberuser@host/work’/>
<iq type=’set’>
<query xmlns=’jabber:iq:roster’>
<item
jid=’contact@host’
subscription=’none’
name=’contact’/>
</query>
</iq>
2、Jabber用户的客户端接着发送一个type=’subscribe’的<presence/>的包给联系人:
JABBER USER SENDS:
<presence
to=’contact@host’
type=’subscribe’>
<status>I would like to add you to my roster.</status>
</presence>
3、Jabber用户客户端接着从服务器收到第二个包含联系人待定子状态的’none’订阅状态的“roster push”;这个待定子状态在其花名册条目中包含一个ask=’subscribe’属性:
JABBER USER RECEIVES:
<iq type=’set’>
<query xmlns=’jabber:iq:roster’>
<item
jid=’contact@host’
subscription=’none’
name=’contact’
ask=’subscribe’/>
</query>
</iq>
4、一旦Jabber用户客户端发送type=’subscribe’的<presence/>包,该包被发送给联系人(我们假定该联系人在线):
CONTACT RECEIVES:
<presence
to=’contact@host’
type=’subscribe’
from=’jabberuser@host’>
<status>I would like to add you to my roster.</status>
</presence>
5、 联系人下一步要做的就是决定是否接受订阅请求。这里我们假定为”happy path”,即联系人接受订阅,这样联系人的Jabber客户端:
(1) 发送一个type=’subscribed’的<presence/>的包给Jabber用户
(2) 从服务器接收一个“roster push”包含Jabber用户的条目,其中subscription状态置为“from”:
CONTACT SENDS:
<presence to=’jabberuser@host’ type=’subscribed’/>
CONTACT RECEIVES:
<iq type=’set’>
<query xmlns=’jabber:iq:roster’>
<item
jid=’jabberuser@host’
subscription=’from’/>
</query>
</iq>
6、 联系人接受订阅请求的结果是:Jabber用户客户端接收到
(1) 一个从联系人发来的type=’subscribed’的<presence/>的包
(2) 从服务器发来的一个“roster push”,其中包含一个更新的条目,用来更新联系人的subscription为“to”;
JABBER USER RECEIVES:
<presence
to=’jabberuser@host’
type=’subscribed’
from=’contact@host’/>
<iq type=’set’>
<query xmlns=’jabber:iq:roster’>
<item
jid=’contact@host’
subscription=’to’
name=’contact’/>
</query>
</iq>
现在Jabber用户已经订阅了联系人。每个user.xml的花名册条目将如下所示:
JABBER USER’S ROSTER:
<item
jid=’contact@host’
subscription=’to’
name=’contact’/>
CONTACT’S ROSTER:
<item
jid=’jabberuser@host’
subscription=’from’/>
(注意:如果在这里Jabber用户发送一个subscription请求给联系人,Jabber用户的Jabber服务器将“吞没”该请求,而不再将其发送给联系人。)
4、从一个联系人获取一个Subscription
如我们在前面所见,需要有一个Jabber用户和联系人之间的双向的subscription。在这个过程结束部分,我们将研究Jabber用户与联系人互相订阅的情况。
5、创建一个相互的Subscription
要建立一个相互的Subscription(如 a subscription=’both’),联系必须发送一个subscription请求给Jabber用户,并且Jabber用户必须接受该请求。让我们看看XML传回了什么,弄清楚其中发生了什么,以及每个人的花名册文件发生了什么变化。
1、 首先,联系人发送一个订阅请求给Jabber用户:
CONTACT SENDS:
<presence to=’jabberuser@host’ type=’subscribe’>
<status>I would like to add you to my roster.</status>
</presence>
2、 联系人客户端从服务器接收到一个“roster push”,其中包含Jabber用户仍然是‘from’的subscription状态,但同时一个待定’to’的subscription通过花名册条目中的ask=’subscribe’的属性产生了:
CONTACT RECEIVES:
<iq type=’set’>
<query xmlns=’jabber:iq:roster’>
<item
jid=’jabberuser@host’
subscription=’from’
name=’jabberuser’
ask=’subscribe’/>
</query>
</iq>
此行为的一个结果是,联系人和Jabber用户的花名册文件的状态如下所示:
CONTACT’S ROSTER:
<item
jid=’jabberuser@host’
subscription=’from’
name=’jabberuser’
ask=’subscribe’/>
JABBER USER’S ROSTER:
<item
jid=’contact@host’
subscription=’to’
name=’contact’
subscribe=’I would like to add you to my roster.’/>
3、 一旦联系人客户端发送一个包含type=’subscribe’的<presence/>包,该包将直接发送给Jabber用户(我们假定此时Jabber用户在线):
JABBER USER RECEIVES:
<presence
to=’jabberuser@host’
type=’subscribe’
from=’contact@host’>
<status>I would like to add you to my roster.</status>
</presence>
4、Jabber用户下一步要做的就是决定是否接受订阅请求。这里我们假定为”happy path”,即联系人接受订阅,这样Jabberyonghu的Jabber客户端:
(1) 发送一个type=’subscribed’的<presence/>的包给联系人
(2) 从服务器接收一个“roster push”包含联系人的条目,其中subscription状态置为“both”:
JABBER USER SENDS:
<presence to=’contact@host’ type=’subscribed’/>
JABBER USER RECEIVES:
<iq type=’set’>
<query xmlns=’jabber:iq:roster’>
<item
jid=’contact@host’
subscription=’both’/>
</query>
</iq>
5、Jabber用户接受订阅请求的结果是:联系人客户端接收到
(1) 一个从Jabber用户发来的type=’subscribed’的<presence/>的包
(2) 从服务器发来的一个“roster push”,其中包含一个更新的条目,用来更新Jabber用户的subscription为“to”;
CONTACT RECEIVES:
<presence
to=’contact@host’
type=’subscribed’
from=’jabberuser@host’/>
<iq type=’set’>
<query xmlns=’jabber:iq:roster’>
<item
jid=’jabberuser@host’
subscription=’both’
name=’jabberuser’/>
</query>
</iq>
此行为的一个结果是,联系人和Jabber用户的花名册文件的状态如下所示:
CONTACT’S ROSTER:
<item
jid=’jabberuser@host’
subscription=’both’
name=’jabberuser’/>
JABBER USER’S ROSTER:
<item
jid=’contact@host’
subscription=’both’
name=’contact’/>
Jabber用户和联系人现在有了一个相互交互的subscription——其subscription的类型为“both”。(注意:如果在这里Jabber用户发送一个subscription请求给联系人,或者联系人发送一个subscription请求给Jabber用户,发送方的Jabber服务器将“吞没”该请求,而不再将其发送给联系人。)
6、取消一个Subscription
一旦在Jabber中你订阅了某人的presence,在某些情况下你就有可能需要取消订阅对方的presence。
尽管你发送的XML都一样,Jabber用户和联系人的花名册文件根据取消订阅包发过来时它们的花名册文件的状态的不同而表现不同。
6.1、例#1:Subscription Type ‘to’
我们的第一个例子中,我们假定Jabber用户有一个到联系人的subscription,而联系人没有到Jabber用户的subscription。在这个例子中,每个人的花名册文件的状态如下:
JABBER USER’S ROSTER:
<item
jid=’contact@host’
subscription=’to’
name=’contact’/>
CONTACT’S ROSTER:
<item
jid=’jabberuser@host’
subscription=’from’
name=’jabberuser’/>
1、 为了取消订阅联系人的presence,Jabber用户发送下面的XML:
JABBER USER SENDS:
<presence to=’contact@host’ type=’unsubscribe’/>
2、 发送presence包的结果,Jabber用户客户端从Jabber服务器上接收到一个“roster push”,其中包含‘ask’属性来取消订阅,指示取消订阅是待定的:
JABBER USER RECEIVES:
<iq type=’set’>
<query xmlns=’jabber:iq:roster’>
<item
jid=’contact@host’
subscription=’to’
name=’contact’
ask=’unsubscribe’/>
</query>
</iq>
3、 现有Jabber服务器功能将会代替联系人的行为自动回复出一个type为unsubscribe的presence包(注意:这意味着联系人在不在线都会一样进行处理)。这就导致Jabber用户接收到以下的XML:
JABBER USER RECEIVES:
<presence
type=’unsubscribed’
to=’jabberuser@host’
from=’contact@host’>
<status>Autoreply</status>
</presence>
4、 Jabber用户还将收到一个来自Jabber服务器的“roster push”,其联系人subscription被再一次设为‘none’:
<iq type=’set’>
<query xmlns=’jabber:iq:roster’>
<item
jid=’contact@host’
subscription=’none’
name=’contact’/>
</query>
</iq>
5、 最后使得联系人和Jabber用户的花名册文件如下所示:
JABBER USER’S ROSTER:
<item
jid=’contact@host’
subscription=’none’
name=’contact’/>
CONTACT’S ROSTER:
no <item/> for Jabber User
6.2、例#2:Subscription Type ‘both’
第二个例子,我们假定Jabber用户有一个到联系人的subscription,联系人也有一个到Jabber用户的subscription。在这个例子中,各人的花名册的状态如下:
JABBER USER’S ROSTER:
<item
jid=’contact@host’
subscription=’both’
name=’contact’/>
CONTACT’S ROSTER:
<item
jid=’jabberuser@host’
subscription=’both’
name=’jabberuser’/>
1、 为了取消来自联系人的presence的订阅,Jabber用户发送以下XML:
JABBER USER SENDS:
<presence to=’contact@host’ type=’unsubscribe’/>
2、 发送此presence包后,Jabber用户客户端马上接收到来自Jabber服务器的一个“roster push”,其中’ask’属性设为unsubscribe,以指示此取消订阅待定:
<iq type=’set’>
<query xmlns=’jabber:iq:roster’>
<item
jid=’contact@host’
subscription=’both’
name=’contact’
ask=’unsubscribe’/>
</query>
</iq>
3、现有的Jabber系统将自动代替联系人回复一个unsubscripe的presence包(注意:这意味着无论联系人是否在线,对于subscription请求是被待定还是不予处理,结果都一样,尽管“roster push”在请求被待定时有少许不同)。Jabber用户将接收到下列XML:
JABBER USER RECEIVES:
<presence
type=’unsubscribed’
to=’jabberuser@host’
from=’contact@host’>
<status>Autoreply</status>
</presence>
4、 Jabber用户将收到一个来自Jabber服务器的“roster push”,其subscription将联系人改成’from’:(联系人继续订阅Jabber用户的presence)
<iq type=’set’>
<query xmlns=’jabber:iq:roster’>
<item
jid=’contact@host’
subscription=’from’
name=’contact’/>
</query>
</iq>
5、 联系人将收到一个来自Jabber服务器的“roster push”,其subscription将Jabber用户改成‘to’(从此Jabber用户不再订阅联系人的presence)
<iq type=’set’>
<query xmlns=’jabber:iq:roster’>
<item
jid=’jabberuser@host’
subscription=’to’
name=’jabberuser’/>
</query>
</iq>
6、 最后,联系人与Jabber用户的花名册文件的状态如下:
JABBER USER’S ROSTER:
<item
jid=’contact@host’
subscription=’from’
name=’contact’/>
CONTACT’S ROSTER:
<item
jid=’jabberuser@host’
subscription=’to’
name=’jabberuser’/>
7、删除一个好友条目
除了取消订阅来自某人的presence,你也可以从你的花名册中删除对方的对应条目。你可以发送一个包含特殊subscription为’remove’的<iq/>包。通常,发送一个删除请求将开始一个从你的花名册删除某人的过程,同时服务器来执行取消订阅的细节。下面是其工作流程……
7.1、例#1:Subscription Type ‘to’
我们第一个例子,我们将假定Jabber用户有一个到联系人的subscription,而联系人却没有到Jabber用户的subscription。在这个例子中,各人的花名册文件的状态如下:
JABBER USER’S ROSTER:
<item
jid=’contact@host’
subscription=’to’
name=’contact’/>
CONTACT’S ROSTER:
<item
jid=’jabberuser@host’
subscription=’from’
name=’jabberuser’/>
1、 第一步是Jabber用户发送取消请求:
JABBER USER SENDS:
<iq type=’set’>
<query
xmlns=’jabber:iq:roster’><item
jid=’contact@host’
subscription=’remove’/>
</query>
</iq>
2、 发送取消请求后,Jabber用户接收到:
(1) 一个对联系人的“roster push”
(2) 一个type=’result’的<iq/>包,指示开始进行请求:
JABBER USER RECEIVES:
<iq type=’set’>
<query xmlns=’jabber:iq:roster’>
<item jid=’contact@host’ subscription=’remove’/>
</query>
</iq>
<iq
type=’result’
from=’jabberuser@host/work’
to=’jabberuser@host/work’/>
3、 另外,联系人接收到:
(1) 一个’unsubscribe’的presence包
(2) 一个“roster push”,指示Jabber用户的花名册条目的subscription为‘none’:
CONTACT RECEIVES:
<presence
type=’unsubscribe’
to=’contact@host’
from=’jabberuser@host’/>
<iq type=’set’>
<query xmlns=’jabber:iq:roster’>
<item
jid=’jabberuser@host’
subscription=’none’
name=’jabberuser’/>
</query>
</iq>
7.2、例#2:Subscription Type ‘both’
第二例子,我们假定Jabber用户有一个到联系人的subscription,联系人也有一个到Jabber用户的subscription。在这个例子中,各人的花名册文件状态如下:
JABBER USER’S ROSTER:
<item
jid=’contact@host’
subscription=’both’
name=’contact’/>
CONTACT’S ROSTER:
<item
jid=’jabberuser@host’
subscription=’both’
name=’jabberuser’/>
1、 第一步是Jabber用户发送删除请求:
JABBER USER SENDS:
<iq type=’set’>
<query
xmlns=’jabber:iq:roster’><item
jid=’contact@host’
subscription=’remove’/>
</query>
</iq>
2、发送请求后,Jabber用户接收到:
(1) 一个对联系人的“roster push”包
(2) 一个type=’result’的<iq/>包来开始请求:
JABBER USER RECEIVES:
<iq type=’set’>
<query xmlns=’jabber:iq:roster’>
<item jid=’contact@host’ subscription=’remove’/>
</query>
</iq>
<iq
type=’result’
from=’jabberuser@host/work’
to=’jabberuser@host/work’/>
3、同时,联系人接收到:
(1) 一个type为’unsubscribe’的presence包
(2) 一个“roster push”包,改变Jabber用户的花名册条目的subscription为‘to’
(3) 一个type为’unsubscribed’的presence包
(4) 一个“rost push”包,改编Jabber用户的花名册条目的subscription为“none”;
CONTACT RECEIVES:
<presence
type=’unsubscribe’
to=’contact@host’
from=’jabberuser@host’/>
<iq type=’set’>
<query xmlns=’jabber:iq:roster’>
<item
jid=’jabberuser@host’
subscription=’to’
name=’jabberuser’/>
</query>
</iq>
<presence
type=’unsubscribed’
to=’contact@host’
from=’jabberuser@host’/>
<iq type=’set’>
<query xmlns=’jabber:iq:roster’>
<item
jid=’jabberuser@host’
subscription=’none’
name=’jabberuser’/>
</query>
</iq>
8、客户端开发注意事项
从上面的论述中,必须要清楚,详尽的XML片断并不一定指得是同一个事物,也不一定拥有相同的含义——其含义根据实际情况中subscription的上下文以及花名册条目的状态决定。Jabber客户端的开发人员在写代码时,需要牢记这一点。一般来说,最好不要修改客户端一方关于花名册地描述,该花名册以你发送到服务器的外部XML为基础。在大多数情况下,你只需要等待“roster push”并使你的客户端根据其进行改变。
9、引用
本文引用了下列材料:
l Jabber Programmers Guide(http://docs.jabber.org/jpg/)
l Jabber Protocol Overview(http://docs.jabber.org/general/html/protocol.html)
l Jabber protocol-Standard(http://docs.jabber.org/proto/)
注意
1、 如果联系人不在线,下列XML保存到联系人的花名册文件中:
<item
jid=’jabberuser@host’
subscription=’none’
subscribe=’I would like to add you to my roster.’/>
联系人下次登录时,服务器将提示’subscribe’属性,并产生一个来自Jabber用户的type=’subscribe’<presence/>包,发送给联系人。
2、 如果联系人拒绝了订阅请求,联系人的客户端发送下列XML给Jabber用户:
<presence to=’jabberuser@host’ type=’unsubscribed’/>
Jabber用户收到这个presence包,同时收到一个来自服务器的“roster push”
<presence
to=’jabberuser@host’
type=’unsubscribed’
from=’contact@host’/>
<iq type=’set’>
<query xmlns=’jabber:iq:roster’>
<item
jid=’contact@host’
subscription=’none’
name=’contact’/>
</query>
</iq>
这个操作后,Jabber用户和联系人的花名册状态如下:
JABBER USER’S ROSTER:
<item
jid=’contact@host’
subscription=’none’
name=’contact’/>
CONTACT’S ROSTER:
no <item/> for Jabber User
你肯定想知道为什么对联系人的<item/>没有直接从Jabber用户的花名册中删除掉(那样返回的花名册我叫它“初始状态”),二是在花名册中保存了一个type为“none”的subscription。好问题,但我想说的时现有的Jabber系统机制是以这个方式运行的。(注意如果在这种状态Jabber用户向联系人发送一个订阅请求,其表现就联系人不在线的表现完全相同,Jabber用户客户端不必从服务器上获取初始的花名册是的这种情况除外。)
3、 如果Jabber用户在线,下面的XML保存仔Jabber用户的花名册文件中:
<item
jid=’contact@host’
subscription=’to’
subscribe=’I would like to add you to my roster.’/>
下次Jabber用户登录时,服务器注意到’subscribe’属性,然后生成一个从联系人到Jabber用户的的type=’subscribe’的<presence/>包。
4、 如果Jabber用户拒绝订阅请求,Jabber用户的客户端发送下列XML给联系人:
<presence to=’contact@host’ type=’unsubscribed’/>
联系人也将从服务器上收到一个“roster push”,改变Jabber用户的订阅状态为“from”:
<presence
to=’contact@host’
type=’unsubscribed’
from=’jabberuser@host’/>
<iq type=’set’>
<query xmlns=’jabber:iq:roster’>
<item
jid=’jabberuser@host’
subscription=’from’
name=’jabberuser’/>
</query>
</iq>
结果,联系人和Jabber用户的花名册名单的状态如下:
CONTACT’S ROSTER:
<item
jid=’jabberuser@host’
subscription=’from’
name=’jabberuser’/>
JABBER USER’S ROSTER:
<item
jid=’contact@host’
subscription=’to’
name=’contact’/>