XMPPFramework使用记录(一)

前言

最近公司需要我们使用XMPP协议,实现一个简单的IM模块。在此之前没有接触过IM相关技术,仅了解iOS可以通过集成XMPPFramework来快速的实现某些需求。本系列文章旨在记录使用XMPPFramework过程中遇到的问题。

正文

首先先聊一下XMPP实现IM,在查过一些资料后,我粗略的认为其实整体来说IM就是一个长连接(暂时抛开优化等深层次的东西),XMPP协议则是约束了用户端和服务端信息交互的规则。当然随着对IM和XMPP等的深入研究,我可能会慢慢改变自己的看法,至少目前来是这样子认识的。

回到正题,我们都知道XMPP是开源的,iOS端使用XMPPFramework,服务端一般都采用Openfire,而大多公司会在此基础上进行二次开发,我们公司就是这样。

关于用户登录的细节网上代码很多,我简单的贴一点。当我们使用我们的JID连接到服务器的时候,我们下一步需要验证密码。在默认的情况下,当我们发送了验证密码的请求,下一次服务端返回的报文是关于验证结果状态的。大家简单看一眼下面的代码:

// 拼接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 ?? "")
}

如果服务端没有改动,那么我们在验证之后收到的报文应该是下面这种:

// 验证成功
<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>标签的报文,包裹着用户资料。

我们通过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,不是的话,不管是什么都返回XMPPHandleAuthResponseSuccess

因为我们会提前收到一条用户信息的报文,所以我们需要在处理此条报文的时候不要处理为失败。唯一的方法就是要对源码做一些改动了。下面是改动后的代码。

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

感谢阅读。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,277评论 6 503
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,689评论 3 393
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,624评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,356评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,402评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,292评论 1 301
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,135评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,992评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,429评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,636评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,785评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,492评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,092评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,723评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,858评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,891评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,713评论 2 354