Skip to content

Latest commit

 

History

History
916 lines (670 loc) · 41.8 KB

2016-07-10-xmpp.md

File metadata and controls

916 lines (670 loc) · 41.8 KB

如何学习XMPP协议


如果你是从零开始学习XMPP协议,我建议你follow这样的路线。

先在本地安装一个xmpp服务器和客户端

对于我来说,服务器我使用了ejabberd,因为我Erlang写的比较多,客户端用Adium。

  • Ejabberd:我比较熟Erlang所以选择。
  • Adium:这客户端简洁优雅关键是有Xml控制台来观察XML数据

Adium隐藏属性,打开控制台

XMPP Advanced Features (Default: Off)

To enable the advanced features (only the XML Console menu item right now), run the following command in Terminal:

defaults write com.adiumX.adiumX AMXMPPShowAdvanced -bool YES

To disable it again, use:

defaults write com.adiumX.adiumX AMXMPPShowAdvanced -bool NO

The XML Console allows you to inspect the traffic and manually send XML to a Jabber server. You can open the console in File → (Your Account) menu.

下载一本《XMPP权威指南》啃

XMPP这个领域的书比较少,只此一本,简单好学,下载点我

研究各种XEP协议

看完权威指南,基本上就入门了,下一步就是看各种XEP的详细文档了。

下面的内容是我学习过程的笔记,祝各位玩的开心。

什么是XMPP协议


可扩展通讯与表示协议(XMPP)是一项用于实时通讯的开放技术。它使用可扩展标记语言(xml)作为交换信息的基本格式。本质上看,XMPP 供了一种近乎实时的在实体之间传递 xml 片段的方式。

XMPP 在应用程序中被广泛使用,同样,你也在你的程序中使用 XMPP。为了实现这种可能性,从抽象视角具体到服务和程序的视角来看待 XMPP 是很有帮助的。XMPP 的服务在IETF 的两个主要规格以及几十个扩展规格中被定义。XMPP 应用一般是组织和个人共同关心的软件程序和部署情景,当然核心服务也允许你建立其他种类的应用程序。

现有的 XMPP 定义可能已经被新的取代。要最新版本的修订,访问 官网

Jabber/XMPP中文翻译计划

核心服务


信道加密 这个服务,定义在[RFC 3920]。它在客户端和服务器,或者是在两台服务器之间 供了加密连接。虽然信道加密不一定是令人兴奋的,但它是构造安全程序的重要步骤。

认证 这个服务,同样定义在[RFC 3920],它是安全应用开发的另一重要组成部分。认证系统能保证试图通过网络进行通信的实体首先被服务器验证,它能起到一种网络访问的“守门人”的作用。

出席 这个服务,定义在[RFC 3921]。它允许你发现其他实体的网络可用性。在最基础的水平上,出席服务回答了这样一个问题,“一个实体是在线而且可以通信的、还是离线并不能通信的呢?”出席数据也可以包括更详细的信息(如一个人是否正在会议中)。典型的情况是,出席信息的共享是基于两个实体之间一个显式的出席订阅,以此来保护用户的隐私信息。

联络清单 这个服务,也被定义在[RFC 3921]。它使你可以在XMPP服务器上存储一个联系人名单或者花名册。这一服务最常见的用法是使用即时消息的好友名单,但是任何在服务器有账户的实体都可以使用这个服务,来维持一个已知的信任的实体的列表(例如,它可以被机器人使用)。

一对一消息 这个服务,定义在[RFC 3920]。它允许你发信息给另一个实体。它常用于个人即时通信,但信息的形式可以是任意 XML,而且同一网络的任何两个实体可以交换信息,包括程序,服务器,组件,开启 XMPP 的 web 服务,或是其他的 XMPP 实体。

多方通讯消息 这个服务,定义在[XEP-0045]。它允许你加入一个虚拟聊天室来在众多参与者之间交换信息,这类似于互联网多线交谈(IRC)。信息可以是普通的文本,或者可以包含 XML 延伸来实现更先进的功能,如房间配置、室内投票,以及各种对话控制信息。

通知 这项服务定义在[XEP-0060]。它允许你创建一个通知并把它传递给众多的订阅者。它类似于多方通讯消息,但是它适合用于具有明确的频道或者主题订阅的一对多传递(称为“节点”)。

服务发现 这个服务,定义在[XEP-0030]。它允许你发现被另一个实体支持的特性,以及任何与它相连的附加实体(如聊天房间服务中的一个房间)。

能力广告 这个服务,定义在[XEP-0115]。它是出席服务的延伸服务,它为服务发现数据 供一种简化符号,从而使你能够轻易地缓存被网络上其他实体支持的特性。

结构化数据表单 这项服务,在[XEP- 0004]定义。它允许你和其他实体交换结构化但是灵活的表单(类似于 HTML 表单)。它常用于你需要收集其他实体的 ad-hoc 的信息的场合,如配置和其他任务。

工作流管理 这个服务,定义在[XEP-0050]。它能让你与另一实体在一个结构化工作流中互动。它支持典型的工作流程的行动,例如移动到业务流程的下一个阶段或执行一个指令。它经常与数据表单一起使用。

