XMPPFramework使用記錄(一)

前言

最近公司須要咱們使用XMPP協議,實現一個簡單的IM模塊。在此以前沒有接觸過IM相關技術,僅瞭解iOS能夠經過集成XMPPFramework來快速的實現某些需求。本系列文章旨在記錄使用XMPPFramework過程當中遇到的問題。swift

正文

首先先聊一下XMPP實現IM,在查過一些資料後,我粗略的認爲其實總體來講IM就是一個長鏈接(暫時拋開優化等深層次的東西),XMPP協議則是約束了用戶端和服務端信息交互的規則。固然隨着對IM和XMPP等的深刻研究,我可能會慢慢改變本身的見解,至少目前來是這樣子認識的。服務器

回到正題,咱們都知道XMPP是開源的,iOS端使用XMPPFramework,服務端通常都採用Openfire,而大多公司會在此基礎上進行二次開發,咱們公司就是這樣。dom

關於用戶登陸的細節網上代碼不少,我簡單的貼一點。當咱們使用咱們的JID鏈接到服務器的時候,咱們下一步須要驗證密碼。在默認的狀況下,當咱們發送了驗證密碼的請求,下一次服務端返回的報文是關於驗證結果狀態的。你們簡單看一眼下面的代碼:async

// 拼接JID而後鏈接
xmppStream.myJID = XMPPJID(user: UserInfo.sysAccount, domain: domain, resource: "iOS")
try? xmppStream.connect(withTimeout: timeOut)

而後在鏈接成功的回調方法中驗證密碼:優化

func xmppStreamDidConnect(_ sender: XMPPStream) {
    try? xmppStream.authenticate(withPassword: self.password ?? "")
}

若是服務端沒有改動,那麼咱們在驗證以後收到的報文應該是下面這種:ui

// 驗證成功
<success xmlns="urn:ietf:params:xml:ns:xmpp-sasl"/>
// 驗證失敗
<failure xmlns="urn:ietf:params:xml:ns:xmpp-sasl"><not-authorized/><code>xxxxx</code><msg>xxxxx</msg></failure>

在確保帳號和密碼都是正確的狀況下,我一直收到驗證失敗的回調。由於咱們的服務端對驗證過程做了一些改動,在驗證密碼成功以後,表明用戶登陸成功,這時會在驗證結果狀態返回以前,插入返回一條<iq>標籤的報文,包裹着用戶資料。spa

咱們經過debug源碼,看了一下整個驗證密碼的過程。debug

具體的老是提示驗證失敗的緣由要定位到XMPPPlainAuthentication.m文件的- (XMPPHandleAuthResponse)handleAuth:(NSXMLElement *)authResponse方法中。代理

- (XMPPHandleAuthResponse)handleAuth:(NSXMLElement *)authResponse
{
    XMPPLogTrace();
    if ([[authResponse name] isEqualToString:@"success"])
    {
        return XMPPHandleAuthResponseSuccess;
    }
    else
    {
        return XMPPHandleAuthResponseFailed;
    }
}

這是XMPPFramework裏面源碼,在處理驗證的時候,直接判斷這個標籤是否是success,是就返回XMPPHandleAuthResponseSuccess,不是的話,不論是什麼都返回XMPPHandleAuthResponseSuccesscode

由於咱們會提早收到一條用戶信息的報文,因此咱們須要在處理此條報文的時候不要處理爲失敗。惟一的方法就是要對源碼作一些改動了。下面是改動後的代碼。

- (XMPPHandleAuthResponse)handleAuth:(NSXMLElement *)authResponse
{
    XMPPLogTrace();
    
    // We're expecting a success response.
    // If we get anything else we can safely assume it's the equivalent of a failure response.
    
#warning sunxb add
    // 添加iq判斷 解決驗證時候多條報文的問題
    if ([[authResponse name] isEqualToString:@"success"])
    {
        return XMPPHandleAuthResponseSuccess;
    }
    // add 
    else if ([XMPPAuthUtils judgeUserInfoWith:authResponse]) {
        return XMPPHandleAuthResponseContinue;
    }
    else
    {
        return XMPPHandleAuthResponseFailed;
    }
}

咱們增長了一個條件判斷,else if 中的judgeUserInfoWith是咱們本身的業務邏輯判斷,咱們的用戶信息是經過<iq>標籤返回的,爲了區別與其餘的報文,咱們給他加上了不同的命名空間(xmlns)(不一樣公司業務不一樣判斷條件也不同,總之咱們的判斷邏輯是此條報文是<iq>,同時攜帶用戶信息,才返回true)。

