1.1. Introduction 简介
第一个Jabber技术的应用是由开源社区发起并一直领导的即时消息的实时系统。Jabber即时消息(IM)系统和现有IM服务相比较由以下几个关键特点:
XML为基础
分布式网络
开放的协议和内核代码
模块化的、可扩展的系统架构
本文档提供一个关于Jabber系统架构的高阶概述,主要集中介绍Jabber开源服务器的设计,目前的版本是1.4(译注:目前最新版本是2.0)。关于Jabber的XML协议的相关内容,请参见Jabber Protocol Overview参考文档(http://docs.jabber.org/general/html/protocol.html)。
(注:本文档综合了以下文件内容:Jeremie Miller 1999年11月19日的《Jabber系统架构概况》,Peter Millard 在2000年4月25日关于此文档的上一个版本,Peter Saint-Andre 2000年11月6日的《Jabber白皮书》
1.2. Foundations 基础知识
Jabber在设计上很大程度上沿袭了Internet上最成功的消息系统:即email。这样Jabber就可以在一个使用共同协议的服务器组成的分布式网络上提供通信,连接这个网络的客户端,可以象接收消息一样发送消息给同一个服务器或其他Internet上的服务器上的用户。不过,尽管email是一个存储-转发系统,但Jabber转发消息却是实时的,因为Jabber服务器(连同其他所有Jabber服务器在内)知道一个用户什么时候在线。这个能力被成为在线,也是即时消息的核心所在。Jabber通过两个附加功能提供这些IM标准特性,这也使得Jabber与众不同。首先是一个允许消息系统间协同作业的开放协议。其次是建立在XML上的强大根本,它使得非但是两个人之间的通信,甚至是应用软件之间的通信成为了可能。
上述每一个功能都将在下文进行进一步的阐述,并进一步扩展本文档的内容。
1.2.1. Client/Server 客户端/服务端
Jabber使用的是客户端-服务端的系统架构,而不是其它一些即时消息系统使用的客户端-客户端的系统架构。所有从一个客户端发给另一个客户端的Jabber消息和数据都必须通过服务端。任何一个客户端都可以通过商议与另一个客户端自由地建立一个直接地连接,但这些连接只用于特殊服务地应用。有一些实例被鼓励建立这种连接,比如文件传输,但这些实例必须先通过一个客户端-服务端形势进行协商,才能建立。
1.2.2. Distributed Network 分布式网络
Jabber地网络体系是模仿e-mail系统地。每一个用户都有自己的本地服务器,并从该服务器上接收信息,消息和在线信息在这些服务器之间传输。可以添加任意数目的Jabber服务器,这些服务器接受客户端的连接,并与其它Jabber服务器进行通信。每一个Jabber服务器都独立于其他Jabber服务器,并且拥有其自身的用户列表。通过Internet,任一Jabber服务器都可以与其他Jabber服务器进行通话。每一个用户都与一个特殊服务器(提供注册服务的服务提供商或行政管理企业)相对应,Jabber地址和email地址的形势是一样的,如:stpeter@jabber.org(下面的Jabber ID部分将介绍更多关于Jabber地址的信息)。
1.2.3. Modular Server 模块化的服务器端
Jabber服务器遵循两个主要法则:
1)监听客户端连接,并直接与客户端应用程序通信
2)与其他Jabber服务器通信
Jabber开源服务器被设计成模块化,由各个不同的代码包构成,这些代码包分别处理类似用户认证、数据存储(离线消息,花名册,用户信息等)等等。另外,服务器可以通过附加服务来进行扩展,如完整的安全策略,允许服务器组件的连接或客户端选择,通向其他消息系统的网关。
一个模块化的例子就是通过Jabber XML翻译成其他协议的独立“transport”(传输器),可以实现Jabber消息系统与非Jabber消息系统之间进行消息和在线信息的交流。这些传输器并不是服务器内核。相反,它们是很容易添加到服务器内核服务器端程序,为终端用户提供更强大的功能服务。
1.2.4. Simple Client 简单的客户端
Jabber系统的一个设计标准是必须支持简单的客户端(如同和telnet连接一样简单的客户端)。事实上,Jabber系统架构对客户端只有很少的几个限制。一个Jabber客户端必须支持的功能有:
通过TCP 套接字与Jabber服务器进行通信
解析组织好的XML信息包
理解消息数据类型
Jabber将复杂性从客户端转移到服务器端。这使得客户端编写变得非常容易(一个证据就是今天出现了种类繁多的客户端),更新系统功能也同样变得容易(这样,就不用强迫用户去下载新的客户端)。Jabber客户端与服务端通过XML在TCP 套接字的5222以上端口进行通信,而不需要客户端之间直接进行通信。在实际应用中,许多低阶的客户端功能(如解析XML,理解基本的jabber XML语言类似<message/>,<presence/>,<iq/>)已经包含在Jabber客户端类库中,这样可以让客户端的开发人员更多的注重用户界面的开发。
1.2.5. XML Data Format XML数据格式
XML是Jabber系统架构的核心部分,它最重要的作用是系统的底层可扩展性,并能表述几乎任何一种结构化数据。(特别地,Jabber利用XML数据流进行客户端-服务器端以及服务器端-服务器端的通信。XML数据流一般是由客户端发起至服务端,XML数据流的有效时间直接与用户的在线会话有效时间相关联。)
Jabber严格遵守XML的同时,不需要知道任何关于信息转发中介的信息:对于信息转发中介没有任何固有的规定,也不需要任何关于信息转发中介的系统架构的知识。这都是可能的,在另一方面,这也使得提供与第三方服务(如:IRC,ICQ,AIM)进行信息传输的传输器的实现成为可能。而在Jabber系统内部,就像Jabber系统中其它每一个组件一样,传输器使用XML语音。更多关于Jabber XML协议的信息可以在《Jabber协议概述》(http://docs.jabber.org/general/html/protocol.html)中。
1.3. High-Level Server Architecture 高阶服务器系统架构
Jabber服务器由若干个组件构成,这些组件分别完成Jabber系统中逻辑上独立的各个功能。服务器的内核是一个转发组件,这个组件的唯一功能就是从一个基本组件往另一个基本组件进行XML解析传递。共有四个这样的基本组件:接收、连接、执行、装入。这些基本组件解析传入的XML,转发给其他基本组件,并使得基本组件的下游组件能够连续的使用XML。下面是一个高阶的系统架构的演示图:
一个服务器启动后,Jabber服务器负责注册的组件通过Jabber的主程序后台(如同在服务器的配置文件中定义的一样)执行其功能单元(?),并运行由这些功能单元组成的信息包(以此来定义所有信息包的传送逻辑)。Jabber服务器的内核包括处理以下公共任务的组件:
会话管理
客户端-服务端的通信
服务器-服务器的通信
DNS解决方案
用户认证
用户注册
数据库查询
为离线用户存储信息
存储并找回vCards
根据用户设定过滤信息
群组聊天(多对多的通信)
系统日志
另外,服务器内核能够补充“传输器”,这些“传输器”被设计来解决不同于Jabber开放的XML格式的其他协议。(详情见传输器部分)。这些传输器可以很自然地作为整体服务器系统架构的内置组件存在。目前存在进行翻译功能的传输器主要是针对以下的协议:
AOL Instant Messenger(AIM)
ICQ
Internet Relay Chat(IRC)
MSN Messenger
Rich Site Summary(RSS 0.9)
Yahoo! Messenger
(注:附加的传输器可以根据需要增加到Jabber上,例如为了解决IM不统一的格式,但未来的传输器没有在本文档中阐述。)
1.4. Basic Message Flow 基本消息流程
对于学习Jabber系统而言,研究通过服务器的典型数据流程是一个好的入门方式。(当XML的“消息”元素仅指Jabber开放的XML协议中规定的三种主要元素中的一种时,它更能体现Jabber最核心的意图:通过使用XML进行消息的点对点发送。)
下面是关于该数据流程的图表:
Jabber服务器(在上述图表中简化为“jabberd”,原义为“Jabber daemon [Jabber后台程序]”)在主机上的用户会话的上下文中接收型为“消息”的包体,正常情况下,该包体在5222端口(如果SSL允许并运行的情况下也可以是5223端口)通过一个直接的TCP套接字产生。如果会话不存在,jabberd将发起认证流程,该流程将会在下面的认真部分中进行介绍。如果会话存在,消息包将被送往Jabber会话管理组件(简称“JSM”)。
下面是一个XML的例子:
<message
to=’psaintandre@aim.jabber.org’
type=’chat’>
<body>Hey, the AIM transport is working great!</body>
</message>
接着,JSM根据Jabber服务器的内部配置文件上的服务器名单查找目标服务器的主机名。通常主机名都会被定义;比如,aim.jabber.org在Jabber.com服务器上的配置文件被定义为指向该主机的AIM传输器(该传输器可能在一台单独的机器上)。如果主机名没有在配置文件中被定义,“dnsrv”组件将把这个主机名于一个IP地址和端口进行对应。另外,由于该主机有问题,消息包将会送到服务器到服务器(s2s)组件,在这个例子中,jabber.org。服务器到服务器组件将直接从指定的外部Jabber服务器(比如jabber.org)或该主机上一个传输器传入。在上面的例子中,消息包有意传递到aim.jabber.org上的一个地址,因此,这个包将被送到jabber.org上的AIM传输器,再传送到一个AOL Instant Messenger 帐号(见下面的传输器部分)。另一个方面,最终的结果是一个消息从一个Jabber客户端流通过一个Jabber服务器流动到另一个Jabber服务器或外部IM系统。
1.5. Authentication 认证
在基本消息流程中提到,消息和在线信息是通过Jabber服务器上一个运行中的主机上的一个用户会话的上下文发送给Jabber的。在Jabber协议中规定,这个会话由两个XML流保持,一个是从客户端到服务器端,另一个是从服务器端到客户端。下面是一个会话的XML显示:
SEND:<stream:stream
SEND:to=’jabber.org’
SEND:xmlns=’jabber:client’
SEND:xmlns:stream=’http://etherx.jabber.org/streams’>
RECV:<stream:stream
RECV:xmls:stream=’http://etherx.jabber.org/streams’
RECV:id=’39ABA7D2’
RECV:xmlns=’jabber:client’
RECV:from=’jabber.org’>
SEND:<iq id=’1’ type=’set’>
SEND:<query xmlns=’jabber:iq:auth’>
SEND:<username>stpeter</username>
SEND:<resource>Gabber</resource>
SEND:<digest>file881517e9917bb815fed112d811d32b4e4b3aed</digest>
SEND:</query>
SEND:</iq>
RECV:<iq id=’6’ type=’result’/>
(XML for user session goes here)
SEND:</stream:stream>
RECV:</stream:stream>
为了让服务器建立一个会话,首先必须对用户进行认证。下面的图表展示的就是认证的活动流程:
当客户端连接到主机,并发起一个XML流时,认证流程就开始了。Jabber服务器会立即在’jabber:iq:auth’的名字空间中对’iq’(info/query的简称)类型和’query’子类型的包体进行查询,该名字空间含有对用户的认证信息。认证信息必须包含一个用户名和明文密码(很明显,这是让人沮丧的),一个使用SHA1算法(这个默认的认证是设计为a.k.a的“数字认证”)加密的密码,或者是一些符合零度认证的数据。
一旦认证信息被接收到,XML解释器发送控制命令给Jabber服务器的“传送”组件,该组件将把从客户端未等待认证结果就发送过来的XML进行缓存。主机(通常,但不全是以JSM形式存在)将把认证包传送到Jabber服务器的’xdb’组件。xdb组件(’xdb’即“Xml Data Base”――XML基数据)将把认证包发送给任一注册了该认证包类型的子组件:例如,明文认证包可能通过检查文件系统中的XML文件用于’xbd_file’子组件,而数字认证包通过检查LDAP用于’xdb_ldap’子组件。传送组件不作任何处理将认证包传送给xdb组件,xdb组件将把该认证包发送给合适的子组件。另外,为了提高性能,xdb_ldap组件拥有其独立的线程池,其运作方式与会话管理器中的线程模式类似。
Xdb组件将认证查询的结果返回给主机(同样,通常是JSM)。如果认证失败,服务器将返回错误代码401给客户端而不发起一个会话。如果认证成功,JSM将开启一个会话(如果需要的话将释放XML缓存),所有在线信息,消息,以及iq基本信息在用户会话的上下文中进行来回传递,直到客户端或服务端通过发送一个关闭数据流的标志(</stream>)终止。
1.6. Jabber Session Manager Jabber会话管理器
下面是Jabber会话管理器的活动流程:
前面提到,Jabber会话管理器组件(简称JSM)处理各种类型的包:消息类型、在线信息类型、查询连接到一个Jabber主机上的发起者或送达者的Jabber用户信息。同时,JSM也处理针对离线用户的数据包。比如,尽管我不在线,你还是通过我的Jabber ID(stpeter@jabber.org)发了一条消息给我。JSM将对这条消息进行适当处理,很可能一直保存到我再次上线。
JSM通过从XML流中查找“资源”元素(所谓的“资源”是指设备、客户端、我的连接所在的位置;可能是“laptop”、“Gabber”、“home”)来判断用户是否在线。通常,如果一个数据包不包含资源元素,表明该用户不在线。但有时资源元素会因为错误而丢失,因此JSM在肯定用户真的离线后,才发送消息包给“离线”组件,“离线”组件可能(举例而言)会保存该消息或重新找回一个vCard。
如果用户在线,消息、在线信息、iq包不再发送到离线组件,而是由JSM进行处理。实际上,任何一个包只会有一到两个可能的状态:要么它被转发给用户,要么它由用户发出。因此,JSM开启两个监听,一个是“to”,一个是“from”,并将它们路由到Jabber服务器中指定的模块中。一旦指定模块处理完包体,包体将被送回监听程序,以备以后更多模块进行处理,如果所有处理完毕,包体将发送给消息源或消息目的地。
下面这个例子将有助于理解。我收到从foobar@jabber.org发出的一个消息。我在线,因此消息备送达JSM。“to监听”监听到有一个包发给我,于是发出一个请求到已经注册到JSM的模块。第一个响应模块是mod_filter,该模块按用户指定的标准对进来的消息进行排序。在这个例子中(我好像从来没有从我们的朋友foobar那里很重要的批评信息),我配置mod_filter将所有从foobar@jabber.org发送到我的邮箱的消息通过SMTP传输器转寄。我们说mod_filter对消息进行了重新格式化,使得指定接收端现在由smtp.jabber.org取代原来的jabber.org,然后将包体发回给“to监听者”。另一个对已注册组件的呼叫上来,单没有任何回应,因此包体被送到stpeter@smtp.jabber.org,使得包体直接转寄到我的电子邮箱中。
需要着重指出的是这个过程是重复的,所以许多模块都可以在包体完成发送到或来自用户动作之前对包体进行处理。这使得JSM拥有了极大的弹性和扩展性,因为这样可以在不对JSM原有模块进行任何改动的基础上,很容易地添加新地模块(只需要对服务器地配置文件进行相应修改即可)。
1.7. Threading 线程
Jabber会话管理器通过线程来提高性能。当服务启动时,一定数量地线程被指派到线程池(实际数目由配置文件决定)。当系统其他部分的装载组件反馈消息包给会话管理器时,会话管理器动态地从线程池中取出没有使用的线程,将它们指派给消息端口,这些消息端口正排队等候包体(一个“消息端口”表示支持一个客户连接的数据结构)。如果线程池中没有可用的线程,会话管理器可能(但不是必须)创建一个新的线程,并将它指派给指定的消息端口。下面是这个过程的可视化描述:
1.8. Delivery Logic 传送逻辑
传送组件是服务器的核心,因为它将数据从一个基本组件移动动另一个基本组件。这个级别的数据处理逻辑如下图:
一旦一个包体被传送到一个基本组件(接收、连接、执行、装入),它将被发送到一个子组件,类似jpolld或xdb_ldap进行进一步处理。
一个预处理的例子可能是一个xdb(比如一个数据库连接)需要被处理。一个处理条件可以是JSM中所有有用的路由名字空间的总和。一个传送包体改变的例子可以是消息格式的改变,比如加上传入地址。
1.9. Transports 传输器
虽然一个健壮的、XML基础的消息系统结构是Jabber项目的核心目标,另一个重要的目标是进行消息系统间的协同作业。幸运的是,Jabber项目通过使它的协议完全开放来实现协同作业。同时,Jabber项目通过使用Jabber世界里叫做“传输器”的东东来实现Jabber开放的XML格式与众多非Jabber格式间的通信。
当一个Jabber用户发送消息给一个外部(非Jabber)系统的用户时,消息的传送包括了一个传输器组件的工作。用户的Jabber客户端发送一个消息给Jabber服务器,并指明一个包含外部系统名的Jabber ID(如psaintandre@aim.jabber.org),而不是发送给外部IM系统上的一个用户。接着Jabber服务器将数据指向指定的传输器应用程序。如果传输器是本地的(在同一台机器上运行),Jabber服务器直接与它进行通信。如果传输器不在本地运行(在另一台机器上),本地服务器发送一个包给远程服务器,该远程服务器将会把包发送给指定的传输器。一旦传输器接收到XML包体,它把信息(或指示)“转变”成另一个IM网络可以识别的本地包,并把这个本地包传送到那个IM网络中。
下面是Jabber传输器工作的高级概览:
实际上,一个传输器实现了一个代理模式。大多数传输器拥有自己的小型会话管理器,这个会话管理器将在线信息、消息、(有时)查询信息进行Jabber XML协议和“外部的”(非Jabber)协议之间的转换。总的来说,当一个用户登陆到Jabber上,传输器就为和这个用户进行通信创建一个线程。
有时,进行Jabber协议的转换是很直接的,例如,当一个外部协议是很好的文档化的(比如IRC协议,即AIM协议的“奥斯卡”版本)。而另外有些时候,对于封闭的或文档的自然协议(如Yahoo! Messenger协议)进行协议转换就非常困难。人们希望IM统一化组织((http://www.imunified.org (http://www.imunified.org/)))能够成功开放一些现在还是封闭的消息协议,或者至少为这些封闭协议的协议转换创立一套开放的协议。
绝大多数传输器都是为了与非Jabber服务进行通信,但也有个别例外。比如,群组聊天传输器,这个传输器使得Jabber用户们可以在一个聊天室里进行聊天,或者以类似IRC界面的方式进行通信。群组传输器保留每一个房间当前所有用户的记录,并发送每条消息给该房间的所有用户,使得一个房间表现得象一个映射服务器。它根据需要创建和销毁房间,如果我象加入一个不存在得房间,传输器将创建该房间,如果我使最后一个离开房间的用户,传输器将在我离开后销毁这个房间。每一个单一的房间通过类似groupname@groupchatserver这样的名字进行识别,每一个参与者通过一个对其昵称的唯一描述进行识别。比如,在莎士比亚的《麦克白》中女巫们的“groupchat”可能发生在一个地址为cauldron@conference.withces.org的房间,女巫们通过类似cauldron@conference.withces.org/firstwitch的名字进行识别。下面使一个用户可能看到的:
1.10. Subscriptions 订阅
一个Jabber实体可以订阅其他Jabber实体(如:任何和一个Jabber ID关联的事物)的在线信息,一个订阅本质上是被订阅者同意发送在线状态改变给订阅者。这个信息同时存储在订阅者和被订阅者的名单中。当我通过认证并在服务器上创建一个会话,我的在线信息被存放到Jabber会话管理器中。当我改变我的在线状态时,<presence/>包将被服务器处理,服务器在我的名单中进行查询,并将在线信息状态包发送给所有订阅我的在线状态的Jabber实体。订阅包括一下几种类别,这些类别存放在包含实体的名单上:
to――另一个发送在线状态信息给你的实体
from――另一个从你这里获得在线状态信息的实体
both――另一个发送再现信息状态给你,又从你这里获取在线信息状态的实体
none――即不从你这里获取再现信息状态,又不发送在线信息状态给你的实体
发送在线状态信息的实体并不一定是另一个Jabber用户,它也可以是一个外部的服务,比如一个数据流或一个非Jabber的IM系统。在后面的例子中,非Jabber系统的用户订阅通过一个传输器解决,Jabber用户注册到指定传输器(如:icq.jabber.org),以便将在线状态信息传送给非Jabber系统的用户。一旦Jabber用户成功注册,传输器就需要知道该用户什么时候上线,因此,它发送一个在线状态信息订阅请求给该用户。一个特殊的带有“from”特性的在线状态信息订阅数据包从传输器产生并发送,其中的数据必须可以登录到本地协议。
Jabber服务器包含一个所有用户的订阅信息组成的名单(该名单通常直接存放与文件系统中,尽管这些信息一个可以存放在数据库中)。这个名单被命名为花名册,很像其他IM系统中的“好友列表”。Jabber的花名册存放在服务器上,这样用户就可以自由的从一个地方到另一个地方,从一台计算机到另一台计算机自由的调用它。Jabber服务器根据用户意愿对花名册上的对应订阅关系进行允许、拒绝等操作。花名册还包括一些用户特殊的其它信息,比如用户的昵称,以及用户所属的群组。这些信息可以通过客户端调用适当接口显示花名册时显现出来。
1.11. Jabber IDs Jabber代号
在Jabber里,有许多不同的实体需要进行相互通信。这些实体可以表现为传输器、群组聊天室、或者是单一的Jabber用户。Jabber IDs是内外结合的表示用户身份或路由信息。Jabber IDs的关键特性包括:
它们唯一确定进行即时消息和在线信息状态通信的独立对象或实体
用户很容易记住它们并在真实世界中进行表达
它们很灵活,以至于可以包容其它IM和在线信息状态表。
每一个Jabber ID(或JID)包括一套有序的元素。JIDs由域、节点、数据流格式的资源组成:
[node@]domain[/resource]
Jabber ID 元素有以下定义:
域名是第一标识符。它表明实体连接的Jabber服务器。每一个可用的Jabber域名都应拥有一个完整的域名。
节点是第二标识符。它表明“用户”本身。所有的节点都对应一个精确的域。不过,节点是可选的,一个精确的域(如conference.jabber.org)是非法Jabber ID。
资源是可选的第三标识符。所有资源都属于一个节点。在Jabber中,资源被用来识别属于用户的特殊对象,比如设备或位置。资源是一个单独的用户可以同时拥有几个与同一Jabber服务器的连接;如:juliet@capulet.com/balcony vs. juliet@capulet.com/chamber.
一个Jabber用户通常通过一个特殊的资源与服务器相连,因此在连接时有一个node@domain/resource形式的地址(如juliet@capulet.com/balcony)。由于资源时会话性的,用户的地址可以和类似node@domain(如juliet@capulet.com)进行通信,就象人们使用和它相同的形式的email一样。
注意虽然在有些情况下,消息可以直接发送到一个精确资源,但总的来说,一个发往juliet@capulet.com消息根据Jabber服务器上的规则进行路由,因为每一个连接实例都有它自己的优先级设定。这样,如果一条消息正好是发送给juliet@capulet.com(即没有指定任一资源),该消息路由到拥有最高优先级的资源,如:juliet@capulet.com/balcony。
1.12. Server Dialback 服务器回滚
1.2版的服务器增加了一个成为服务器回滚的功能。这个功能是设计用来服务器欺骗的,这样就为服务器-服务器之间的交互增加了一个额外的安全方法。关于这个功能的详细信息会在这个文档的未来版本中提供。