对等网络媒体会话 这个服务,定义在[XEP-0166]。它允许你来协调和管理与另一实体的媒体会话。这样的会话可用于语音聊天,视频聊天,文件传输,以及其他实时互动等目的。

这些都是你(或者你的应用程序)作为一个参与者,在一个 XMPP 网络中能得到的核心服务。XMPP 社区的开发者在各种 XMPP 扩展中定义了一些附加功能,但在这里,我们将着重于对构建实时应用程序最有益的服务。

消息:message


XMPP 的节是使用基本的”push”方法来从一个地方到另一个地方得到消息。 因为消息通常是不告知的,它们是一种”fire-and-forget”的机制来从一个地方到另一个地方快 速获取信息。消息被用于 IM、groupchat、alerts 和 notifications,以及其他应用。 消息节有五种不同的类型,通过 type 属性来进行区分:

  • normal:类型为 normal 的消息和电子邮件消息最相似,因为它们是单个的消息,对应的回应可能会或也可能不会很快到来。
  • chat:类型为 chat 的消息在两个实体间的实时对话中交换,例如两个朋友之间的即时通讯聊天。
  • groupchat: 类型为 groupchat 的消息在多用户聊天室中交换,和互联网中的中继聊天类似(我们将 在第七章中讨论群聊消息)。
  • headline: 类型为 headline 的消息用于发送警示和通告,并不期望有回应(收到 headline 的客户端 不应该让用户回复)。
  • error: 如果对于先前发送邮件发生错误,实体检测这个问题将返回一个类型为 error 的消息。

私聊消息

<message 
      to="ejabberd2@localhost/2822439916144906745650" 
      from="ejabberd1@localhost/zhuoyikangdeMacBook-Pro" 
      id="purple643fdba3" 
      type="chat">
    <body>fewfwefwef</body>
  </message>

聊天状态通告:XEP-0085

聊天状态通告[XEP-0085] 英文 中文 聊天状态 述了你所参与的对话,可以是以下状态之一:

  • starting: 某人开始一个对话,但是你还没有参与进来。
  • active: 你正参与在对话中。当前你没有组织你的消息,而是在关注。
  • composing: 你正在组织一个消息。
  • paused: 你开始组织一个消息,但由于某个原因而停止组织消息。
  • inactive: 你一段时间里没有参与这个对话。
  • gone: 你参与的这个对话已经结束(例如,你关闭了聊天窗口)。

在对话期间,聊天状态最可能进行如下变化: 在 composing 状态组织完一个消息后,状 态变为 active,等待消息的回复。 但是,从一个具体状态变化到另一个并不是总是有意义。 例如,如果你不暂停一小段时间,在你撰写消息期间,你不能长时间地地变成 inactive。图 4-2 展示了聊天状态之间的可以变化

通过在消息节中嵌入相关的聊天状态元素来进行对话中的状态改变。例如,母亲-女儿 的对话的开始如下所示:

<message from="[email protected]" to="[email protected]"
type="chat"> <body>Hi honey!</body>
<active xmlns="http://jabber.org/protocol/chatstates"/> </message>

通过添加元素到你的消息中,指示了你在对话中处于活跃状态。你的女儿开始 进行回复,这样她的客户端发送给你一个聊天状态更新,通过添加一个元素到 一个空的消息里:

<message from="[email protected]" to="[email protected]"
type="chat">
<composing xmlns="http://jabber.org/protocol/chatstates"/>
</message>

通告之后不久,实际的消息到达,再次使她成为对话中的活跃参与者:

<message from="[email protected]" to="[email protected]"
type="chat"> <body>Hi</body>
<active xmlns="http://jabber.org/protocol/chatstates"/> </message>

这个对话持续一会儿,直到你问她关于奶奶的事:

<message from="[email protected]" to="[email protected]"
type="chat">
<body>Did you visit grandma this afternoon? What did she tell you?</body>
<active xmlns="http://jabber.org/protocol/chatstates"/>
</message>

<message from="[email protected]" to="[email protected]"
type="chat">
<composing xmlns="http://jabber.org/protocol/chatstates"/>
</message>

这里她突然停下码字去接电话,几秒钟后,她的客户端给你发送一个的通告:

<message from="[email protected]" to="[email protected]"
type="chat">
<paused xmlns="http://jabber.org/protocol/chatstates"/>
</message>

一会儿后,她又恢复回答:

<message from="[email protected]" to="[email protected]"
type="chat">
<composing xmlns="http://jabber.org/protocol/chatstates"/>
</message>

最后,跳到对话的结尾,她发送再见并关闭了聊天窗口:

<message from="[email protected]" to="[email protected]"
type="chat">
<body>Everything was fine. I have to go do my homework now.</body> <active xmlns="http://jabber.org/protocol/chatstates"/>
</message>
<message from="[email protected]" to="[email protected]"
type="chat">
<gone xmlns="http://jabber.org/protocol/chatstates"/>
</message>

