iOS—XMPP快速登录

上一篇文章中,当XMPP需要建立起连接的时候,总共会发送 6 条数据包给Openfire服务器。先来回顾一下:

17:22:41.357  -[XMPPManager xmppStreamWillConnect:] socket正在连接...

17:22:42.100  -[XMPPManager xmppStream:socketDidConnect:] socket连接成功...

17:22:42.102  ①、SEND: 

17:22:42.103  ②、SEND: 

17:22:43.085  RECV: PLAIN

17:22:43.086  -[XMPPManager xmppStreamDidConnect:] [Line 198] xmpp连接成功, 正在授权......

17:22:43.087  ③、SEND:  APINT4ANBTYg6IJ 
 APINT4ANBTYg6IJ iOS_V1.0

17:22:43.534  RECV: 

17:22:43.534  ④、SEND: 

17:22:43.870  RECV: 

17:22:43.871  ⑤、SEND: iOS_V1.0

17:22:44.261  RECV: [email protected]/iOS_V1.0

17:22:44.262  ⑥、SEND: 

17:22:44.669  RECV: 

17:22:44.670  -[XMPPManager xmppStreamDidAuthenticate:] [Line 208] xmpp授权成功。

看这些数据包也发现不了什么问题,但是当产品在线上运行了很久,加上用户量达到一定的规模之后,通过数据监控发现,XMPP连接成功率并不高。于是技术部几个同事开会讨论如何把这个指标提高。经过前后端联调发现,建立连接时客户端并不需要发送那么多条数据包,有几个操作没必要在客户端做,在服务器上操作会更方便,还省了些带宽和降低服务器压力,客户端每发一条数据都会给服务器增加一些压力。

于是一个新名词就出现了,我们把这次优化的登录叫做快速登录。主要的做法是:

  • 把①去掉,这个数据包貌似只是为了探测网络的,并没有什么用
  • ②中的stream:stream包是必须的,为了区别旧版本,多加一个字段v=‘1.0’
  • ③中的auth非常重要,是用于鉴权的。这里做一些修改,把resource子节点作为auth节点的属性,并且为了兼容旧版本,这里的资源需要和旧版不一样,服务器端根据这个来判断新旧版本,把resource的值改为iOS_FL_V1.0
  • 把④去掉
  • ⑤和⑥这个步骤都只需要在服务器上做操作

修改之后,整体登录的收发数据包看起来像这样:

11:52:24:022 SEND: 

11:52:24:030 RECV: 

11:52:24:031 RECV: PLAIN

11:52:24:044 SEND: APINT4ANBTYg6IJ

11:52:24:054 RECV: 

客户端只发送了2条数据包即可实现快速登录,是不是简洁了很多。如果客户端需要发送很多数据包才能登录,这期间也许因为网络的原因导致某条数据包没发出去,或者服务器返回的某条数据包无法收到,都会导致无法登录。而快速登录就是为了减少不必要的数据包,增加登录的成功率。

下面讲一下如何修改源码来达到快速登录。当然还是最重要的那个类XMPPStream,根据上面的做法一步步来:

  • 找到- (void)sendOpeningNegotiation;这个方法的实现,把

    if (![self didStartNegotiation])
    

    {
    // TCP connection was just opened - We need to include the opening XML stanza
    NSString *s1 = @"";

      NSData *outgoingData = [s1 dataUsingEncoding:NSUTF8StringEncoding];
      
      XMPPLogSend(@"SEND: %@", s1);
      numberOfBytesSent += [outgoingData length];
      
      [asyncSocket writeData:outgoingData
                 withTimeout:TIMEOUT_XMPP_WRITE
                         tag:TAG_XMPP_WRITE_START];
      
      [self setDidStartNegotiation:YES];
    }
    

这段代码注释

  • 同样- (void)sendOpeningNegotiation;这个方法,
    if (myJID_setByClient)
    {
    // 修改这里
    s2 = [NSString stringWithFormat:"", xmlns, xmlns_stream, [myJID_setByClient domain]];
    }
    else if ([hostName length] > 0)
    {
    temp = @"";
    s2 = [NSString stringWithFormat:temp, xmlns, xmlns_stream, hostName];
    }
    else
    {
    temp = @"";
    s2 = [NSString stringWithFormat:temp, xmlns, xmlns_stream];
    }

  • 修改auth包的内容稍微复杂一些,当Socket连接上了之后,会在- (void)xmppStreamDidConnect:(XMPPStream *)sender;这个代理回调里面调用- (BOOL)authenticateWithPassword:(NSString *)inPassword error:(NSError **)errPtr;进行鉴权登录。跟着这个方法进去,我们app中登录会走else if ([self supportsPlainAuthentication]),进入XMPPPlainAuthentication这个类,需要修改auth包的内容,找到- (BOOL)start:(NSError **)errPtr;这个方法,修改如下:
    ......
    NSXMLElement *auth = [NSXMLElement elementWithName:@"auth" xmlns:@"urn:ietf:params:xml:ns:xmpp-sasl"];
    [auth addAttributeWithName:@"mechanism" stringValue:@"PLAIN"];
    [auth addAttributeWithName:@"res" stringValue:@"iOS_FL_V1.0"];
    [auth setStringValue:base64];
    ......

  • 当客户端发送了auth包之后,如果成功了,服务器端则会返回success包,在XMPPStream类的- (void)handleAuth:(NSXMLElement *)authResponse;中处理。这个方法的实现有些怪异,明明鉴权成功了,为啥还要发stream包,经过与后端联调发现没有必要再发了,于是做了下判断,把shouldRenegotiate这个变量写死为NO即可,这样就不会发了

通过快速登录,客户端与服务器之间的数据包传送数量少了,经过我们线上几百万用户的使用情况上看,效果还是很明显的,没有出现什么问题,大大的提升了登录成功率。

XMPP连接登录,一直以来坑比较的多,只有在用户量达到一定规模才会显现出来,曾面试过一些人,他们也都有了解XMPP,做过相关的项目,然而问其一些关于连接的问题时,基本上说没有遇到什么问题。这其实不是他们的问题。作为一款IM产品,如果不能正常的聊天,如何谈得上是IM呢。

吃午饭了…

你可能感兴趣的:(iOS—XMPP快速登录)