发表时间:2010-01-27
最后修改:2010-01-27
1.Jabber包怎么路由
理解Jabber路由计划的关键是Im怎么实现用户之间,而非client到client,机器到机器。换句话说,一个Im消息是发送到逻辑用户,不是网络上那个特别的机器。Im路由系统的重要职责是将包访送给指定用户,无论用户在什么网络环境下。
Im的一个重要特征是跨越空间和时间。Message跨越空间意味着Im路由系统使Packets穿越的网络,什么地方发送,什么地方接收。
IM穿越时空意味着接受和发送都是瞬间完成。理想的状态时实时的。然而Jabber系统不能保证用户一直在线接受数据。IM路由系统只有在接受方在线的情况下才能接受。
路由过程开始与JabberID,Packets是一个JabberID,这个jabberID的属性包含着一些信息。这些信息决定着告诉服务器应该路由到那个服务。回想第一章中描述,JabberID包含着客户的信息和服务器的信息。一个完整的JabberID是user@domain/resource。从软件的角度来说,就是解析JabberID。我们看一下java代码:
public class JabberID{
String user;
public String getUser() { return user; }
public void setUser(String name){ user = name; }
String domain;
public String getServer() { return domain; }
public void setServer(String name){ domain = name; }
public String getDomain() { return domain; }
public void setDomain(String name){ domain = name; }
String resource;
public String getResource() { return resource; }
public void setResource(String value){ resource = value; }
类的开始定义几个变量存储用户,域名,和JabberID资源。适当的Set/get方法使得可以访问。成员变量还被用到其他的更多的地方:
public boolean equalsDomain(String domain){
if ( this.domain == null ^ domain == null ) {
return false;
}
return this.domain.equalsIgnoreCase(domain);
}
public boolean equalsDomain(JabberID testJid){
return equalsDomain(testJid.domain);
}
public boolean equalsUser(String user){
if ( this.user == null ^ user == null ) {
return false;
}
return this.user.equalsIgnoreCase(user);
}
public boolean equalsUser(JabberID testJid){
return equalsUser(testJid.user);
}
public boolean equalsResource(JabberID test){
return equalsResource(test.resource);
}
public boolean equalsResource(String test){
if ( resource == null ^ test == null ) {
return false;
}
return resource.equalsIgnoreCase(test);
}
public boolean equalsUser(String user, String resource){
return equalsUser(user) && resource.equals(resource);
}
public boolean equals(JabberID jid){
return equalsUser(jid) && equalsDomain(jid) && equalsResource(jid);
}
public boolean equals(String jid){
return equals(new JabberID(jid));
}public void setJID(String jid){
if (jid == null){
user = null;
domain = null;
resource = null;
return;
}
int atLoc = jid.indexOf("@");
if (atLoc == -1){
user = null;
} else {
user = jid.substring(0, atLoc).toLowerCase();
jid = jid.substring(atLoc + 1);
}
atLoc = jid.indexOf("/");
if (atLoc == -1) {
resource = null;
domain = jid.toLowerCase();
} else {
domain = jid.substring(0, atLoc).toLowerCase();
resource = jid.substring(atLoc + 1).toLowerCase();
}
}public JabberID(String user, String domain, String resource){
setUser(user);
setDomain(domain);
setResource(resource);
}
public JabberID(String jid){
setJID(jid);
}
public String toString(){
StringBuffer jid = new StringBuffer();
if (user != null){ jid.append(user);
jid.append("@");
}
jid.append(domain);
if (resource != null){
jid.append("/");
jid.append(resource);
}
return jid.toString();
}
}
2.1.5 逐步分析:一个消息在jabber中的传输
设想我们有一个jabber客户端,jabberID是bigwig@manning.com/work。我们发送一个<message>包到用户iain,所在的jabber域名是shigelka.com。这个包如下:
<message to=’iain@shigeoka.com’/>
然后,我们发送到我们的mainning.com服务器。服务器检测接受者的属性:jabberID,隐藏的发送地址,发送包到达的目的地shigeoka.com jabber 服务:
<message to=’iain@shigeoka.com’ from =’bigwig@mainning.com/work’/>
shigeoka.com服务得到inin@shigeoka.com地址之后查找用户iain。如果在线用户没有登记iain这个用户,消息将存储在晚些时候发送。当资源被验证,shigeoka.com服务能够交付一个完整的<message>packets到资源。下面的这个包将被接受:
<message from =’bigwig@mainning.com/work’/>
注意接受地址已经被剥离。在这个案例中,客户假设信息的地址是她自己。Jabber服务不必剥离接受地址因此客户将能处理包有或没有接受地址。
2.2jabber协议的核心
jabber协议定义了实现Im相关任务的一个关于数据结构的集合以及交换他们的规定。Jabber协议是简单和实用的,为使用者体统了强大的保证和可扩展性。他建立在普通的技术至上,例如tpc/ip,URI,以及XML,因此学习曲线比较平坦。
Jabber协议有三个核心:
Message
Presence
Info/Query
每个都是Jabber的重要组成。我们下面大概的论述一下:
2.2.1 Message:发送数据
message是一最简单jabber协议。在大部分的情况下,多数的包都是遵循message规则,因他的简单性、易用性使得它成为最好的消息模式。因为他用简单的设计逻辑设计,使得事情变得简单而且优秀变得可能。
如你所想的,在用户之间交互的message用的人类可识别的。这些消息能够像email或在线聊天。
Message在第四章有详细介绍。
2.2.2 出席:更新用户在线状态
另外一个常常会用到的核心协议是出席协议。这个协议包括预约、核准和更新jabber系统的出席信息。和Message一样他也是设计简单的逻辑,有效的管理出席。
出席在第5、8章详细介绍。
2.2.3info/Query :处理一切
IQ协议是最后一个核心协议,如果一个协议不是message,也不是出席,那么它就是IQ协议。IQ是一个一般的Request-respose协议。它是设计简单和扩展的。
一个一般的Iq包的格式就如同一个普通的信封包含发生的iq协议的类型和查询接受者,通常是调用IQ句柄。IQ信封下是0个或多个<query>包。<query>包确定了一个默认地命名空间,而每个<query>能够包含已定义的子包。这个命名空间保证了时的<query>包内容避免放生明名冲突。
例如:如果一个客户端希望得到一个IQ回复:iq:auth IQ扩展协议,这个包看起来如下:
<iq type='get' to='handlerJID'>
<query xmlns='jabber:iq:auth'>
<username>iain</username>
</query>
</iq>
有20多个IQ扩展标准和协议。这些协议覆盖了jabber用户注册、当前时间查询的验证或服务器或客户端的版本。另外,开发人员可以自由的开发他们自己的IQ扩展协议,他们不能用标准IQ扩展协议的命名空间。
这种简单的可扩展结构允许开发人员任意的并且快捷容易的扩展Jabber系统。另外,因为查询的内容是一个在XML命名空间,并且Jabber路由信息外部包围IQ,自定义的IQ扩展包不需要修改Jabber软件就可以在jabber系统发送信息。仅仅接收者和处理者需要懂得扩展协议的意思。客户端或服务端不支持此扩展协议时只需发送一个错误指明不支持。
例如:设想我们创建一个IQ扩展协议httP://shigeoka.com/game/character为一个jabber在线游戏,它为其他用户描述了在游戏中的字符。一个客户可以发送下面的IQ包到服务器:
<iq type='set' to='evilwizard@shigeoka.com'>
<query xmlns='http://shigeoka.com/game/character'>
<name>Mario</name>
<sex>male</sex>
<occupation>plumber</occupation>
<lives>3</lives> <level>5</level>
<score>4200</score>
</query>
</iq>
Jabber服务器完整的接收这个包,发送到shigeoka.com服务器。固定的shigeoka.com服务把它交付给evilwizard用户。那个evilwizard的用户客户端接收到包并更新它的显示。没有哪个服务器知道自定义IQ包但是能够适当发送包。
现在让我们看一个具体的例子。
尽管我们仅仅知道Jabber协议工作的基本原理,但这已经足够让我们在站在更高的层次上来理解Jabber通讯会话。理解一个Jabber协议的最好办法是看看怎么在网络中穿行的。
Jabber的xml-based包简单的格式化一个原始的jabber数据。不同的规定以来与二进制数据,xml使用简单,标记文本是熟悉的Html。你不需要另外解码就可以给所有人识别,对外发数据也不需要任何工具。我们可以手动的用一般的teltel工具连接任何jabber服务器,读取原始的XML,用一般的键盘键入。在此,我们有详细的解释。
开始,你启动你的Telnet程序。在一些平台,你的telnet程序可以是一个好看的可视程序启动。不管怎样,每一个我知道的操作系统,不管window还是Unix和MacosX,都可以用telnet服务端口。你必须充实你的服务器和端口。例如你可以用开放的shigeoka.com服务:telnet shigeoka.com 5222.
telnet程序在做这些之后将得到一些公开的应答信息。
在这个示例中,我们将看到一个简单的jabber验证和消息的发送的练习。对于发送和接受信息,我们将用两个人客户端会话,记录两个不同的客户账户。记录如下:
lain smirk
1、 链接jabber服务shigeoka.com
2、 打开jabber流
3、 在shigeoka.com创建一个用户iain,密码是secret
4、 在shigeoka.com服务器验证用户iain,密码secret
5、 链接jabber服务shigeoka.com
6、 打开jabber流
7、 在jabber.org创建一个用户smirk,密码secret
8、 验证用户smirk
9、 发送一个信息到用户smirk
10、 更新available的出席状态
11、 更新出席状态到available
12、 从iain回复信息
13、 发送信息到iain
14、 接受阿里子smirk的信息
15、 关闭流
16、 关闭流
第一步链接jabber服务器用telnet:
“初始” telnet屏幕 发生了什么
% telnet shigeoka.com 5222 开启telnet连接服务器.
Trying shigeoka.com... Telnet应用的信息
Connected to shigeoka.com. 额外文本可以改变
Escape character is '^]'.
与服务器对话的开始,我们需要开启一个jabber xml流。<stream>元素定义在http://etherx.jabber.org/streams命名空间,必须总是一个正确的jabber流。我们也用到client/server协议,因此我们必须定义jabber:client默认的命名空间。另外,为支持虚拟jabber服务,开放的<stream>标签需要包含一个我们希望链接的服务器的名字的属性。
“初始” telnet屏幕 发生了什么
<?xml version='1.0'?> XML 版本 (可选).
<stream:stream 未关闭的stream元素.
xmlns:stream=
'http://etherx.jabber.org/streams'
<stream>命名空间
xmlns='jabber:client' <stream>默认命名空间.
to='shigeoka.com'> 告诉服务器连接的域名
<?xml version='1.0'?> XML 版本 (可选)
<stream:stream 未关闭的stream元素.
xmlns:stream=
'http://etherx.jabber.org/streams'
<stream>命名空间
id='3C0FB738' 这个连接随机的“session ID” xmlns='jabber:client' <stream>默认命名空间
from='shigeoka.com'> 告诉客户端连接的来源
一旦确立了会话,我们就可以创建一个用户。在许多时候,你的帐户一名存在于服务器中。实际上,只要打开jabber服务例如jabber.org就能允许你建立一个用户。如果你已经拥有了一个帐户那你可以跳过这一步。
“初始” telnet屏幕 发生了什么
<iq type='set' Account registration is an IQ set protocol.
id='reg_id'> We use a unique ID to track this query.
<query xmlns='jabber:iq:register'> jabber:iq:register IQ extension protocol.
<username>iain</username> The user name for the account.
<password>secret</password> The password for the account.
</query>
</iq>
<iq type='result' Empty result indicates success.
id='reg_id'/> Match queries and results using ID.
现在,我们拥有了一个用户帐户。我们能够用jabber:iq:authIQ扩展协议验证它。在这里,我们将用到简单的Plain authentication 协议。
Step 4: 服务器验证
“初始” telnet屏幕 发生了什么
<iq type='set' 验证是一个 IQ set类型协议.
id='auth_id'> 我们用 unique ID 跟踪这个查询.
<query
xmlns='jabber:iq:auth'> 这是一个 jabber:iq:auth IQ扩展协议.
<username>iain</username> 帐户的用户名
<password>secret</password> 密码
<resource>test</resource> 这个客户的resource
</query>
</iq>
<iq type='result' 结果为空表示成功.
id='auth_id'/> packet ID 高屋我们IQ 查询时成功的.
这是不是相当容易?现在我们重复同样的步骤在第二个telnet窗口,因为我们有两个jabber会话记录在iain和smirk帐户之间。
Step 5-8: The “smirk” client authenticating with the server
“初始” telnet屏幕 发生了什么
<password>secret</password> The password for the account.
<resource>test</resource> The resource for this client.
</query>
</iq>
<iq type='result' Empty result indicates success.
id='auth_id'/> The packet ID tells us what IQ query was successful.
“Raw” smirk client session
% telnet shigeoka.com 5222
Trying shigeoka.com...
Connected to shigeoka.com.
Escape character is '^]'.
<?xml version='1.0'?>
<stream:stream xmlns:stream='http://etherx.jabber.org/streams'
xmlns='jabber:client'
to='shigeoka.com'>
<?xml version='1.0'?>
<stream:stream xmlns:stream='http://etherx.jabber.org/streams'
id='3C0FB73C'
xmlns='jabber:client'from='shigeoka.com'>
<iq type='set'>
<query xmlns='jabber:iq:register'>
<username>smirk</username>
<password>secret</password>
</query>
</iq>
<iq type='result'/>
<iq type='set'>
<query xmlns='jabber:iq:auth'>
<username>smirk</username>
<password>secret</password>
<resource>work</resource>
</query>
</iq>
<iq type='result'
id='pthsock_client_auth_ID'/>
现在我们验证了用户,能够发送消息。我们能够用iain客户端发送一个消息到smirk。
Step 9: The “iain” client sends a message to the “smirk” client
from='shigeoka.com'>
<iq type='set'>
<query xmlns='jabber:iq:register'>
<username>smirk</username>
<password>secret</password>
</query>
</iq>
<iq type='result'/>
<iq type='set'>
<query xmlns='jabber:iq:auth'>
<username>smirk</username>
<password>secret</password>
<resource>work</resource>
</query>
</iq>
<iq type='result'
id='pthsock_client_auth_ID'/>
我们所做的都是顺势信息,因此我们希望消息立即到达smirk客户端。
看看smirk客户端会话。消息是否已交付?应该没有消息。消息没有被交付,为什么呢?
回答是jabber的出席。回想如果客户可以接受消息,必须能够被服务器感知。当一个客户首次登陆,会话的出席状态设置成unavailable。我们必须设置我们的感知状态为available以便接受消息。我们更新我们的出席状态到available:
Step 10: The “iain” client updates its presence status to available
“初始” telnet屏幕 发生了什么
<presence type='available'/> Presence update.
<message from='shigeoka.com' Server message.
to='iain@shigeoka.com'> Addressed to “iain” client.
<subject>Welcome!</subject> The message’s “subject.”
<body>Welcome to Jabber! </body> The message’s “body.”
<x xmlns='jabber:x:delay' Optional server delay “X extension.”
from='iain@shigeoka.com' Indicates account where message delayed.
stamp='20011206T18:22:09'> The time the message was sent/received.
Offline Storage Message explaining the delay.
</x>
</message>
然后我们的iain客户端变成available,服务端交付消息到客户端。这个消息来自服务端,并显示为欢迎消息。在客户登录之后,Jabber服务经常发送一个消息给用户更新他的最新信息或者服务器状态。这是以特可选项,因此服务器可以不发送。
特殊的消息示例也显示jabber:X:delay扩展。这是一个附加的包,服务器和客户端知道消息延迟。在这种情况下,服务器交付消息的同时进行验证。然而,消息不能被交付是因为客户是unavailable.delayX扩展不是必须的jabber协议特征,因此你的jabber服务可以不包含这些消息延迟的包。
选择你的smirk客户端telnet会话。注意没有任何改变。他的感知仍然是unavailable。下面的过程我们仅仅在iain客户端会话。然而,让我们用一个快捷方式。<presence>包默认的类型是unavailbale因此我们不能忽略保存贷款。这些动作做如下:
“初始” telnet屏幕 发生了什么
<presence/> Presence update (available is default).
<message from=' shigeoka.com' Server message.
to=’smirk@shigeoka.com'> Addressed to “smirk” client.
<subject>Welcome!</subject> The message’s “subject.”
<body>Welcome to Jabber! </body> The message’s “body.”
<x xmlns='jabber:x:delay' Optional server delay “X extension.”
from='smirk@shigeoka.com' Indicates the account message delayed at…
stamp='20011206T18:22:38'> The time the message was sent/received.
Offline Storage Message explaining the delay.
</x>
</message>
<message to='smirk@shigeoka.com' Message to “smirk.”
from='iain@shigeoka.com/test'> from iain’s “test” resource.
<subject>Hello</subject> The message’s “subject.”
<body>message text.</body> The message’s “body.”
<x xmlns='jabber:x:delay' Optional server delay “X extension.”
from='smirk@shigeoka.com' Indicates the account message delayed at…
stamp='20011206T18:23:54'> The time the message was sent/received.
Offline Storage Message explaining the delay.
</x>
</message>
如我们所期待的,一旦smirk客户变成available,我们如愿的接受的消息。在此,服务器欢迎消息,我们发送消息从iain到smik在第九步。在来一次,服务器附加一个delay X扩展的消息。你的服务不能做这些。
回头看iain会话。没有新的包出现。如你所能见到,不管message还是presence协议都有一个服务回复。IQ协议在在其他方面为每一请求发送回复。假定一个<message>或<presence> 包从客户发送到服务器,服务器保证它的交付。没有回复以为这成功。失败将被服务发送一个错误包到发送者手里。
当客户端编程available,终于得到了消息。然而,因为这是一个实时的因此我们看看但我们发送一个消息到其他可用的用户时发生的事情。我们将用smirk会话发送一个消息到iain:
“初始” telnet屏幕 发生了什么
<message to='iain@localhost'> A new message to “iain.”
<body>I love messages</body> The message’s “body.”
</message>
当你发送信息,我们不能期望从一个回复的Message包得到什么。然而,如果你看到iain会话,你将立即看到如下包:
“Raw” iain client session What is happening
<message to='iain@shigeoka.com' A new message to “iain.”
from='smirk@localhost/test' The sender is “smirk” on resource “test.”
<body>I love messages</body> The message’s “body.”
</message>
注意这些包缺乏delayX扩展因为没有服务延迟这些消息。你能够随便的在两个会话中发送消息。当你完成之后关闭在iain和smirk会话中的流。一旦关闭,服务将自动关闭telnet连接。如下:
“Raw” client session What is happening
</stream:stream> Close the stream.
Connection closed by foreign host. Telnet application information: server has closed
connection.
% Command prompt.
尽管我们对jabber协议没有一个更加详细的了解,当时我么农业对jabber协议和不同的jabber包有了一个很好的概念。
4、结论
Jabber系统是一个简单的,可扩展的,和容易感知的技术。看到xml数据在客户和服务端穿梭,我们能够容易的理解,分析和调试jabbwer协议。这些对开发者友好的属性来自于jabber使用了xml。在本书的下一个部分,我么你将看到jabber更新层次的问题。建一个软件项目所需的协议知识。沿着这条路,我们将开发一个基于java软件类理解,分析和开发协议。最后我们将涉及到所有个jabber协议核心,一些重要的IQ扩展协议。另外,我们将建一个服务器和客户端基于java。我们能够有使用这个软件演示jabber,扩展它用到你的项目中。