你正在交流的这个人对接收到你的聊天状态不是总感兴趣。例如,当他正在使用移动电 话进行 IM 时,她宁愿以不能得知你的状态为代价,来节省有限的网络带宽使用。为了探究 是否其他人对你的聊天状态感兴趣,你像平常一样,通过向消息里面添加元素开启 一个对话。如果返回的回复没有任何聊天状态信息,你不得不假定其他人要么不知道如何处 理聊天状态更新,要么是不想要接收到他们。从那时起,你继续你的对话,没有添加任何状 态信息到你接下来的消息中。(自然地,如果你知道其他人不支持聊天状态协议,你就完整 地去掉通告。我们在第五章中谈论发现支持各种协议扩展的方式。)

另一个你不想发送聊天状态通告的原因是隐私。你不想要别人知道你在什么时候正在使 用 IM 客户端(聊天状态通告隐藏的信息)。但是,它也不会总是让所有的通告类型都失效。 你可以配置你的客户端,使它只能发送基本的聊天状态信息(例如,你是是否是 active 或 composing),并且不发送关于更多状态的更多信息,例如 paused、inactive 或 gone。这些基 本信息只会显露你是否正在组织回答,并且留下一迹象表明你是否已经离开你的 IM 客户端 或重新考虑谈话并关闭对话。

格式化消息

XMPP 让你能够自定义消息的外观或表达,使用 定义在 XHTML-IM[XEP-0071]中的一个 HTML 子集:

<message from="[email protected]"
to="[email protected]"
type="chat">
<body>I love this movie I saw last night, it's awesome!</body> <html xmlns="http://jabber.org/protocol/xhtml-im">
<body xmlns="http://www.w3.org/1999/xhtml"> <p>
I <em>love</em>, this new movie I saw last night,
it's <strong>awesome</strong>! </p>
</body> </html>
</message>

vcard

不用担心,XMPP 覆盖了这方面的内容。我们感兴趣的扩展被称为 vCard-temp[XEP-0054], 能让你发布一个电子商务的卡片,叫做 vCard,而且能得到其他人已经发布的 vCards。 vCard 标准(最初是出版在 vCard MIME Directory Profile[RFC 2426]中)定义了许多你想 要标榜的数据字段,包括你的名字、昵称、地址、电话和传真号、所属公司、电子邮件地址、 生日、个人网址、你的头像、甚至还有你的 PGP 密钥。你可以不用发布任何你不想发布的 信息,但是这样做能让人们找出更多关于你的信息,这样可以加快对话。

我们假定在 skh.whu.edu.cn 的 suke 发送一个不请自来的消息给 beta:

<message from="[email protected]"
to="[email protected]">
<body>O Beta do you know the way out of this pool?</body>
</message>

在回复之前,beta 也许会通过发送一个 IQ-get 到 JabberID 检查 suke 的 vCard:

<iq from="ejabberd1@localhost" id="pw91nf84"
to="ejabberd2@localhost"
type="get">
<vCard xmlns="vcard-temp"/>
</iq>

由于这个请求是发送到 suke 的 bare JID,suke 的服务器代表她进行回复:

<iq from="[email protected]" id="pw91nf84"
to="[email protected]"
type="result">
<vCard xmlns="vcard-temp">
<N> <GIVEN>suke</GIVEN>
</N> <URL>http://sku.whu.edu.cn/~suke/</URL> <PHOTO>
<EXTVAL>http://www.cs.whu.edu/~rgs/suke03a.gif</EXTVAL> </PHOTO>
</vCard> </iq>

因此,beta 至少可以在进行聊天之前,浏览 suke 的个人网址并查看她的图片。自然地, 在 vCard 中的所有数据可能是假的,所以可能会为得到的 vCard 结果付出代价。但是,在许 多情况下,有总比没有好!

更新你的 vCard

<iq from="[email protected]" id="w0s1nd97"
to="skh.whu.edu.cn"
type="set">
<vCard xmlns="vcard-temp">
<N> <GIVEN>suke</GIVEN>
</N> <URL>http://skh.whu.edu.cn/~suke/</URL> <PHOTO>
<EXTVAL>http://www.cs.whu.edu/~rgs/suke03a.gif</EXTVAL> </PHOTO> <EMAIL><USERID>[email protected]</USERID></EMAIL>
</vCard> </iq>

阻止和过滤通讯

许多人使用基于XMPP的IM服务,但是你不想要和他们所有人聊天。事实上,你可能会想阻止某个人跟你聊天——如你之前的上司、儿时死敌或者上周在聊天室中你遇到的一个 奇怪的家伙。

因为XMPP开发者关心隐私,他们已经定义了一个通讯阻止的扩展(定义在 Privacy ListsXEP-0016) ,和隐私列表的一个精简界面(定义在 Simple Communication Blocking[XEP-0191]中)。

阻止 [email protected]

<iq from="[email protected]/Psi" id="yu4er81v"
    to="[email protected]"
    type="set">
    <block xmlns="urn:XMPP:blocking">
    <item jid="[email protected]"/> </block>
</iq>

获取阻止列表

<!-- 获取 -->
<iq from="[email protected]/Psi" id="92h1nv8f"
    to="[email protected]"
    type="get">
  <blocklist xmlns="urn:XMPP:blocking"/>
</iq>

