首页 > 代码库 > XMPP学习及使用1

XMPP学习及使用1

XMPP 简介

本小节将简要介绍 XMPP,它的起源,以及为何它是一个适合实时 web 通信的协议。您将检查 XMPP 通信设置的组件,并查看展示这些组件如何使用的示例。

Web 标准和 XMPP

XMPP 是一组基于 XML 的技术,用于实时应用程序。最初,XMPP 作为一个框架开发,目标是支持企业环境内的即时消息传递和联机状态应用程序。当时的即时消息传递网络是私有的,非常不适合企业使用。例如,AOL Instant Messenger 不能针对公司内的安全通信进行调整。尽管存在一些商业解决方案,但它们固定的特性集通常不能进行调整,以满足组织的特殊需求。XMPP,当时名为 Jabber,允许组织构建自己的定制工具来促进实时通信,并允许安装现成的第三方解决方案。

XMPP 是一个分散型通信网络,这意味着,只要网络基础设施允许,任何 XMPP 用户都可以向其他任何 XMPP 用户传递消息。多个 XMPP 服务器也可以通过一个专门的 “服务器-服务器” 协议相互通信,提供了创建分散型社交网络和协作框架的有趣可能性,但这个主题已超出了本教程的讨论范围。

顾名思义,XMPP 可用于满足广泛的、对时间敏感的特性要求。实际上,Google Wave,一个大型多用户协作环境,将 XMPP 作为其联合协议的基础。尽管 XMPP 的出现是为了满足 “个人-个人” 即时消息传递的要求,但它完全不必局限于此任务。

 

XMPP 通信的结构

要促进消息传递,每个 XMPP 客户端用户必须拥有一个全局惟一标识符。基于历史原因,这些标识符称为 Jabber IDs,或称为 JIDs。鉴于这个协议的分布式特征,重要的是 JID 应包含联系用户所需的所有信息:不存在将用户链接到他们连接到的服务器的中央知识库。JID 的结构类似于电子邮件地址(但不要求 JID 同时也是有效的电子邮件收件人)。

客户端和服务器节点,我将它们统称为 XMPP 实体,都拥有 JIDs。SomeCorp 公司的员工 John Doe 可能拥有 JIDJohn.Doe@somecorp.com。这里,somecorp.com 是 SomeCorp 公司的 XMPP 服务器的地址,John.Doe 是 John Doe 的用户名。

JIDs 还拥有连接到它们的资源。这允许在一个 XMPP 实体标识符之外进一步处理细粒度;例如,尽管上面的示例总体上能够表示 John Doe,但 John.Doe@somecorp.com/Work 可以用于将数据发送到与他的工作相关的工具。

这些资源可以采用任意用户定义的名称,一个 XMPP 实体可以拥有任意数量的资源。除了可以是上下文依赖的外,它们还可以绑定到设备、工具或工作站。对于您的 Pingstream 示例,web 站点的每个访问者都将作为同一个用户登录 XMPP 服务器,但他们拥有不同的资源。

 

通信类别

使用 XMPP 的实时消息传递系统包含三大通信类别:

  • 消息传递,其中数据在有关各方之间传输;
  • 联机状态,它允许用户广播其在线状态和可用性;
  • 信息/查询请求,它允许 XMPP 实体发起请求并从另一个实体接收响应。

这些类别是互补的。例如,如果用户或实体离线(尽管在许多用例中,理想的状态是服务器在用户返回之前一直持有用户的消息),则没有将数据发送给用户或发起一个实体的信息/查询请求的点。这些消息中的每一条都将通过一个完整的 XML 传递 — XML 节是以 XML 表达的独立信息项。

这三种类型的 XMPP 节都拥有以下公共属性:

  • from:源 XMPP 实体的 JID;
  • to:目标接收者的 JID;
  • id:这次对话的可选标识符;
  • type:节的可选子类型;
  • xml:lang:如果内容是人们可读的,则为消息语言的描述。

基于 XMPP 的数据传输发生在一些 XML 流上,默认在端口 5222 上操作。这些 XML 流实际上是两个完整的 XML 文档,每个文档对应一个通信方向。一旦会话建立,stream 元素将打开。这个元素将封装整个通信文档。然后,一些节被注入这个文档的第二层。最后,一旦通信结束,stream 元素将关闭,形成一个完整的文档。

例如,清单 1 展示了一个 stream 元素,它建立了从客户端到服务器的通信。


清单 1. 建立从客户端到服务器的通信的 stream 标记

					
<stream:stream from="[server]" id="[unique ID over conversation]" 
xmlns="jabber:client" xmlns:stream="http://etherx.jabber.org/streams" version="1.0">

 

 

