Jabber '协议概述

Peter Saint-Andre

stpeter@jabber.org

1.4Jabber服务协议的概述

1. '介绍



    Jabber是一个由开源社区发起并领导开发的即时消息和在线状态的系统。Jabber系统和其它即时消息(IM)服务的一个功能上的差别在于Jabber拥有开放的XML协议。在保持Jabber1.0版本有关消息核心以及在线状态的协议的基础上,增加了一些必要的扩展。本文档将对Jabber服务器1.4版的Jabber协议进行介绍。

    XML上下文的数据流中,Jabber开放的XML协议包括三个顶级XML元素:

l          

l          

l           (info/query)

每一个元素通过属性和名字空间包含大量的数据,这些属性和名字空间都是Jabber的组成部分(但不包括为特殊应用的名字空间应用)。下面将描述整个Jabber协议。如果您想获得关于Jabber系统结构的信息,请查看名为Jabber技术概述的文档(http://docs.jabber.org/general/html/overview.html)。

2. XML'

    一个Jabber会话由两个平行的XML流组成,一个从客户端到服务器端,另一个从服务端到客户端。当一个Jabber客户端连接上一个Jabber服务器时,这个客户端将发起一个客户端-服务器的XML流,同时作为响应,服务器也将发起一个服务器-客户端的XML流。所有,以及元素都被放在这些XML流的上下文中。下面就有一个例子:

 

    SEND:<?xml version="”1.0” encoding=”UTF-8” ?>

    SEND:

SEND:to=’jabber.org’

SEND:xmlns=’jabber:client’

SEND:xmlns:stream=’http://ether.jabber.org/streams’>

RECV:

RECV:xmlns:stream=’http://etherx.jabber.org/streams’

RECV:id=’39ABA7D2’

RECV:xmlns=’jabber:client’

RECV:from=’jabber.org’>

(XML for user session goes here)*

SEND:

RECV:

 

*注意:必须等到当一个元素(特别指一个’jabber:iq:auth’名字空间下的元素)发送了认证信息后,服务器才会同意用户会话进行工作。

3. '元素



    元素是Jabber开放XML协议三个顶级元素中的一个。它被用来包含两个Jabber用户间互相发送消息内容,或者两个Jabber IDs之间更一般的消息(因为,通过神奇的传输器,另一个IM系统可以用Jabber ID代替,70902454@icq.jabber.org)。

3.1. '消息类型-'“type”'属性

    Jabber支持几种不同的消息,这些消息通过“type”属性来进行区分。下面就是“type”属性的有效值:

3.1.1. [default]'(不设置'“type”'属性)



    表示本消息是一个普通消息。默认的情况下,客户端在没有设置属性时,将消息类型定为普通消息。

    例子:

 



Wherefore art though Romeo?



3.1.2.type=”chat”

    表示消息需要被一个接一个的显示在聊天界面上(一般是一条接一条的界面,不过实际界面可以被用户自己定义。)

    例子:

 

   

Wherefore art though Romeo?



3.1.3.type=”error”

    表示消息被描述成一个错误条件。实际的错误在消息中用一个元素来描述。一个元素包括一个和一个。下面的例子将演示,当Juliet对一个不存在的用户发送一个“hi”的消息时,将收到的包体:

    例子:

 



to="juliet@capulet.com"

from="flomeo@montague.net"

type="error">

hi

Not Found



3.1.4. type=”groupchat”

    表示本消息需要在一个聊天室的界面中进行显示。

    例子:

 

   

Double, double toil and trouble



3.2. '其它属性

    除了设置“type”属性,还可以通过下面的属性修改元素:

3.2.1. “from”'属性



    消息发送者的标志。总得来说,Jabber ID”from”必须是user@host/resource的格式。从XML的观点来说,这个属性必须包含于所有的元素中,为了对付一些欺骗,实际应用中服务器添加或置换这个属性。因此,它并不是客户端开发者需要关心的问题。

    例子:

 

   

Wherefore art thou Romeo?



3.2.2. “id”'属性

    “id”属性是作为消息的一个唯一标志。它由Jabber客户端或客户库(类似WinJabJabberCOM),生成,客户端用它来为跟踪消息(比如将发送到一个群组聊天室的信息和从这个聊天室收到的消息相关联,或将一个消息与它可能产生的错误相关联)进行标识。“id”属性是可选的,并且在系统的别的方是不可用的。

    例子:

 

   

Wherefore art thou Romeo?



3.2.3. “to”'属性



    表示消息的接收者。Jabber ID“to”属性一般是user@host/resource格式,user@host也可以被接受。如果接收者在线,通过user@host/resource方式解决,如果用户不在线,直接发送离线存储器。这个属性是所有元素必须的。

    例子:

 



Wherefore art thou Romeo?



3.3. '中的子元素

    下面的子元素是用来定义一个Jabber消息的上下文或数据元。

3.3.1

    这个元素包含消息的主要文本。元素只存在于一个元素中,并且只能包含明文。不过,它可以由其它子元素构成(如)。

3.3.2.

    当消息类型属性被置为“error”时,这个元素将被使用。错误代码被一个”type”属性定义,这个”type”属性包含代码数,通过元素来包含一个这个错误的文本描述。元素必须包含在原始消息的中。下面的例子将演示,当Juliet向一个不存在的用户发送“hi”消息后的包体:

    例子:



to="juliet@capulet.com"

from="flomeo@montague.net"

type="error">

hi

Not Found



    附文中有非法代码与它们所定义的含义表。

3.3.3.

    元素结合XHTML名字空间(http://www.w3.org/1999/xhtml),为一个消息提供一个交替的名字空间,就像在email世界中规定同时发送普通文本与HTML版本的邮件消息一样。这个功能使用的是W3C开发的基本XHTML格式(http://www.w3.org/TR/xhtml-basic/),关于Jabber在消息中的XHTML执行细节,请看Jabber消息的暂定格式规范(http://docs.jabber.org/draft-proto/html/xhtml.html)。所有XHTML的根元素都必须是。这个根元素中,支持任何一个基本XHTML格式中定义的元素,但不是每一个元素可以通过即时消息的上下文来进行理解。

3.3.4.

    这是一个可选元素,包含消息的标题。

3.3.5.

    当一个Jabber客户端发送一个”chat”类型的消息,将生成唯一的线程ID(通常是由发送者的Jabber ID和当前时间生成的一个hash)。消息发送者和接收者在聊天中后续的消息都包含一个子元素,这个子元素包含原始线程ID。这使得消息发送者和接收者对回复进行认证并创建一个对话线程。下面的是例子:



from="juliet@capulet.com/balcony"

to="romeo@montague.net/orchard"

type="chat">

AAC96FEFDE226C2C04BE8F2E19A2151B

Art thou not Romeo and a Montague?



 



from="romeo@montague.net/orchard"

to="juliet@capulet.com/balcony"

type="chat">

AAC96FEFDE226C2C04BE8F2E19A2151B

Neither, fair saint, if either thee dislike.



3.3.6.

    元素作为一个扩展机制来使用,或发送从服务器发送命令给客户端,或从一个客户端发送命令到另一个客户端。每次用到这个元素时,必须指明相关的XML名字空间。单一消息可以包含多个子元素实例。下列已定义的名字空间可以被子元素使用:

l           jabber:x:autoupdate

l           jabber:x:delay

l           jabber:x:encrypted

l           jabber:x:oob

l           jabber:x:roster

这些名字空间在下面的Jabber名字空间中被更完整地描述。

下面是一个使用jabber:x:delay名字空间在加入的群组聊天室的显示上下文的例子:

 



type=’groupchat’

from=’cauldron@conference.witches.org’>

Thrice the brinded cat hath mew’d.



stamp=’10541031T21:43:40’>Cached In GC History





4. '元素



    元素提供关于一个Jabber实体的可用性信息。任何一个通过一个Jabber ID确认的实体可以与另一个实体进行在线状态信息的通信,这种通信大多以订阅在线状态信息的方式进行。

    所有实体表现出的在线状态不是可用就是不可用可用状态表示发送者可以立即收到消息。不可用状态表示发送者不能在当前时间收到任何数据。默认情况下,所有元素除非包含type=”unavailable”属性外,都表示可用可用的更多特殊形势通过子元素进行指定。

4.1. “type”'属性

    “type”属性根据不同目的使用。除了提示其它用户本用户的可用性状态的普通用法外,还包括订阅、取消订阅、以及探测在线状态信息。下面是“type”属性的可用值:

4.1.1. type=”available”

    如果没有包含任何“type”属性,在线状态包将被设为type=”available”,用来提供用户在线时的信息。一个type=”available”的在线状态包通常包含一个元素以详细说明Jabber实体的可用类型,以及一个元素,该元素包含人能看懂的关于该可用类型的描述。在线状态包还可以包含一个元素,来指明该连接的Jabber ID的优先级。

    例子:

 

   

from="juliet@capulet.com/balcony"

to="romeo@montague.net/orchard"/>

away

Stay but a little, I will come again.



   

    元素的可用值为:

l           away――Jabber用户或实体在线,但不能马上联系上

l           chat――Jabber用户或实体在线并有兴趣聊天

l           dnd――Jabber用户或实体在线,但不想被打扰(“dnd”表示“do not disturb”

l           xa――Jabber用户或实体在线,但已经离开很长时间了(“xa”表示“extended away”

可以包含任意特性数据,这些数据通常包含对于用户状态的描述,如,开会中“away”的一个表现值,或者忙于编码可以是“dnd”的一个表现。

4.1.2. type=”error”

    当一个在线状态包发送给一个不存在的JID时,或在发送在线状态请求时发生一个错误时,服务器都将返回一个type=”error”的在线状态包。

    下面是一个例子(注意域名的类型):

 



       type="subscribe"

to="romeo@montague.nett"/>

 

    回复的例子:

 

   

       from="romeo@montague.nett"

to="juliet@capulet.com/balcony"

type="error">

packet.



4.1.3. type=”probe”

    向一个特定实体(实体不能发送在线状态消息给自己)发出在线状态信息请求。服务器而非客户端,进行一个探测请求。实际上,虽然Jabber客户端也可以发送一个探测一个指定Jabber用户是否在线的请求,但通常都是服务器端发出探测请求。注意,只有在发起请求的用户在请求所对应的用户的花名册上,并且拥有“from”“both”订阅类型时,服务器才会发出探测请求。在下面的例子中,我们将看到我们的英雄RomeoJuliet发出一个探测请求,Juliet所在服务器回复一个状态报告包(包含jabber:x:delay名字空间中指明的上次在线状态更新的时间戳)

    探测请求的例子:

   

   

from="romeo@montague.net"

to="juliet@capulet.com"

type="probe"/>

 

    回复探测请求的例子:

 

   

from="juliet@capulet.com/."

to="romeo@montague.net">

Stay but a little, I will come again.

away

     

xmlns="jabber:x:delay"

from="juliet@capulet.com/."

stamp="15310309T23:47:15"/>



4.1.4. type=”subscribe”

    发送一个请求,这个请求是接收者在其在线状态改变时,自动发送在线状态信息给发送者。

    例子:

 

   

from="romeo@montague.net"

to="juliet@capulet.com"

type="subscribe"/>

 

4.1.5. type=”subscribed”

    发送一个消息,表明发送者接受了接受者对其再现状态订阅的请求。从现在起,当发送者的在线状态信息改变时,服务器将会把状态信息发送给接收者。

    例子:

 

   

from="juliet@capulet.com"

to="romeo@montague.net"

type="subscribed"/>

 

 

4.1.6. type=”unavailable”

    例子:

 

   

from="juliet@capulet.com"

to="romeo@montague.net"

type="unavailable">

Disconnnected



4.1.7. type=”unsubscribe”

    发送一个请求,表明接收者停止发送在线状态信息给发送者。

    例子:

   



from="juliet@capulet.com"

to="romeo@montague.net"

type="unsubscribe"/>

4.1.8. type=”unsubscribed”

    这种类型的在线状态包有两个用途:

1.    发送一个表明发送者接受接收者关于不订阅其在线状态信息的请求的通知。服务器将不再发送发送者的在线状态信息给接收者。

2.    拒绝一个订阅请求。服务器将不再发送发送者的在线状态信息给试图订阅其在线状态信息的用户。

例子:

 



from="romeo@montague.net"

to="juliet@capulet.com"

type="unsubscribed"/>

4.2. '其它''属性



    除了设置“type”属性,还可以通过下列属性修改元素:

4.2.1. “from”'属性

标识在线状态包的发送者。一般Jabber ID“from”属性中必须写成user@host/resource的格式。从XML的观点来说,这个属性是所有元素所必须的,而在实际应用中,都是服务器对该属性进行添加和修改(防止一些欺骗的手段),因此客户端开发者不需要考虑它。

例子:

 



4.2.2. “id”'属性



    为在线状态请求包配置唯一的验证符。“id”属性由Jabber客户端或客户库(如GabberJabberCOM)生成,客户端用其为在线状态包的轨迹进行验证。“id”属性是可选的,并且不能用于系统其它任何地方。

    例子:

 

   

4.2.3. “to”'属性

    标识在线状态包的接收者。一般Jabber ID“to”属性中都是user@host/resource的格式,user@host的格式也是可以的,但当接收者在线时,它将转化为user@host/resource。在Jabber的一般用法中,Jabber客户端向服务器发出在线状态包,服务器将这些在线状态包发送到该用户花名册上所有的用户。当一个Jabber客户端发送的元素中不含“to”属性时,服务器将将其转化为在线状态包发送给指定的Jabber实体。下面的例子将演示这一过程。

    发送给服务器的在线状态的例子:

 

   

Stay but a little, I will come again.

away



 

服务器发给指定用户的在线状态例子:

 



to="romeo@montague.net/orchard"

from="juliet@capulet.com/balcony">

Stay but a little, I will come again.

away



4.3. '的子元素



    下面的元素用于协议。

4.3.1.

    Jabber中,根据Jabber知道的设备数或位置数,一个用户可以建立相同数量的连接。用户可以给每一个资源赋予一个数字优先级(通过对指派给该资源的Jabber客户端进行设置),这个优先级与元素的子元素方法进行通信。

    当在线状态包发送到服务器后,服务器有能力决定哪一个连接资源需要指派一个单一的Jabber ID,该资源将收到发往该Jabber ID的消息,这是因为数字最高的资源是默认或第一资源。如果最高优先级的资源不可用,消息和其它Jabber通信将发送给次高优先级的资源(当其它资源都不可用时,一个没有从默认数到0的优先级的客户端,也可以成为第一资源)

    举例来说,Julie可以通过两个资源:the balcony the chamber连接她的帐号juliet@capulet.com。如果“balcony”资源的优先级被设置为“2”,而“chamber”资源的优先级被设为“1”,而且两个资源都可以连接,消息将发送到juliet@capulet.com/balcony,而不是juliet@capulet.com/chamber

4.3.2.

    可选的元素告诉Jabber客户端如何显示一个用户的在线状态。标签是一个典型的标签的伴随标签,标签包含更多在线状态改变理由的描述信息。如果元素不在元素中,Jabber接收客户端将指明该用户处于普通状态。下面是四个可选项:

标签

含义

chat

该客户可以马上联系上

away

该客户在线,但刚刚离开(如吃午饭,开会)

xa

该客户在线,但已经处于非活动状态很长时间了。

dnd

该用户处于谢绝打扰的模式


4.3.3.

    元素包含更多关于用户在线状态的描述。大多数Jabber客户端都包含一些元素的默认设置;另外,它们还允许我们提供富有个性的描述如我在吃午饭或者钓鱼中

4.3.4.

元素作为一个扩展机制来使用,或发送从服务器发送命令给客户端,或从一个客户端发送命令到另一个客户端。每次用到这个元素时,必须指明相关的XML名字空间。单一消息可以包含多个子元素实例。下列已定义的名字空间可以被子元素使用:

l           jabber:x:autoupdate

l           jabber:x:delay

l           jabber:x:encrypted

l           jabber:x:oob

l           jabber:x:roster

这些名字空间在下面的Jabber名字空间中被更完整地描述。

5. '元素

    信息/查询(IQ)Jabber中在两个实体间构建一个根本的会话,并且允许实体间来回传送XML格式的请求和响应。信息/查询主要的用处是取得或设置公共的用户信息,比如名字,电子邮件,地址等等。但它的灵活设计使得任何种类的会话都可以发生。任何通过一个Jabber ID标识的实体都能通过一个IQ与其它实体进行会话。

5.1. “type”'属性



    元素的“type”属性用于决定信息/查询是请求还是响应。下面是“type”属性的可用值:

5.1.1. type=”error”

    表示查询失败。实际错误在元素的子元素中描述。

    例子:

   

   

Forbidden



5.1.2. type=”get”

    找回指派给一个查询名字空间的信息。如果没有设置属性,默认情况,这个属性包含在查询中。一个type=”get”元素由一个子元素构成,这个子元素通常是,但也可以是或另一个已有的子元素。不过,指定的子元素必须左空,以便接收一个用户所需提供的信息的空间的清单。一个成功的get查询的结果type=”result”元素,是元素嵌套在包含了所需信息的指定子元素(如)。

    例子:

   



   

5.1.3. type=”result”

    表示包是一个type=”get”type=”set”查询的成功响应。这个成功查询的结果是一个type=”result”元素,该元素嵌套在一个包含所查询的信息的子元素中(如)。一个成功的查询或获取查询结果的返回是一个空的type=”result”元素。一个不成功的返回是一个type=”error”元素。通常与“id”属性的获取、设定查询有关。

例子:

   



vCard data goes here





5.1.4. type=”set”

    表示包是对设值或更改现有数据值。一个type=”set”元素总是包含一个指定的子元素,通常是,也可以是或者其它可以接受的子元素。一个设置命令查询的成功返回是一个空的type=”result”元素。

    例子:

 

   



vCard data goes here





5.2. '其它''属性

    除了“type”属性外,IQ元素还可以通过下面的属性进行修改。

5.2.1. “from”'属性



    表示iq包的发送者。从XML观点来说,这个属性是所有元素所必须的,实际应用中这个属性是服务器来增加和修改的(为防止某些欺骗),因此客户端的开发人员不需要过多关注这个属性。

    例子:

   

   

5.2.2. “id”'属性

    一个iq包只有唯一的标识。Jabber客户端或客户库生成“id”属性,客户端通过它来标识包,以完成对包的跟踪(如:一个type=”result”对应一个type=”get”type=”set”)。“id”属性是可选的,并且不能用于系统的其它地方。

    例子:

   



5.2.3“to”'属性



    表示iq包的接收者。在Jabber客户端,对应“to”属性的Jabber ID的格式为user@host/resourceuser@host也是可以的,如果用户在线,user@host将被置换成user@host/resource,如果用户不在线,user@host将被直接指向一个离线模块(对于类似服务器端网关这种非用户实体,情况又有所不同,因为它们不拥有资源,并且只是简单通过Jabber ID的一个“server”端口进行标识。Jud.jabber.org——Jabber用户目录就是这样一个例子)。如果包没有属性,服务器将对该包进行处理。

    例子:

   

5.3 '的子元素

    下面介绍元素的子元素。

5.3.1.

    iq的属性类型被设置为“error”时,将用到本属性。错误代码根据一个含有错误数的“type”属性进行定义,员司包含该错误的文本描述。比如,下面就是一个坏请求:

   

    Bad Request

   

    在附录中,列有错误编码及其对应的错误描述。

5.3.2.

    元素为客户端-服务器之间交互提供一层安全保护。它被用户jabber:iq:registerjabber:iq:search名字空间中。

    当一个客户端发起一个与服务其之间的交互时,服务器将发送一个包含一个唯一值的给客户端。客户端在返回的消息中,包含上诉唯一值到子元素中。这样,服务器就认为该客户端时接收到原来密码的同一实体。

5.3.3.

    元素中的子元素定义所执行的查询的类型。特殊情况下,查询可以拥有一个特殊的名字空间,这个名字空间是一个通过“xmlns”属性定义的子元素。一个元素中只能有一个子元素。

    下面的例子是当我通过我的Linux机器上的Grabber客户端登陆到服务器上,将出现的XML

 





stpeter

Gabber

f1e881517e9917bb815fed112d81d32b4e4b3aed





 

就象你看到的那样,认证查询通过客户端认证名字空间(jabber:iq:auth)向服务器发送认证信息。一般,Jabber核心协议保留jabber:iq:*这样的名字空间。而开发者可以通过增加jabber:iq:*名字空间对Jabber核心进行扩展。这些将在下面的名字空间中进行更完整的描述。

一个空子元素可以与元素一起发送,这样可以找回与子元素中指定的名字空间相关的一个Jabber实体的信息。比如,找回一个客户端的花名册(联系人名单),下面的XML可以被发送:







6. '名字空间



    现面是Jabber中的标准名字空间:

l           jabber:iq:agent——代理工具

l           jabber:iq:agents——有效的代理组

l           jabber:iq:auth——简单用户认证

l           jabber:iq:autoupdate——版本更新申请

l           jabber:iq:browse——Jabber浏览

l           jabber:iq:conference——一般会议

l           jabber:iq:gateway——用户地址网关

l           jabber:iq:last——上次使用时间

l           jabber:iq:oob——绑定数据输出

l           jabber:iq:private——私有数据存储

l           jabber:iq:register——注册请求

l           jabber:iq:roster——花名册(联系人名单)管理

l           jabber:iq:search——用户数据库查询

l           jabber:iq:time——客户端时间

l           jabber:iq:version——客户端版本

l           jabber:x:autoupdate——版本通知申请

l           jabber:x:conference——会议邀请

l           jabber:x:delay——显示的对象

l           jabber:x:encrypted——加密的消息

l           jabber:x:envelope——消息封套

l           jabber:x:event——消息事件

l           jabber:x:expire——消息终止

l           jabber:x:oob——绑定数据(文件传输)输出

l           jabber:x:roster——内部的花名册条目

l           jabber:x:signed——标记的在线状态

l           vcard-temp——临时的vCard

下面将详细介绍这些名字空间的细节。

6.1. jabber:iq:agent——'代理工具

    代理工具名字空间包含一个代理的所有工具。jabber:iq:angents查询通常用于注册到一个指定的服务、代理、传输器。

    它也可能检验一个指定代理的所有工具细节。比如,一个客户端可以决定一个开放的注册是否可以进行。

    例子:

   





AIM Transport

This is the AIM Transport

AIM/AOL ScreenName

aim









6.2. jabber:iq:angents——'可用的代理列表



    可用代理列表名字空间包含一组实体,这些实体拥有一些特殊工具,并能够为另一个实体提供服务。大多数情况下,可用代理列表是用来显示一台服务器上的可用传输器列表。

    注意:这个名字空间被jabber:iq:browse的支持者所反对(??)。

    例子:

 

   





User Directory



You may register and create a public searchable

profile, and search for other registered Jabber

users.



jud









AIM Transport

This is the AIM Transport

AIM/AOL ScreenName

aim









6.3. jabber:iq:auth——'简单客户端认证

简单客户认证名字空间是对客户端进行认证的一个简单机制,它为客户端到服务器之间的连接创建一个资源。

认证成功返回一个type=”result”IQ响应。不成功,就返回一个错误IQ元素。

如果没有发送名字空间或密码,如果服务器允许,服务器将创建一个匿名资源。

例子:

 





juliet

secret

balcony









juliet

a5e052c48c455bf21d937ccf17225916d9486b09

balcony









juliet

secret

balcony





6.4. jabber:iq:autoupdate——'版本更新申请



    版本更新申请名字空间允许客户端对所有可用软件更新进行申请请求。

    例子:







6.5. jabber:iq:browse——Jabber'浏览

    Jabber浏览名字空间的作用是,一是为Jabber世界中已有的Jabber IDs增加结构的一个途径,一是发现为不同Jabber实体提供的服务或应用。一个Jabber浏览的基本概念就是一个jid-type(类似文件的一个模拟类型的概念)。下面是想象中的最高级别的jid-types,不过随着时间的推移,一些其它的逻辑将被增加进来:

l           应用

l           会议

l           标题

l           关键字

l           回报

l           服务

l           用户

上面每一个范畴都有一个子类列表,如用户/客户端,或用户/设备(客户端设备只是一个资源的概念),会议/irc,服务/icq,标题/库存。一般的,通过使用jabber:iq:browse名字空间你将拥有一个与上述某一jid-type范畴相对应的元素,这个元素通常有一个指定子类型的“type”属性。

Jabber:iq:browse的一个用户就是代替jabber:iq:agents名字空间来列出一个服务器上所对应的代理或其它服务。下面是一个浏览服务器的例子:

Jabber客户端发送:

 







 

Jabber服务器返回:

 





type="jabber"

jid="jabber.org"

xmlns="jabber:iq:browse>



type="jud"

jid="users.jabber.org"

name="Jabber User Directory">

jabber:iq:register

jabber:iq:search





type="aim"

jid="aim.jabber.org"

name="AIM Transport">

jabber:iq:register

jabber:iq:search





type="private"

jid="conference.jabber.org"

name="Private Chatrooms">







 

在会议中的一个例子:

 













6.6. jabber:iq:conference——'一般会议



    一般会议名字空间提供一个会议室的机制(如群组聊天室),并提供该会议室的入口。这个名字空间与元素配套使用来获得一个会议室的信息,包括要求加入这个会议室的请求信息,以及为这个会议室设置信息(通常是为了加入这个房间)。一个普通进入房间的请求将和下面的类似:

1.    想要加入的房间提交一个type=”get”的请求。这个XML将是下面的形式:

 







 

2.    接受来自要加入的房间的type=”result”iq响应,其中有进入房间所需要的参数。这个XML将是下面的形式:

 











Room Name





 

这里所有的请求元素的子元素都是可选的。昵称(nickname)元素表示进入房间需要一个昵称。密钥(secret)元素表示进入房间的用户需要提供一个密码。独处(privacy)元素表示如果用户在一个type=”set”(下面可以看到例子)的iq包中提供这样的元素,会议服务器将隐藏用户的真实Jabbere ID。名字(name)元素是房间的名字(用jabberiq:browse浏览房间时可以看到同样的房间名)。

3.    发送当前状态给要加入的房间。XML如下:

 



 

4.    想要加入的房间提交一个type=”set”iq包以及进入所需信息。XML如下:

 





mynick

mysecondnick

thesecret





 

注意在用户第一个选择被接受时,用户可以提交多个昵称。如果用户没有提供昵称,服务器将指派一个昵称给用户。

5.    收到你加入的房间的通知,这个iq以下面的格式返回:

 





room@server/uniqueidnumber

mynick





 

这个唯一的ID号是用户Jabber ID的一个哈希。

6.7. jabber:iq:gateway——'用户地址网关

    一般通过一个网关或传输器(比如AIM传输器)来实现与非Jabber网络用户的通信,每一个非Jabber网络都可能拥有唯一的用户地址,这个地址可能和Jabber IDs并不统一。用户地址网关就是解决这个问题的,它使得Jabber客户端向非Jabber网络用户发出订阅时给出正确Jabber IDs变得很容易。这些通过对用户进行提示和会话,引导用户提供创建一个正确Jabber ID所需信息来实现。下面将演示这种普通的交互:

1.    客户端向网关发出带有jabber:iq:gate名字空间查询的iq get

 







 

2.    服务器返回一个包含请求批准及其描述文本的iq

 







Please enter the AOL Screen Name of the person

you would like to contact.



Screen Name





 

3.    客户端向网关发送一个包含指定信息的iq set

 





Joe Cool





 

4.    服务器返回一个包含正确Jabber IDiq result

 





JoeCool@aim.jabber.org





6.8. jabber:iq:last——'最后一次在线时间



最后一次在线时间名字空间提供一个标准的方法,通过这个方法可以查询一个Jabber实体一个或多个服务更新时间,或者一个客户最后一次连接(或活动)到一台服务器上的时间。每一次返回的值都是一个秒的数目。进行查询的实体可以通过下面的法则解释获取的信息:

l           user@server/resource——用户最后一次活动的时间

l           user@server——用户最后一次连接的时间

l           server——服务器最后一次启动的时间

例子(服务器):

 







 







 

例子(客户端):

 







 





Disconnected





 

6.9. jabber:iq:oob——'绑定数据输出

    绑定数据输出名字空间为客户端-客户端的文件传输提供一个标准方法,通过服务器的传输/代理的传输器实现一个特殊的名字空间:

例子:

 





http://denmark.com/act4/letter-1.html

There’s a letter for you sir.





6.10. jabber:iq:private——'私有数据存储



    私有数据存储名字空间是Jabber服务器上存储私有数据的一个机制。存储的数据可以是任何合法的XML数据。在服务端存放客户端的当前状态信息是这个名字空间的典型用法。

    例子1(客户端存储私有数据):

 







Hamlet







 

例子2(客户端找回私有数据):

 











 

另外,对于私有数据存储,1.4版服务器支持服务器上所有有效名字空间(以“jabber”开头的名字空间保留,为Jabber系统进行调用)的XML数据。这个数据存在向服务器端发送type=”set”iq包的用户的花名册中。

例子1(客户端保存公有数据):

 







Thai

blue

Bach







 

例子2(客户端找回公有数据):

 











6.11. jabber:iq:register——'注册请求

注册请求名字空间对一个或多个服务进行注册。它也被用来更新或删除一个注册。

例子:





Some instructions to be displayed when the

user is filling out the form.















6.12. jabber:iq:roster——'联系人名单管理



联系人名单管理名字空间是客户端用来管理他们的花名册的,该花名册保存在服务器上,这样用户可以在任何地方得到它。花名册是该帐号上所有订阅信息的命令列表,包括用户昵称和联系列表。当用户从任何一个资源登陆上来,服务器将把花名册发送给用户。

花名册是一个条目的列表。每一个条目元素都有描述它的属性。每一个条目元素都包含它所属组的组元素。下面是描述元素的属性:

l           “jid”是条目的Jabber ID

l           “subscription”所有和本条目相关的订阅所对应的用户的当前状态。它可以是“none”(不可订阅),“to”(有一个发到该条目的订阅),“from”(从该条目发送一个订阅给我们),“both”tofrom),或者“remove”(从花名册上删除一个条目)。

l           “ask”是对这个条目的一个请求的当前状态。Subscribe表示请求对该条目进行订阅,它的值就是,Unsubscribe表示取消订阅。

l           “name”表示昵称

通过使用一个,一个Jabber客户端可以控制用户的JabberID,名称属性,群组元素,并且可以在联系人名单上创建或删除联系人。服务器根据客户端对当前状态订阅请求的响应来管理其它所有属性。

订阅请求的例子:

 







name="Romeo"

subscription="none"

ask="subscribe">

friends







 

当一个花名册条目在服务器上改变时,服务器将把这个改变推送给一个用户。这个推送是一个普通的从服务器发送到客户端的包。下面是一个例子:

 











 

    在上面的例子中,服务器推送给客户端一个花名册,表明该用户对romeo@montague.net的当前状态信息有订阅。一个花名册推送可能发生一个连接的任何时间中,该连接是另一个连接修改资源或服务器(修改订阅属性)的结果。客户端只有使用花名册推送来改变花名册的显示。但不是每次花名册推送都对用户起作用。

6.13. jabber:iq:search——'用户数据库查询

    任何一个代理都可以成为一个查询代理。例如,JUD查询Jabber用户,ICQ传输器查询ICQ用户。

    用户发送一个进行可查询域的Get请求:

 



type="get"

id="1001"

to="users.jabber.org"

from="romeo@montague.net/orchard">





 

    查询代理返回一个可查域:





Fill in a field to search for any

matching Jabber User













067941fd96a6a2752a21abcb6d737130dd51dd50





 

    注意这些域是以提示的形式返回。为了传输的安全还包含一个密钥(下面会看到)。用户现在可以在允许的域内进行标准的查询了。用户发回一个Set请求给代理进行实际查询:

   

type="set"

id="1002"

to="users.jabber.org"

from="romeo@montague.net/orchard">



Capulet

11b830e604215c3a2a24652c69fd4efa2a7a5746





 

    服务器根据查询返回结果:

 







Juliet Capulet

Juliet

Capulet

juliC





Sampson Capulet

Sampson

Capulet

sammy









 

    注意有两套items标记含有指定信息。这是因为通过代理发送返回有两种方法:

l           单一返回标签

l           多个“pushed”到用户的标签,类似花名册推送,如在一个时间上的一个记录。

发送的返回的样式是查询代理的一个工具。比如:

 







juliet@capulet.com

juliC

Juliet

Capulet







 

多个查询结果通过服务器进行推送。当所有数据发送完毕,服务器将发送下面的返回结果:

 







 

客户端接收到多个“sets”,每一个都被记录,最后收到一个表示数据结尾返回。在每一个标签中,JID属性都被强制带上。

例子:

 



type="get"

id="1001"

to="users.jabber.org"

from="juliet@capulet.com">





6.14. jabber:iq:time——'客户端时间



    客户端时间名字空间是客户端改变当前时间的一个标准方法。

    例子:

 





20010214T18:55:06

2/15/00 4:55:06 PM





6.15. jabber:iq:version——'客户端版本

    客户端版本名字空间是得到另一个用户的客户端的版本信息的一个标准方法。

    例子:

 





Gabber

0.8.1 (Powered by: jabberoo 1.0.0

Linux 2.2.16-22 i686





6.16. jabber:x:autoupdate——'应用程序版本通知



    版本更改申请名字空间使服务器可以在一个应用的新版本可用时通知客户端。

    例子:

   



A new version of WinJab

is available, see http://winjab.sourceforge.net/





6.17. jabber:x:conference——'会议邀请

    会议邀请名字空间使用户可以邀请其它Jabber用户到指定会议室。相关联信息——该会议室的Jabber ID,内嵌在一个消息的元素中。

    例子:

 



Wherefore art thou Romeo?



6.18. jabber:x:delay——'标记对象延时



    标记对象延时名字空间是消息和当前状态信息的时间戳信息,其中,当一个Jabber实体不在线时,当前状态信息可以保存并在稍后发送。当实体上线时,包含的信息标记对象延时名字空间使Jabber客户端显示出该包体最早产生的时间,而不是它发送给Jabber实体的时间。

    例子:

 



type=’groupchat’

from=’cauldron@conference.witches.org’>

Thrice the brinded cat hath mew’d.



stamp=’10541031T21:43:40’>Cached In GC History





 

6.19. jabber:x:encrypted——'加密消息

    加密消息名字空间用来支持使用公共密钥(一般通过客户端使用PGPGPG,服务端用同样的名字空间进行加密来实现)加密的消息交互。一个相关的名字空间,jabber:x:signed,用来进行当前状态信息的加密。

    例子:

 



This Message is Encrypted



hQEOA7ucqu53AhlPEAP/ZbU6oPnRAbIcUxMK1XRVnkgZ/Agtq1tcTQuEZxbpZLl4

C/4psQGLgBU5h5Y3/khxtJTPXKn1izyc+DRZ8hqn2p4mwC8ioKTBJ6P6dfEpQEyt

a4bimM5fqdeh4gRkMhwThRSJxBCJBVVWFEViu+0KlHHB5AeeL4qwRGb2dhGjIgQD

/R9x0D0qtgBGwf/TVnRGZYRX7epxCNuNDEYKZSs4LEoTPL8CVsAWOzS2QgS0GBqt

tFDKId6XaNu36dB2D8VHdxQnI8RtHo9pfTYDaXWB3dMGTt896iEO/sTuucYObf3s

K5Kygg0uWpBpvQPj8Y041FhnUBz8DRGCnuFLQxCI6ch4ybauXfOKNOGDQwmsnJZm

6OaeVFUwdsedI3c6VdQtodnWVutckR5BOjnn0VqnhrVTu7cp6VXrrRK4g9atPEe6

C4R/MilBjzIJBcET0jhWuAyiBo3gN/6IqYRZNSXL9ZqGPJwNTlYim1EHN3qBqiUw

zUMamEoRzcusn80Z7kylve5ujIeSD/pVwoawHHvLp92kO2hd0yGD0UrWSfKU1o6y

EY8yhZ5P1v02pIKigAUI2c6LTDxt/KhSxQ==

=fijN





6.20. jabber:x:envelope——'消息信封



    消息信封名字空间是表明消息有更多的寻址方式,如联合email进行的寻址。下面是一些所支持的名字空间:

l           to

l           cc

l           replyto

l           from

l           forwardedby

上述每一个元素都带有一个”jid”属性来标识Jabber实体是发送、中转、还是收到消息。

例子:

 



And there I am. Where is my Romeo?



Romeo

God





6.21. jabber:x:event——'消息事件

    消息时间名字空间是标识一条消息的状态的一个机制。现在,事件与一条消息如下般相关联:

l           ——个用户正在对消息进行回复

l           ——发送给指定接收者的消息

l           ——显示给指定接收者的消息

l           ——为离线进行存储的消息

对于客户端来说,这些消息事件的支持是可选的,而且,只有在另一用户在聊天中发出请求,这些消息事件才会被发送。不同的Jabber客户端将对当前状态消息事件进行不同的显示。

对消息通知的请求的例子:

 



from="juliet@capulet.com/balcony"

to="romeo@montague.net/orchard"

id="1001">

By whose direction found’st thou out this place?









 

发送消息通知的例子:

 



By whose direction found’st thou out this place?





1001





 

    可以同时请求多个消息事件。

6.22. jabber:x:expire——'消息到期



    消息到期名字空间是说明一条消息拥有一个有限的存活事件的一个简单扩展。如果消息被离线存储,而到了到期时间,服务器将不再发送该消息。如果一条消息为预览就进行发送,Jabber客户端可以选择不显示该消息。“secondes”属性定义消息发送的事件。

    例子:

 



There’s a letter for you sir





 

6.23. jabber:x:oob——'绑定数据输出

    绑定数据输出名字空间使用户可以通过交换一个标准的URL来实现文件传输的目电。使用jabber:x:oobURLs交换可以包含任一消息(在一个子元素内),感觉就像email里的附件一样。多个附件可以包含在同一个消息中。

    例子:

 



URL Attached.



http://denmark.com/act4/letter-1.html

There’s a letter for you sir





6.24. jabber:x:roster——'内置的花名册条目



    内置的花名册条目使用户可以在一个消息中包含花名册条目,这样很容易在用户之间发送联系方式。每一个花名册条目都包含在一个的一个子元素中。这个元素一般用在一个元素中,但这不是必须的。

    例子:

 





Here are some new Jabber users

to add to your contact list!







Royalty





Friends









6.25. jabber:x:signed——'有符号的当前状态

    有符号的当前状态名字空间用来支持交换使用公共密钥(客户端使用PGPGPG,服务端使用同样的名字空间进行加密)加密的当前状态信息。一个相关的名字空间,jabber:x:encrypted用来支持加密消息。

    例子:

 



away

be back later



iD8DBQA6kasv0xpc2/POfPkRAnz0AJ9UEYYWWSReddIKk3AYMfTFtqQDJwCfbcLd

JcSUOR0FlS+rDFjAPaSMgSM+iNaNm





6.26. vcard-temp——'临时'vCard

    vCard格式是一个电子商务卡的标准格式,通过使用通过互联网进行个人数据的直接交换,如同在非互联网下的环境一样。由于XMLvCard的格式还没有标准化,Jabber.org项目在XMLvCard标准建立之前,暂时设置了这样一个临时名字文件。由于这几年在vCardXML的官方标准的指定上没有任何进步,Jabber.org开发者专门为这个项目创建了一个项目吸引Jabber社区外其它开发者的注意。因此,最后的XMLvCard格式的最后DTD可能会在下面的URL中找到:

http://www.vcard-xml.org/

   

    例子1(对vCard的客户端请求):

 







 

    例子2(客户端收到vCard数据):

 





vCard data goes here





 

    例子3(客户端向服务端发送vCard):





vCard data goes here





7. '使用用例



    这一部分提供一些在Jabber协议上略有不同的观点,通过用例来阐述。下面每一个例子都展示一个Jabber用户完成一个完整的任务的消息流程,该流程包括接收和发送,如注册到一台服务器,登陆,改变当前状态,或者发送一条消息。如果时间允许,我将把这部分引申的更远。

7.1. Jabber'用户注册

    本用例同时邪显示Jabber用户向服务器开启一个socket的连接,以及服务器的响应(如:标签)

1.    Jabber用户通过开启一个从客户端到服务器端的XML流,来申请一个在服务器上的socket连接。

 

SEND:

to=’capulet.com’

xmlns=’jabber:client’

xmlns:stream=’http://etherx.jabber.org/streams’>

 

2.    服务器通过开启一个从服务器到客户端的XML流进行回复。

 

RECV:

from=’capulet.com’

id=’180763465’

xmlns=’jabber:client’

xmlns:stream=’http://etherx.jabber.org/streams’>

 

3.    Jabber用户提供一个需要注册一个帐号(理论上,这需要一个不同的顺序:首先询问服务器需要什么信息,然后服务器器告知客户端需要什么样的信息;但在实际中,假定需要的信息是:用户名,资源,密码)的信息。

 

SEND:             

               Juliet

               balcony

          

       



 

4.    服务器响应一个空的类型为”result”iq元素,表示注册已成功。

 

RECV:   7.2. Jabber'用戶登陆



1.     Jabber用户询问服务器,登陆所需要提供的信息。

 

SEND:            

              Juliet

          

      



 

2.     服务器提示Jabber用户登陆所需要的信息。

 

RECV:            

              Juliet

             

             

              500

              3B905BFD

             

          

      



 

3.     Jabber用户提供所需的信息——在本例中,是一个元素,它是对信息进行一个零度知识认证的一个哈希。(详情请见http://docs.jabber.org/draft-proto/html/zerok.html)。

 

SEND:            

              Juliet

              balcony

                          77d7eacde5e56b9622d0a075cb88361b110f

b9d7

                    

                 



 

4.     服务器响应一个空的类型为”result”iq元素,表明登陆成功。

 

RECV:   

5.     Jabber用户发送当前状态给服务器,表明其在线。

 

SEND: 

           Online

      

7.3. Jabber'用户增加一个联系人

1.     Jabber客户端在花名册上增加一个联系人(只是预备的操作)。

 

SEND: 

          

             

Name=’remeo’/>

                    

                 



 

2.     Jabber用户发送一个对该联系人的订阅请求。

 

SEND: 

           Wherefore are thou?

      

 

3.     服务器发送一个花名单推送给用户一个新条目和一个类型为”none”的订阅(早已有了该订阅)。

 

RECV: 

          

             

name=’romeo’

subscription=’none’/>

                    

                 

 

4.     服务器发送一个类型为”result”iq包,表示花名册推送成功(让人有点疑惑的是,这个包是从Jabber用户发送到Jabber用户的!)

 

RECV: 

from=’juliet@capulet.com/balcony’

to=’julie@capulet.com/balcony’

type=’result’>

 

5.     服务器发送另一个花名单推送,这次是ask=’subscribe’属性,表示订阅的状态未定。

 

RECV: 

          

             

ask=’subscribe’

jid=’romeo@montague.net’

name=’romeo’

subscription=’none’/>

                    

                 



 

6.     Romeo对订阅请求的下一个响应会是什么呢?现在我们假定订阅以Romeo接受订阅请求为结束的。

 

RECV: 

from=’romeo@montague.net’

to=’juliet@capulet.com’

type=’subscribed’/>

 

7.     服务器再次发送一个花名单推送给客户端,这次subscription=’to’,表示订阅请求被接受了(Juliet现在可以订阅到Romeo的当前状态了)。

 

RECV: 

          

             

jid=’romeo@montague.net’

name=’romeo’

subscription=’to’/>

                    

                 

7.4. Jabber'用户获得花名册

           SEND: 

                    

                 

 

RECV:    

                    

                        

jid=’romeo@montague.net’

name=’romeo’

subscription=’both’/>

                    

                 

7.5. Jabber'用户发送一条消息

           SEND: 

                     Wherefore are thou?

                 

    注意:服务器会根据Jabber用户的会话信息加上一个源地址,这样接收者收到消息时,消息中已经包含了源地址。

7.6. Jabber'用户改变当前状态



       SEND: 

                  stepped away…

                  away

             



7.7. Jabber'用户登出

    登出时很容易的,只需要关闭……

 

       SEND: 

       RECV: 

8. '参考文献



    本协议参考以下文献

l           Jabber开发指南(http://docs.jabber.org/jpg/

l           Jabber协议――标准(http://docs.jabber.org/proto/

l           Jabber协议――草案(http://docs.jabber.org/draft-proto/

l           Romeo and Juliet

(http://tech-two.mit.edu/Shakespeare/Tragedy/romoandjuliet/full.html)

9. '结束语

    本文档提供了一个关于JabberXML协议的一个详细的怪数。如果你对本文档有任何问题,请通过emailJabber的方式自由地与作者联系(Peter Saint-Andre),他的帐号时stpeter@jabber.org

10. '版权信息



This document is copyright 2001 by Peter Saint-Andre.

Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License (http://www.gnu.org/copyleft/fdl.html),

Version 1.1 or any later version published by the Free Software Foundation, with no

Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. You may obtain a

copy of the GNU Free Documentation License from the Free Software Foundation by

visiting http://www.fsf.org/ or by writing to:

The Free Software Foundation, Inc.

59 Temple Place - Suite 330

Boston, MA 02111-1307

USA

附录' A '标准错误代码

    下面是Jabber中错误代码的一些简要描述。Jabber服务器在碰到不同的错误条件下生成这些错误代码。一般Jabber错误代码以HTTP规格的RFC 2616http://www.ietf.org/rfc/rfc2616.txt)为基础。但Jabber没有使用所有的HTTP的错误代码,并且Jabber错误代码与HTTP错误代码相对应时,通常只有Jabber自己的含义。注意本附录只包含Jabber服务器生成的错误代码,而不包括服务器端组件如网关发到外部消息系统。

 

错误代码

描述

注意

302

重定向

尽管HTTP规定中包含八种不同代码来表示重定向,Jabber只用了其中一个(用来代替所有的重定向错误)。不过Jabber代码302是为以后的功能预留的,目前还没有用到

400

坏请求

Jabber代码400用来通知Jabber客户端,一个请求因为其糟糕的语法不能被识别。例如,当一个Jabber客户端发送一个的订阅请求给它自己活发送一条没有包含“to”属性的消息,Jabber代码400就会产生。

401

未授权的

Jabber代码401用来通知Jabber客户端它们提供的是错误的认证信息,如,在登陆一个Jabber服务器时使用一个错误的密码,或未知的用户名。

402

所需的费用

Jabber代码402为未来使用进行保留,目前还不用到。

403

禁止

Jabber代码403Jabber服务器用来通知Jabber客户端该客户端的请求可以识别,但服务器拒绝执行。目前只用在注册过程中的密码存储失败。

404

没有找到

Jabber代码404用来表明Jabber服务器找不到任何与JabberID匹配的内容,该JabberID是一个Jabber客户端发送消息的目的地。如,一个用户打算向一个不存在的JabberID发送一条消息。如果接受者的Jabber服务器无法到达,将发送一个来自500级数的错误代码。

405

不允许的

Jabber代码405用在不允许操作被’from’地址标识的JabberID。例如,它可能产生在,一个非管理员用户试图在服务器上发送一条管理员级别的消息,或者一个用户试图发送一台Jabber服务器的时间或版本,或者发送一个不同的JabberIDvCard

406

不被接受的

Jabber代码406用于服务器因为某些理由不接受一个包。例如,这个可能发生在,一个Jabber客户端试图使用jabber:iq:private在服务器上存储信息,但当前的用于存储的名字空间用”jabber:”开头(在Jabber里是一个被存的XML开头)。另一种可能产生406错误的情况是当一个Jabber客户端试图用一个空密码注册到一台Jabber服务器上。

407

必须注册

Jabber代码407当前不被使用

408

注册超时

当一个Jabber客户端不能在服务器准备好的时间内发起一个请求时,Jabber服务器生成Jabber代码408。这个代码当前只用于Jabber会话管理器使用的零度认证模式中。

409

冲突



500

服务器内部错误

当一台Jabber服务器遇到一种预期外的条件,该条件阻止服务器处理来自Jabber客户端的包,这是将用到Jabber代码500。现在,唯一会引发500错误代码的时间是当一个Jabber客户端试图通过服务器认证,而该认证因为某些原因没有被处理(如无法保存密码)。

501

不可执行

当服务器不支持Jabber客户端请求的功能,使用Jabber代码501。例如,该代码只当Jabber客户端发送一个认证请求,而该认证请求不包含服务器配置中定义的任何一种认证方式时,服务器发送Jabber代码501。这个代码还被用于,当一个Jabber客户端试图注册一个不允许注册的服务器。

502

远程服务器错误

当因为无法到达远程服务器导致转发一个包失败时,使用Jabber代码502。该代码发送的特殊例子包括一个远程服务器的连接的失败,无法获取远程服务器的主机名,以及远程服务器错误导致的外部时间过期。

503

服务无法获得

当一个Jabber客户端请求一个服务,而Jabber服务器通常由于一些临时原因无法提供该服务时,使用Jabber代码503。例如,一个Jabber客户端试图发送一条消息给另一个用户,该用户不在线,但它的服务器不提供离线存储服务,服务器将返回一个503错误代码给发送消息的JabberID。当为vcard-tempjabber:iq:private名字空间设置信息时,出现通过xdb进行数据存储的写入错误,也使用该代码。

504

远程服务器超时

Jabber代码504用于下列情况:试图连接一台服务器发生超时,错误的服务器名。

510

连接失败

Jabber代码510目前还没有使用。

转载于:https://www.cnblogs.com/yangaiyoyo/archive/2009/11/03/1594962.html

Logo

瓜分20万奖金 获得内推名额 丰厚实物奖励 易参与易上手

更多推荐