<!-- 回复 -->
<iq from="[email protected]" id="92h1nv8f"
    to="[email protected]/Psi"
    type="result">
  <blocklist xmlns="urn:XMPP:blocking">
    <item jid="[email protected]"/>
  <item jid="spammers.lit"/> </blocklist>
</iq>

完整例子

发起屏蔽

<iq xmlns='jabber:client' from='ejabberd16@localhost' to='ejabberd16@localhost/zhuoyikangdeMacBook-Pro' id='push' type='set'>
	<block xmlns='urn:xmpp:blocking'>
		<item jid='ejabberd11@localhost'/>
	</block>
</iq>

出席:presence


只有通过你授权的人才能看到你是否在线。这个授权被称为出席订阅 (presence subscription)。为了让某人看到你的出席,这个人需要向你发送订阅请求,并且 你需要批准该请求。一旦你批准了这个订阅,用户将自动地接收到关于你的网络可用性的常规通知。订阅模型意味着 XMPP 的节本质上是一个简单、专门的发布-订阅方法, 通过这种形式,当你在线,然后将状态改为“会议中”或者“午餐中”,然后离线,向你订阅了出席的人将收到更新的出席信息。

出席订阅

让我们看一下握手订阅过程实际上是怎样运作的。 为了获取某个人的出席信息,你给他发送一个订阅请求(通过 subscribe 类型):

<presence from="[email protected]" to="[email protected]" type="subscribe"/>

当指定的接受者接受到你的出席订阅请求时,他可以接受它(通过 subscribed 类型)或 者拒绝它(通过 unsubscribed 类型):

<presence from="[email protected]" to=" [email protected]" type="subscribed"/>

你可以想象到,为了创造一个双向的出席订阅,当一个人接受了对方发送的订阅请求之后,也需要返回一个订阅请求给对方。

<presence from="[email protected]" to=" [email protected]" type="subscribe"/>

一般来说,你的客户端这时会自动同意请求,而不是要求你同意对方返回的请求。

<presence from="[email protected]" to="[email protected]" type="subscribed"/>

一旦你订阅了另一个人的出席信息,当他的网络可用状况改变的时候,你会自动得到通 知。通知信息的格式如下:

<presence from="[email protected]" to=" [email protected]">
<show>xa</show>
<status>down the rabbit hole!</status> </presence>

出席消息传递

现在你和你的联络者互相被订阅了,那么出席信息是怎样在你们之间传递的呢?这里有 一个简单的概括。

  1. 你和你的服务器商议 XML 流信息(见第 12 章)

  2. 你发送一个初始化的出席节到你的服务器:

    <presence/>

是的,这是你可以看见的最小的 XMPP 小节。初始化出席可以包含更多的可用状态信 息,关于这一点后面会详述。

  1. 你的服务器检查你的名册,然后发送一个出席通知到订阅你的每一个人,发送的格式如 下:
    <presence from="[email protected]" to="[email protected]"/>
    <presence from="[email protected]" to="[email protected]"/>
    [etc.]
  1. 现在每一个订阅了你的出席的人知道你现在在线,并且可以进行通讯。但是你怎么知道 他们是否在线呢? 这里,你的服务器再一次发挥了作用,因为它发送一个出席调查给你订阅的每一个人:
    <presence from="[email protected]" to="[email protected]" type="probe"/>
    <presence from="[email protected]" to="[email protected]" type="probe"/>
    [etc.]
  1. 一旦你的联络者的服务器接受到了调查,它们根据记录检测许可,如果你被允许查看联 络者的出席信息,你将至少会受到来自那些在线的人的一次通知,有时不在线的话也会 给一个通知,包括上一次出席通知的发送时间。
    <presence from="[email protected]"  to="[email protected]"  type="unavailable"> 
<delay xmlns="urn:XMPP:delay"
    stamp="2008-11-26T15:59:09Z"/> 1 </presence>
    <presence from="[email protected]" to="[email protected]"/>
    <presence from="[email protected]" to="[email protected]"/>
    [etc.]

元素是由联络者的服务器添加的,而 UTC 时间戳是出席节被联络者发送的 时间。(在这个例子中,是联络者下线的时间) 注意你可能接收到一片以上的出席片,因为任何给定的联系可能包含多个链接的资源。 你总是接收到不可用的出席信息吗? 有些服务器实现并不针对出席调查返回不可用通知,它们只是简单地忽略掉出席调查。 从理论上说,如果被调查的实体不返回任何出席通知,就可以假设实体并不在线。

出席状态更新

  <presence from="ejabberd1@localhost/zhuoyikangdeMacBook-Pro" > 
    <show>xa</show>
    <status>down the rabbit hole!</status>
  </presence>

下线

<presence type="unavailable"/>
  • 你的服务器向你的联络列表中的所有人广播你不可用的通知。
  • 你的服务器同样向所有你发送了定向出席的实体广播你的不可用状态的通知。
  • 如果你没有其他的在线资源,当你的联系人的服务器接收到一个不可用通知时,它们应该会停止向你发送出席通知。 如果你没有其他的在线资源,你的服务器将停止发送信息给你,将它们储存起来,下次你上线时,将这些信息传递给你。

信息/查询:iq


