轉 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. }
相關文章
相關標籤/搜索