最終處理目的就是若是收到了用戶消息的報文,不要返回XMPPHandleAuthResponseFailed,先返回XMPPHandleAuthResponseContinue。(其餘的亂七八糟的報文跟以前同樣當作fail處理)

XMPPHandleAuthResponseContinue這個類型有什麼不同呢?咱們還得看源碼。

咱們定位到XMPPStream.m中的- (void)handleAuth:(NSXMLElement *)authResponse方法,你們不要混了,都是叫handleAuth,下面這個方法中調用了咱們上面提到的方法,並且上面的那個方法是有返回值的。你們不用太仔細的看代碼,只要知道這個方法,是根據上面的handleAuth方法的返回值,分別作了處理。

爲了讓代碼緊湊一些,我刪除了一些註釋。

- (void)handleAuth:(NSXMLElement *)authResponse
{
    NSAssert(dispatch_get_specific(xmppQueueTag), @"Invoked on incorrect queue");
    
    XMPPLogTrace();
    
    XMPPHandleAuthResponse result = [auth handleAuth:authResponse];
    
    if (result == XMPPHandleAuthResponseSuccess)
    {
        [self setIsAuthenticated:YES];
        
        BOOL shouldRenegotiate = YES;
        if ([auth respondsToSelector:@selector(shouldResendOpeningNegotiationAfterSuccessfulAuthentication)])
        {
            shouldRenegotiate = [auth shouldResendOpeningNegotiationAfterSuccessfulAuthentication];
        }
        
        if (shouldRenegotiate)
        {
            [self sendOpeningNegotiation];
            
            if (![self isSecure])
            {
                
                [asyncSocket readDataWithTimeout:TIMEOUT_XMPP_READ_START tag:TAG_XMPP_READ_START];
            }
        }
        else
        {
            state = STATE_XMPP_CONNECTED;
            
            [multicastDelegate xmppStreamDidAuthenticate:self];
        }
        auth = nil;
        
    }
    else if (result == XMPPHandleAuthResponseFailed)
    {
        state = STATE_XMPP_CONNECTED;
        
        // Notify delegate
        [multicastDelegate xmppStream:self didNotAuthenticate:authResponse];
        
        // Done with auth
        auth = nil;
        
    }
    else if (result == XMPPHandleAuthResponseContinue)
    {
        // Authentication continues.
        // State doesn't change.
    }
    else
    {
        XMPPLogError(@"Authentication class (%@) returned invalid response code (%i)",
                   NSStringFromClass([auth class]), (int)result);
        
        NSAssert(NO, @"Authentication class (%@) returned invalid response code (%i)",
                     NSStringFromClass([auth class]), (int)result);
    }
}

從代碼中能夠看到,若是失敗會直接調用驗證失敗的回調,可是若是結果是XMPPHandleAuthResponseContinue,沒有作任何處理。

因此咱們須要在驗證過程當中對收到的用戶信息報文,返回continue狀態。由於用戶信息屬於有用的報文(須要儲存),因此這個地方咱們也要作一些修改,把咱們的用戶信息傳出去。

else if (result == XMPPHandleAuthResponseContinue)
    {
#warning sunxb add: when auth response is continue, just send authResponse to delegate
        [multicastDelegate xmppStream:self didReceiveCustomElement:authResponse];
    }

注: 全部修改的源碼我都會加一個#warning,方法之後定位本身修改過得源碼。

作完上面的處理以後,咱們的代理方法回調就正常了。

func xmppStreamDidAuthenticate(_ sender: XMPPStream) {
    // 此處收到驗證成功回調
}
    
func xmppStream(_ sender: XMPPStream, didReceiveCustomElement element: DDXMLElement) {
    // 用戶消息的報文走這個回調,咱們須要再判斷一下是不是用戶消息報文
    // 是不是用戶消息
    if XMPPAuthUtils.judgeUserInfo(with: element) {
        JMClientUtils.storageUserInfo(with: element)
    }
}

總結

本文是使用了XMPPFramework後遇到的第一個小問題,特此記錄。隨着XMPPFramework的使用可能會遇到其餘的問題,到時候會繼續記錄分享。

由於要對源碼作修改,XMPPFramework我採用了手動集成的方式,但XMPPFramework的依賴庫依然是使用了Cocoapods來管理。(若是你想把全部的相關庫都用手動集成,那須要修改的地方太多了,不推薦)

感謝閱讀。

相關文章
相關標籤/搜索