信息/查询(IQ)节 供了一种用于请求-应答交互和简单工作流的结构,和你熟悉的 HTTP 中的 GET、POST 和 PUT 方法相似。和节不同,一个 IQ 节能包含仅有一个有效载 荷,用于定义处理的请求或接收人采用的行为。另外,发送 IQ 节的实体必须总是接收一个 回复(通常由目的接收者或接受者的服务器产生)。请求和应答通过使用 id 属性跟踪,id 属 性由请求实体生成,并被包含在应答的实体中。最后,type 属性在 IQ 节中有特定的值:

  • get: 请求实体信息,例如请求注册一个账户(类似于 HTTP GET)。
  • set: 请求实体 供一些信息或作出一个请求(类似于 HTTP POST 或 PUT)。
  • result: 应答实体返回 get 操作的结果(例如一个实体必须 供信息用来注册账户),或者确认 一个 set 请求(类似于一个 HTTP200 状态码)。
  • error: 应答实体或一个中间实体,例如 XMPP 服务器,通知请求实体它不能处理 get 或 set 请 求(例如,因为请求的格式不正确,请求实体无权执行该操作等)。早期在 HTTP 中使用的 数字错误代码已被可扩展错误条件的 XML 元素取代。

花名册/获取

  发送一个get类型的ir,query为`jabber:iq:roster`

  <iq type='get'
      id='5595091f-0bf2-45f1-b7be-4dfc575eaa9a-3'>
    <query xmlns='jabber:iq:roster'/>
  </iq>

  回复类型为'result',返回了分组信息

  <iq xmlns='jabber:client'
      from='ejabberd1@localhost'
      to='ejabberd1@localhost/zhuoyikangdeMacBook-Pro'
      id='5595091f-0bf2-45f1-b7be-4dfc575eaa9a-3'
      type='result'>
    <query xmlns='jabber:iq:roster'>
      <item subscription='to' jid='ejabberd3@localhost'>
        <group>Buddies</group>
      </item>
      <item subscription='both' jid='ejabberd2@localhost'>
        <group>联系人列表</group>
        <group>Buddies</group>
      </item>
    </query>
  </iq>

花名册/添加

  发送一个类型为set的iq,query为`jabber:iq:roster`

  <iq xmlns='jabber:client' from='ejabberd1@localhost' to='ejabberd1@localhost/zhuoyikangdeMacBook-Pro' id='push18026560777104592747' type='set'>
    <query xmlns='jabber:iq:roster'>
      <item subscription='none' jid='ejabberd@localhost'>
        <group>Buddies</group>
      </item>
    </query>
  </iq>

  返回一个空的result确定名单更新

  <iq xmlns='jabber:client' from='ejabberd1@localhost' to='ejabberd1@localhost/zhuoyikangdeMacBook-Pro' id='purplebf9a1ae3' type='result'/>

多方通讯互动


群聊基础

群聊的重点是一个有自己的 JabberID 的特定的房间,例如[email protected]

房间是在 teaparty@conference. skh.whu.edu.cn 托管,而不是 skh.whu.edu.cn。这地址是 原先的 jabberd 服务器一部分,其中唯一由核心 XMPP 后台程序处理的服务是出席,名册, 一对一的消息,和一般节路由。其他服务由叫做组件的附加模块处理。这些组件被分配不同 的域名,如在 jabber.org 的群聊服务 conference.jabber.org。这些域名随后用于内部布线,使 任何目标为 conference.jabber.org 的节被自动传送到群聊组件。更多现代 XMPP 服务器项目 遵循同样的做法,虽然 XMPP 没有什么能防止一个如 [email protected] 地址成为群 聊室。

Suke 这样加入我们的 teaparty 房间:

<presence from="suke@ skh.whu.edu.cn /rabbithole" to="teaparty@conference. skh.whu.edu.cn /suke"/>

在你加入一个房间的时候,几件事情发生:

  • 房间里发出了一个参与通知,从你的 JID 到其他参与者。
  • 房间会从所有其他参与者的房间 JID 发送给你的出席,让你的客户可以建立一个专门的房间居住者“名册“。
  • 房间通常会通知你最近的一些交流消息使你掌握谈论的背景。

首先,房间的参与者收到了 Suke 的加入通知:

<presence from="[email protected]/suke" to="[email protected]/underahat"/>
<presence from="[email protected]/Suke" to="[email protected]/chair"/>
<presence from="[email protected]/Suke" to="[email protected]/sleepspace"/>

接下来,Suke 接受到房间参与者的名单

<presence from="[email protected]/Mad Hatter" to="[email protected]/rabbithole"/>
<presence from="[email protected]/March Hare" to="[email protected]/rabbithole"/>
<presence from="[email protected]/Dormouse" to="[email protected]/rabbithole"/>

