转 XMPP资源绑定(Resource Binding)

XMPP资源绑定(Resource Binding)

一个XMPP的帐号由三部分组成: 用户名(user/node),域名(domain)和资源(resource) 。例如 alice@xmpp.irusher.com/mobile ,user部分(或node)是alice,domain是xmpp.irusher.com,resource部分是mobile。user和domain组合也叫Bare JID,例如:alice@xmpp.i8i8i8.com ,Bare JID经常使用标识一个用户。包含了user,domain和resource的ID也叫Full JID,在Full JID中,resource通常用来区分一个用户的多个会话,能够由服务端或客户端指定。下面介绍一下resource的绑定过程。node

客户端经过服务端的验证以后,应该给XMPP流绑定一个特殊的资源以使服务端可以正确的定位到客户端。客户端Bare JID后必须附带resource,与服务端交互时使用Full JID,这样就确保服务端和客户端传输XML段时服务端可以正确的找到对应的客户端。web

当一个客户端经过一个资源绑定到XML流上后,它就被称之为"已链接的资源"。服务器应该容许同时处理多个”已链接的资源“,每一个”已链接的资源“由不一样的XML流合不一样的resource来区分。服务器

资源绑定用到的XML命名空间为 "urn:ietf:params:xml:ns:xmpp-bind" .session

服务端在SASL协议成功,发送了响应的stream头以后,必需紧接着发送一个由'urn:ietf:params:xml:ns:xmpp-bind'标识的<bind/>元素。dom

  1. S:<stream:stream
  2. from='im.example.com'
  3. id='gPybzaOzBmaADgxKXu9UClbprp0='
  4. to='juliet@im.example.com'
  5. version='1.0'
  6. xml:lang='en'
  7. xmlns='jabber:client'
  8. xmlns:stream='http://etherx.jabber.org/streams'>
  9. S:<stream:features>
  10. <bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'/>
  11. </stream:features>

一个资源标识至少在同一个Bare JID的全部resource标识中是惟一的,这一点须要由服务端来负责。this

2.1 服务端生成resource标志

客户端经过发送一个包含空的<bind/>元素,类型为的setIQ来请求服务端生成resource标志。spa

  1. C:<iq id='tn281v37' type='set'>
  2. <bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'/>
  3. </iq>

服务端生成后发送响应给客户端:code

  1. S:<iq id='tn281v37' type='result'>
  2. <bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'>
  3. <jid>
  4. juliet@im.example.com/4db06f06-1ea4-11dc-aca3-000bcd821bfb
  5. </jid>
  6. </bind>
  7. </iq>

失败状况:orm

  1. 一个Bare JID下已经达到了同时在线的上限;
  2. 客户端不被容许绑定资源

2.2 客户端设置resource标志

客户端也能够本身设置resource。

客户端经过发送一个包含<bind/>元素,类型为的setIQ来请求服务端生成resource标志。<bind/>元素包含一个子元素<resource/><resource/>元素包含长度非零的字符串。

  1. C:<iq id='wy2xa82b4' type='set'>
  2. <bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'>
  3. <resource>balcony</resource>
  4. </bind>
  5. </iq>

服务端应该接受客户端提交的resource标志。服务端经过IQ返回一个<bind/>元素,其中包含了一个<jid>元素,<jid>元素中包含一个Full JID,其中的resource是客户端提交的resource标志。

  1. S:<iq id='wy2xa82b4' type='result'>
  2. <bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'>
  3. <jid>juliet@im.example.com/balcony</jid>
  4. </bind>
  5. </iq>

服务端有可能会拒绝客户端提供的resource标志,而使用服务端生成的resource标志。

失败状况:

  1. 提交的标志包含非法字符,地址格式能够在这里查到: Address Format
  2. 提交的标志已经被占用

2.2.1 resource标志冲突

当客户端提供的resource标志冲突时,服务端应该遵循如下三个策略之一:

  1. 从新生成新链接提交的resource标志,使新链接可以继续;
  2. 拒绝新的链接,并维持现有的链接;
  3. 断开现有的链接,并尝试绑定新的链接;

若是是第一种状况,服务端返回从新生成的resource标志:

  1. S:<iq id='wy2xa82b4' type='result'>
  2. <bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'>
  3. <jid>
  4. juliet@im.example.com/balcony 4db06f06-1ea4-11dc-aca3-000bcd821bfb
  5. </jid>
  6. </bind>
  7. </iq>

若是是第二种状况,服务端向新链接返回一个<conflict/>流错误:

  1. S:<iq id='wy2xa82b4' type='error'>
  2. <error type='modify'>
  3. <conflict xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
  4. </error>
  5. </iq>