消息

一旦通信建立,客户端就能使用 message 元素将消息发送到另一个用户,message 元素包含以下任意子元素:

  • subject:一个可读的字符串,表示消息主题。
  • body:一个可读的字符串,表示消息体。如果每个消息体标记都拥有一个不同的 xml:lang 值,那么可以包含多个消息体标记。(xml:lang 是惟一可能的属性。)
  • thread:一个惟一标识符,表示一个消息线程。客户端软件可以使用这个子元素将相关消息串联在一起。

但是,消息也可以非常简单,如 清单 2 所示:


清单 2. 样例消息

					
<message from="sendinguser@somedomain" to="recipient@somedomain" xml:lang=‘en‘>
  <body>
    Body of message
  </body>
</message>

 

对于提供实时 web 界面而言,消息节是最有用的节。“发布-订阅” 模型 — 在实时 web 应用程序中使用消息来传输数据的一种替代方法 — 将稍后介绍。

 

信息/查询

信息/查询节拥有广泛的功能。一个例子就是 “发布-订阅” 模型,在该模型中,发布者通知服务器某个特定资源进行了更新,服务器则通知已选择订阅这些通知并拥有适当授权的所有 XMPP 用户。

来自发布者的一系列项目被编码为一些节,格式为基于 XML 的 Atom 发布格式。每个项目都包含在一个 item 元素内,然后合并到一个 pubsub 元素中,最后成为一个信息/查询节。在 清单 3(选自 XMPP 发布-订阅规范)中,Shakespeare‘s Hamlet(JID 为 hamlet@denmark.lit/blogbot)用他著名的独白发布一个更新到 pubsub.shakespeare.lit pubsub 更新节点:


清单 3. 对 pubsub.shakespeare.lit pubsub 更新节点的更新

					
<iq type="set"
    from="hamlet@denmark.lit/blogbot"
    to="pubsub.shakespeare.lit"
    id="pub1">
  <pubsub xmlns="http://jabber.org/protocol/pubsub">
    <publish node="princely_musings">
      <item>
        <entry xmlns="http://www.w3.org/2005/Atom">
          <title>Soliloquy</title>
          <summary>
To be, or not to be: that is the question:
Whether ‘tis nobler in the mind to suffer
The slings and arrows of outrageous fortune,
Or to take arms against a sea of troubles,
And by opposing end them?
          </summary>
          <link rel="alternate" type="text/html"
                href=http://www.mamicode.com/"http://denmark.lit/2003/12/13/atom03"/>>

 

信息/查询节也用于请求一个特定 XMPP 实体的有关信息。例如,在 清单 4 中的节中,boreduser@somewhere 正在查找friendlyuser@somewhereelse 拥有的公共项目。


清单 4. 用户查找由 friendlyuser@somewhereelse 拥有的公共项目

					
<iq type="get"
    from="boreduser@somewhere"
    to="friendlyuser@somewhereelse"
    id="publicStuff">
  <query xmlns="http://jabber.org/protocol/disco#items"/>
</iq>

 

反过来,friendlyuser@somewhereelse 使用一列可被订阅到使用 “发布-订阅” 的项目进行响应,如 清单 5 所示:


清单 5. 使用一列项目响应

					
<iq type="result"
    from="friendlyuser@somewhereelse"
    to="boreduser@somewhere"
    id="publicStuff">
  <query xmlns="http://jabber.org/protocol/disco#items">
    <item jid="stuff.to.do"
          name="Things to do"/>
    <item jid="stuff.to.not.do"
          name="Things to avoid doing"/>
  </query>
</iq>

 

在 清单 5 中的信息/查询节中的每个返回项目都拥有一个可以订阅到的 JID。信息/查询还允许超出本教程范围的广泛的服务器信息请求。它们中的许多在针对多服务器环境的 web 应用程序上下文中有用,或者作为复杂的分散型协作框架的基础。

 

联机状态

联机状态信息包含在一个联机状态(presence)节中。如果 type 属性省略,那么 XMPP 客户端应用程序假定用户在线且可用。否则,type 可设置为 unavailable,或者特定于 pubsub 的值:subscribesubscribedunsubscribe 和unsubscribed。它也可以是针对另一个用户的联机状态信息的一个错误或探针。

一个联机状态节可以包含以下子元素:

  • show:一个机器可读的值,表示要显示的在线状态的总体类别。这可以是 away(暂时离开)、chat(可用且有兴趣交流)、dnd(请勿打扰)、或 xa(长时间离开)。
  • status:一个可读的 show 值。该值为用户可定义的字符串。
  • priority:一个位于 -128 到 127 之间的值,定义消息路由到用户的优先顺序。如果值为负数,用户的消息将被扣留。