然后是房间历史。房间发送多少信息取决于配置。注意这些消息包括一个延迟发送时间 戳显示(UTC),它说明每个消息最初收到的时间,这样你可以知道历史信息多久以前被送 往(因此可以知道房间的繁忙程度):

    <message from="[email protected]/March Hare" 
             to="[email protected]/rabbithole"
             type="groupchat">
      <body>Have some wine</body>
      <delay xmlns="urn:XMPP:delay" stamp="2008-11-07T18:42:03Z"/>
    </message>


    <message from="[email protected]/Mad Hatter" to="[email protected]/rabbithole"
             type="groupchat">
      <body>Two days wrong!</body>
      <delay xmlns="urn:XMPP:delay" stamp="2008-11-07T18:42:17Z"/> 
    </message>

    <message from="[email protected]/Mad Hatter" to="[email protected]/rabbithole"
             type="groupchat">
      <body>March Hare: I told you butter wouldn’t suit the works!</body> 
      <delay xmlns="urn:XMPP:delay" stamp="2008-11-07T18:42:49Z"/>
    </message>

你一旦进入房间,就可以通过发送信息给房间本身来参加谈话。因为这些信息是“实时 的”,它们不包含延迟标志:

  <message from="[email protected]/rabbithole"
           to="[email protected]"
           type="groupchat">
    <body>March Hare: There’s PLENTY of room!</body>
  </message>

房间发送的消息,随后传递到所有的参与者,包括发送消息的一方。

<message from="[email protected]/Suke" to="[email protected]/underahat"
type="groupchat">
<body>March Hare: There’s PLENTY of room!</body>
</message>
<message from="[email protected]/Suke" to="[email protected]/sleepspace" type="groupchat">
<body>March Hare: There’s PLENTY of room!</body> </message>
<message from="[email protected]/Suke" to="[email protected]/chair"
type="groupchat">
<body>March Hare: There’s PLENTY of room!</body>
</message>
<message from="[email protected]/Suke" to="[email protected]/rabbithole"
type="groupchat">
<body>March Hare: There’s PLENTY of room!</body>
</message>

要发送悄悄话给 March Hare,Suke 将发出一个聊天类型的消息到 March Hare 的房间的 JID:

  <message from="[email protected]/rabbithole"
           to="[email protected]/March Hare" type="chat">
    <body>you silly Hare!</body>
  </message>

由于私人邮件被发送到March Hare的房间JID,它是由MUC服务器处理,它把消息从 Suke 转发到 March Hare。通过重写地址,使该消息似乎来自 Suke 的房间 JID;并重写地址以 便该消息被传递到 March Hare 的真正的 JID:

<message from="[email protected]/Suke" to="[email protected]/chair"
type="chat"> <body>you silly Hare!</body>
</message>

Suke 和 Hare 这样就可以开始一个完整的交谈,如果他们想要的话。(尽管如果他们想 邀请第三人他们的谈话,他们将需要创建另一个 MUC 的房间)。 如果你想离开房间时,你向你当前房间的 JID 发送一个不可用出席(或你的服务器在你 离线的时候根据你的行为发送):

<presence from="[email protected]/sleepspace" to="[email protected]/Dormouse" type="unavailable"/>

人群控制

完整的多用户聊天技术包含了各种人群控制,包括踢出并 禁止命令,限制谁可以在 MUC 的房间中有交谈的能力,以及如何完全限制访问。

例如,一个有 [email protected] JabberID 和“武士”的绰号的 人在 [email protected] 聊天室作祟,所以 king 更替武士的角色为访客:

  <iq from="[email protected]/throne" id="ks61f36g"
      to="[email protected]"
      type="set">
    <query xmlns="http://jabber.org/protocol/muc#admin">
    <item nick="The Knave" role="visitor"/> </query>
  </iq>

  <iq from="[email protected]" id="ks61f36g"
      to="[email protected]/throne"
      type="result"/>

MUC 的服务然后在房间里通知(包括受影响的乘客)每个人该冒犯者的身份已更改为 游客,它是通过从冒犯者发送包含新角色符号的更新的出席实现的。

  <presence from="[email protected]/The Knave"
            to="[email protected]/mobile"> 
    <x xmlns="http://jabber.org/protocol/muc#user">
    <item affiliation="none" role="visitor"/>
    </x>
  </presence>

冒犯者现在已经无法将消息发送到整个房间,如果他试图这样做,房间里将返回“禁止 发言”错误:

<message from="[email protected]/mobile" to="[email protected]"
type="groupchat"> <body>boo!</body>
</message>
<message from="[email protected]" to="[email protected]/mobile"
type="error"> <body>boo!</body> <error type="auth">
<forbidden xmlns="urn:ietf:params:xml:ns:XMPP-stanzas"/> </error>
</message>

一个较强的人群控制措施,是将冒犯者从房间中踢出。当你将一个人从房间踢出,他就 被临时从房间中删除,但他在这之后可以自由加入(有时这种功能是作为一个笑话,甚至开 玩笑地使用)。 在 MUC 中,通过将一个人在房间的角色改变为“无”来踢出他。

  <iq from="[email protected]/throne"
      id="u7r61fsv" to="[email protected]" type="set">
    <query xmlns="http://jabber.org/protocol/muc#admin">
      <item nick="The Knave" role="none"/>
  </query> </iq>

对角色的踢出和其他临时更改是根据用户在房间的昵称,而不是真正的 JID(主要是 因为房间的主持人不一定能看到房间讨论者的实际 JID)。