若是是第三种状况,服务端向已链接的客户端发送<conflict/>流错误,关闭已链接的客户端的流,而后向新的链接发送绑定的结果:

  1. S:<iq id='wy2xa82b4' type='result'>
  2. <bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'>
  3. <jid>
  4. juliet@im.example.com/balcony
  5. </jid>
  6. </bind>
  7. </iq>

相似QQ,不一样设备上最多只能有一个帐户在线,例如一个帐号不能在两个iPhone上同时在线,在第二台上登陆,就要把第一台踢下线,可是,又容许桌面或web上登陆相同的帐号。像这样的需求,能够经过resource标志来实现。

假定策略以下:一个帐号最多只能在相同系统的设备上有一处登陆,例如,用户在一台iPhone上登陆,若是又在另一台设备上登陆,那就把第一台踢下线,可是能够容许有一个Android设备登陆相同的帐号。

实现: 在iOS版本中,登陆时,客户端提交自定义的resource标志: iOS,一样,Android版本中,提交自定义的resource标志: Android。这样就能够限制相同系统只能有一处登陆了。

假如要求一个帐号只能在一个移动设备上登陆,实现的时候,则须要iOS和Android使用相同的resource标志,例如: Mobile.

须要特别说明的是,当旧的链接被踢下线后,服务端向客户端发送<conflict/>流错误,并关闭流。客户端须要正确的处理这种状况下的应用逻辑。

在管理器的的  服务器>服务器管理器>系统属性 中设置属性xmpp.session.conflict-limit的值。

Openfire相关的源码以下,能够根据须要配置对应的属性值:

  1. String username = authToken.getUsername().toLowerCase();
  2. // If a session already exists with the requested JID, then check to see
  3. // if we should kick it off or refuse the new connection
  4. ClientSession oldSession = routingTable.getClientRoute(new JID(username, serverName, resource,true));
  5. if(oldSession !=null){
  6. try{
  7. int conflictLimit = sessionManager.getConflictKickLimit();
  8. if(conflictLimit ==SessionManager.NEVER_KICK){
  9. reply.setChildElement(packet.getChildElement().createCopy());
  10. reply.setError(PacketError.Condition.conflict);
  11. // Send the error directly since a route does not exist at this point.
  12. session.process(reply);
  13. returnnull;
  14. }
  15. int conflictCount = oldSession.incrementConflictCount();
  16. if(conflictCount > conflictLimit){
  17. // Kick out the old connection that is conflicting with the new one
  18. StreamError error =newStreamError(StreamError.Condition.conflict);
  19. oldSession.deliverRawText(error.toXML());
  20. oldSession.close();
  21. }
  22. else{
  23. reply.setChildElement(packet.getChildElement().createCopy());
  24. reply.setError(PacketError.Condition.conflict);
  25. // Send the error directly since a route does not exist at this point.
  26. session.process(reply);
  27. returnnull;
  28. }
  29. }
  30. catch(Exception e){
  31. Log.error("Error during login", e);
  32. }
  33. }
  1. xmpp.session.conflict-limit == -1 :向新链接发送资源绑定冲突的流错误;
  2. xmpp.session.conflict-limit <= 1 && != -1 : 向旧链接发送资源绑定冲突的流错误,而且关闭旧的链接(会话);
  3. xmpp.session.conflict-limit > 1: 向新链接发送资源绑定冲突的流错误;

ps: 修改完须要重启openfire。

自定义resource

设置XMPPStream类的实例变量myJID,附带自定义的resource便可。

  1. XMPPJID *myJID =[XMPPJID jidWithString:[NSString stringWithFormat:@"%@@%@/%@",user,XMPP_SERVER_ACCOUNT_HOSTNAME,@"xmpp"]];
  2. [self.xmppStream setMyJID:myJID];

处理冲突

XMPPStreamDelegate的回调方法中处理资源绑定冲突产生的流错误:

  1. -(void)xmppStream:(XMPPStream*)sender didReceiveError:(id)error
  2. {
  3. NSXMLElement*element =(NSXMLElement*) error;
  4. NSString*elementName =[element name];
  5. //<stream:error xmlns:stream="http://etherx.jabber.org/streams">
  6. // <conflict xmlns="urn:ietf:params:xml:ns:xmpp-streams"/>
  7. //</stream:error>
  8. if([elementName isEqualToString:@"stream:error"]||[elementName isEqualToString:@"error"])
  9. {
  10. NSXMLElement*conflict =[element elementForName:@"conflict" xmlns:@"urn:ietf:params:xml:ns:xmpp-streams"];
  11. if(conflict)
  12. {
  13. }
  14. }
  15. }
相关文章
相关标签/搜索