例如,清单 6 中的 boreduser@somewhere 可以用这个节来表明聊天意愿:


清单 6. 样例联机状态通知

					
<presence xml:lang="en">
  <show>chat</show>
  <status>Bored out of my mind</status>
  <priority>1</priority>
</presence>

 

注意 from 属性此处省略。

另一个用户 friendlyuser@somewhereelse 可以通过发送 清单 7 中的节来探测 boreduser@somewhere 的状态:


清单 7. 探测用户状态

					
<presence type="probe" from="friendlyuser@somewhereelse" to="boreduser@somewhere"/>
Boreduser@somewhere‘s server would then respond with a tailored presence response:
<presence xml:lang="en" from="boreduser@somewhere" to="friendlyuser@somewhereelse">
  <show>chat</show>
  <status>Bored out of my mind</status>
  <priority>1</priority>
</presence>

 

这些联机状态值源自 “个人-个人” 消息传递软件。show 元素的值 — 通常用于确定将向其他用户显示的状态图标 — 在聊天应用程序之外如何使用现在还不清楚。状态值可能会在微博工具中找到用武之地;例如,Google Talk(一个 XMPP 聊天服务)中的用户状态字段的更改可以被导入为 Google Buzz 中的微博条目。

另一种可能性就是将状态值用作每用户应用程序状态数据的携带者。尽管此规范将状态定义为可读,但没有什么能够阻止您在那里存储任意字符串来满足您的要求。对于某些应用程序而言,它可以不是可读的,或者,它可以携带微格式形态的数据负载。

您可以为一个 XMPP 实体拥有的每个资源独立设置联机状态信息,以便访问和接收连接到一个应用程序中的单个用户的所有工具和上下文的数据只需一个用户帐户。每个资源都可以被分配一个独立的优先级;XMPP 服务器将首先尝试将消息传递给优先级较高的资源。

 

XMPP 使用 BOSH 越过 HTTP

要通过使用 JavaScript 的 XMPP 进行通信的 web 应用程序必须符合一些特殊要求。出于安全考虑,不允许 JavaScript 从 web 页面的域与不同域上的多个服务器通信。如果您的 web 应用程序界面被托管在 application.mydomain.com,所有 XMPP 通信也必须发生在 application.mydomain.com

防火墙是另一个问题所在。理想情况下,如果您将 XMPP 用作您的 web 界面的实时元素的基础,那么您希望它对防火墙后面的用户有效。但是,公司防火墙通常只对少数几个协议开放几个端口,以便允许 web 数据、电子邮件和类似的通信通过。默认情况下,XMPP 使用端口 5222,这很可能是公司防火墙阻止的端口。

假设您知道您的用户前面的防火墙在端口 80 上允许 HTTP(这是用于访问 web 的默认协议和端口)。理想情况是您的 XMPP 通信能够越过该端口上的 HTTP。但是,HTTP 的设计并不针对持续连接。web 的架构不同于实时数据所需的通信架构。

下面我们看看 Bidirectional-streams Over Synchronous HTTP (BOSH) 的标准,该标准为双向同步数据提供一个模拟层。借助这个标准,可以与一个 XMPP 服务器建立一个较长的 HTTP 连接(时长一分钟或两分钟)。如果新数据在那个期间到达,则 HTTP 请求返回数据并关闭;否则,该请求只是失效。不管是哪种情况,一旦一个请求关闭,另一个请求将重新建立。尽管结果是对一个 web 服务器的一系列重复连接,但它是一个比 Ajax 轮询更有效的数量级,特别是因为连接到的是一个专业服务器而不是直接连接到 web 应用程序。

BOSH 上的 XMPP 允许 web 应用程序通过一个原生连接持续与 XMPP 服务器通信。客户端通过端口 80 上的 HTTP 上的一个标准 URL 连接。然后,web 服务器将这个连接代理到由 XMPP 服务器操作的一个不同端口 — 通常是 7070 — 上的 HTTP URL。这样,无论何时数据被发送到 XMPP 服务器,web 应用程序只需使用一些资源,而 web 客户端可以使用通常支持的 web 标准从防火墙后操作。维持 BOSH 的较长 HTTP 轮询的开销主要由 XMPP 服务器而不是 web 服务器或 web 应用程序承担。web 服务器和 XMPP 服务器都不会受到与使用 JavaScript 进行通信一样的域限制,正是因为这一点,消息才能够被发送到其他 XMPP 服务器和客户端。

现在,您理解了 XMPP 如何适合实时 web,可以下载并设置它,以便开始创建这个 Pingstream 应用程序。


XMPP学习及使用1