然后服务通过从他的房间 JID 到房间里每个人发送不可用的出席类型来移除这个人,并 通过具有特殊地位的代码,表明他的离开是不由自主的:

  <presence from="[email protected]/The Knave"
            to="[email protected]/mobile"
            type="unavailable">
    <x xmlns="http://jabber.org/protocol/muc#user">
      <item affiliation="none" role="none"/>
      <status code="307"/>
    </x>
  </presence>

一个 307 状态代码是指该用户已被暂时踢出了房间。状态码的完整列表在多用户聊天 规范中 供。

如前所述,被踢后,冒犯者可以返回到房间,导致更多麻烦。如果出现这种情况,管理 员可以采取更激烈的行动,并通过改变人的从属关系来完全禁止冒犯者的访问:

  <iq from="[email protected]/throne" id="d82csl87"
      to="[email protected]"
      type="set">
    <query xmlns="http://jabber.org/protocol/muc#admin">
      <item jid="[email protected]" affiliation="outcast"/>
    </query>
  </iq>

禁止访问和改变所属更改建立在真正的 JID 用户的基础上,而不是房间昵称。 该服务然后通过发送不可用出席类型到房间中的每个人来从房间删除这个人,附加一个 特别代码表明他是被禁止访问的。

  <presence from="[email protected]/The Knave"
            to="[email protected]/mobile"
            type="unavailable">
    <x xmlns="http://jabber.org/protocol/muc#user">
      <item jid="[email protected]" affiliation="outcast"/>
      <status code="301"/>
    </x>
  </presence>

一个 301 状态代码是指该用户已被永久禁止进入房间。状态码的完整列表在多用户聊 天规范中 供。 踢人和禁止访问使用 MUC 协议的管理功能之一:角色和从属关系。这是对 JabberID 和 JID 在房间里的权限的临时或者永久关联。例如,一个弃儿永久关联,表明用户是不允许加 入房间。

角色和从属关系

在 MUC 的规范定义了以下角色及从属关系:

  • 弃儿:不能加入房间的人。
  • 游客:可以加入聊天室,并听取交谈,但不能发言的人。
  • 参与者:可以同时听和说的人。
  • 成员: 可以听、说和加入房间的人(如果是会员制)。
  • 主持人:可以听、说、踢参与者和游客、以及切换他人发言权限的人。
  • 管理者: 可以听、说、踢和禁止参加者和游客,切换别人说话能力,看到居民的实际 JID,命名 新主持人和成员,并重新配置一些房间选项的人。
  • 业主: 可以听、说、踢和禁止参加者和游客,切换别人说话能力,看到居民的实际 JID,命名 的新主持人和成员,重新配置所有的房间选择,命名新管理员,并摧毁房间的人。

正如你可以看到,角色和背景以层次结构排列,从强大的房主到卑微的弃儿。这些房间 可以使管理员和业主不仅可以作为个体参与者实施它们的决定,而且可以配置一些空间范围 的选项,尤其是以下方面:

  • 任何人都可以发言,还是在房间内只有某些受限制的用户加入对话(即是审帖或主 持房间)?
  • 任何人都可以加入房间,或仅限于某些个人(例如是房间公开或仅限会员)?

这些类型的房间能够在踢人和禁止之外行使更加强大的权限,因为如果只有某些人可以 在房间里说,这就使得垃圾邮件发送者更难攻击,如果只有房间成员可以加入,就可以更强 烈地保护安全,防止滥用。

服务发现 XEP-0030


本文定义了一个XMPP协议扩展用于发现其他XMPP实体的信息.有两种信息可以被发现:

  • (1) 一个实体的身份和能力, 包括它支持的协议和特性;
  • (2) 和一个实体相关的条目, 例如一个多用户聊天服务的房间列表.

查询信息

<iq from='ejabberd16@localhost' 
    to='localhost' 
    type='get' 
    id='disco1'>
  <query xmlns='http://jabber.org/protocol/disco#info'/>
</iq>

然后目标实体 必须 返回一个IQ结果,或者返回一个错误

<iq xmlns='jabber:client' from='localhost' to='ejabberd16@localhost/zhuoyikangdeMacBook-Pro' id='disco1' type='result'>
	<query xmlns='http://jabber.org/protocol/disco#info'>
		<identity category='pubsub' type='pep'/>
		<identity category='server' type='im' name='ejabberd'/>
		<x xmlns='jabber:x:data' type='result'>
			<field var='FORM_TYPE' type='hidden'>
				<value>http://jabber.org/network/serverinfo</value>
			</field>
		</x>
		<feature var='http://jabber.org/protocol/commands'/>
		<feature var='http://jabber.org/protocol/disco#info'/>
    ...
		<feature var='iq'/>
		<feature var='jabber:iq:last'/>
		<feature var='jabber:iq:privacy'/>
		<feature var='jabber:iq:version'/>
		<feature var='msgoffline'/>
		<feature var='presence'/>
		<feature var='urn:xmpp:blocking'/>
		<feature var='urn:xmpp:carbons:1'/>
		<feature var='urn:xmpp:carbons:2'/>
		<feature var='urn:xmpp:ping'/>
		<feature var='urn:xmpp:time'/>
		<feature var='vcard-temp'/>
	</query>
</iq>

错误

