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来管理。(若是你想把全部的相关库都用手动集成,那须要修改的地方太多了,不推荐)

感谢阅读。

相关文章
相关标签/搜索