<iq type='error'
    from='plays.shakespeare.lit'
    to='[email protected]/orchard'
    id='info1'>
  <query xmlns='http://jabber.org/protocol/disco#info'/>
  <error code='404' type='cancel'>
    <item-not-found xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
  </error>
</iq>

简单通信屏蔽:XEP-0191


列表操作

获取到用户的屏蔽列表

<iq type='get' id='blocklist1'>
  <blocklist xmlns='urn:xmpp:blocking'/>
</iq>

服务器回复列表

<iq type='result' id='blocklist1'>
  <blocklist xmlns='urn:xmpp:blocking'>
    <item jid='[email protected]'/>
    <item jid='[email protected]'/>
  </blocklist>
</iq>
    

服务器回复了一个空列表

<iq type='result' id='blocklist1'>
  <blocklist xmlns='urn:xmpp:blocking'/>
</iq>
    

实际屏蔽

屏蔽一个用户

<iq type='set' id='purple46f4f0e2'>
	<block xmlns='urn:xmpp:blocking'>
		<item jid='ejabberd11@localhost'/>
	</block>
</iq>
    

取消屏蔽

<iq type='set' id='unblock1'>
  <unblock xmlns='urn:xmpp:blocking'>
  	<item jid='ejabberd11@localhost'/>
  </unblock>
</iq>
    

TODO block/unblock push

多用户聊天:XEP-0045


出席信息广播

果服务能够添加用户到房间, 它必须(MUST)从所有现存的房客的房间JID发送出席信息给新的房客的全JID, 包括扩展的关于角色的出席信息, 一个满足 'http://jabber.org/protocol/muc#user' 名字空间的 元素并包含一个子元素, 这个子元素的'role'属性值设为"moderator", "participant", 或"visitor", 这个子元素的'affiliation'属性值设为"owner", "admin", "member", 或 "none" 中的一个:

服务从现有的房客发送出席信息给新的房客

<presence
    from='[email protected]/firstwitch'
    to='[email protected]/pda'>
  <x xmlns='http://jabber.org/protocol/muc#user'>
    <item affiliation='owner' role='moderator'/>
  </x>
</presence>
<presence
    from='[email protected]/secondwitch'
    to='[email protected]/pda'>
  <x xmlns='http://jabber.org/protocol/muc#user'>
    <item affiliation='admin' role='moderator'/>
  </x>
</presence>

什么是匿名/非匿名房间?

  • 匿名房间:用户标识只有昵称没有JID。
  • 非匿名房间:如果房间是非匿名的, 服务必须 MUST 发送新房客的全JID给所有房客,使用满足 'http://jabber.org/protocol/muc#user' 名字空间的扩展出席信息,其中带有 元素并包含一个 子元素,其 'jid' 属性值为这个房客的全JID:
  • 半匿名房间:如果房间是半匿名的, 服务必须MUST如上文所述从新房客发送出席信息给所有房客, 但是必须MUST只在发给"主持人"的时候发送新房客的全JID,而非主持人则不发(全JID).

有没有办法避免加入房间时发送出席信息

我有一个很大的群组,这个群组大概20W人,需要禁止掉出席信息,否则爆掉。

方法1 dark room patch

禁止掉所有的出席信息。

diff --git a/src/mod_muc/mod_muc_room.erl b/src/mod_muc/mod_muc_room.erl
index 164805d..5513064 100644
--- a/src/mod_muc/mod_muc_room.erl
+++ b/src/mod_muc/mod_muc_room.erl
@@ -2096,6 +2096,8 @@ send_new_presence(NJID, Reason, StateData) ->
     Affiliation = get_affiliation(LJID, StateData),
     SAffiliation = affiliation_to_list(Affiliation),
     SRole = role_to_list(Role),
+    Destinations = [{LJID, Info} || {LJID, Info} <-
+	?DICT:to_list(StateData#state.users), (NJID == Info#user.jid)],
     lists:foreach(
       fun({_LJID, Info}) ->
 	      ItemAttrs =
@@ -2145,7 +2147,7 @@ send_new_presence(NJID, Reason, StateData) ->
 		jlib:jid_replace_resource(StateData#state.jid, Nick),
 		Info#user.jid,
 		Packet)
-      end, ?DICT:to_list(StateData#state.users)).
+      end, Destinations).
 
 
 send_existing_presences(ToJID, StateData) ->
@@ -2189,7 +2191,7 @@ send_existing_presences(ToJID, StateData) ->
 			RealToJID,
 			Packet)
 	      end
-      end, ?DICT:to_list(StateData#state.nicks)).
+      end, []).
 
 
now_to_usec({MSec, Sec, USec}) ->

可以给ejabberd/mod_muc_room.erl增加一个自定义参数来禁止掉presence消息。

新建房间

Jabber用户新建一个房间并声明对多用户聊天的支持

<presence
    from='[email protected]/desktop'
    to='[email protected]/firstwitch'>
  <x xmlns='http://jabber.org/protocol/muc'/>
</presence>

<presence
    from=ejabberd16@localhost/desktop'
    to='darkcave@localhost/firstwitch'>
  <x xmlns='http://jabber.org/protocol/muc'/>
</presence>