iOS XMPP搭建IM

XMPP框架地址: https://github.com/robbiehanson/XMPPFramework   
eJabberd服务器:https://www.process-one.net/en/ejabberd/downloads 
Spark: http://www.igniterealtime.org/downloads/download-landing.jsp?file=spark/spark_2_7_7.dmg

  • 一、安装mySql
  • https://segmentfault.com/a/1190000007838188
  • https://www.jianshu.com/p/fd3aae701db9
  • https://www.cnblogs.com/yajunLi/p/6251808.html
  • http://blog.csdn.net/qq55214/article/details/50396656


启动mysql服务

1、如果你已经安装了MySQLStartupItem.pkg,重新启动电脑即可。

2、如果你有安装MySQLStartupItem.pkg或者不想启动电脑,运行:应用程序-实用工具-终端,在终端中输入命令:sudo /Library/StartupItems/MySQLCOM/MySQLCOM start,然后输入你的系统管理员密码即可。


关闭mysql服务

终端中输入命令:sudo /Library/StartupItems/MySQLCOM/MySQLCOM stop,然后输入你的系统管理员密码即可。


更改mysql root账户密码

终端中输入命令:/usr/local/mysql/bin/mysqladmin -u root password 新密码


终端登录mysql

方法1:绝对路径

终端中输入命令:/usr/local/mysql/bin/mysql -u root -p

提示:输入你的新密码


方法2:(推荐)相对路径

终端中输入命令:

查看路径中有没有需要的路径:

终端中输入命令:echo $PATH

没有,继续

添加需要路径:PATH="$PATH":/usr/local/mysql/bin

  • 在终端登录到MySQL的命令如下:mysql -u root -p

    然后输入密码,如果没有设置密码,直接按enter键。


  • 二、搭建Openfire服务器
      • 也可以搭建eJabbered服务器
      • https://www.jianshu.com/p/d47a2fa85009
      • https://www.jianshu.com/p/7c80a61c8de6

      • 三、客户端搭建

  • 一个XMPP客户端必须支持的功能有:   1. 通过TCP套接字与XMPP服务器进行通信;   2. 解析组织好的XML信息包;   3. 理解消息数据类型。 所以,只要客户端满足上述三个功能,就可以实现基于XMPP的即时通信。   安装完spark后,通过账户创建一个新的账户进行登入,可以从服务器(用户/组)看到,新创建的账户处于在线状态,而admin账户由于没有登入,所以就处于离线状态。

  • 我们还可以在XMPP官网下载Adium等客户端软件进行调试

  •  
       
    其实Mac自带的iMessage也能调试,只要客户端满足之前所述XMPP客户端所必须支持的三个功能,那么任何两个客户端都能够实现即时通信;接下来我们就实现下iMessage和spark之间进行即时通信;   打开iMessage->添加账户->其他“信息”账户->继续;在账户类型里选择Jabber(注:XMPP前身),用户名填写格式为:用户名@服务器名称,服务器名称可以在服务器的属性查看,填入密码,勾选“自动查找服务器和端口”,点击登入;登入之后我们可以添加一个在spark中登入的账户,添加之后就可以进行通话了,效果如下图所示

  • 基于XMPP的8大特性(开放、标准、证实可用、分散、安全、可扩展、弹性佳、多样性),被开发出基于XMPP的软件越来越多;XMPP客户端需要支持的功能:
  • 1).通过TCP套接字与XMPP服务器进行通信;
  • 2).解析组织好的XML信息包;
  • 3).理解消息数据类型。

  • 四、xmpp使用

  • 1.新建项目,使用cocoapods 导入XMPP架。安卓平台可以使用Smack.

  • 包含:
  • CocoaLumberjack。这是在整个项目中使用的日志框架。
  • CocoaAsyncSocket依赖性。这是底层的网络代码所使用的框架。
  • 需要添加引用库 1、CFNetwork framework 
  •                        2、Security framework

  • KissXML在Mac OS X上,苹果提供了处理XML元素的NSXML类集群(NSXMLDocument,NSXMLElement NSXMLNode)。然而,他们不提供这些类在iOS。KissXML旨在替代苹果的iOS集群在NSXML类
2.初始化xmpp流

#define JBXMPP_HOST @"lujiangbin.local"
#define JBXMPP_PORT 5222
- (void)setupStream
{
    if (!_xmppStream) {
        _xmppStream = [[XMPPStream alloc] init];
       
        [self.xmppStream setHostName:JBXMPP_HOST]; //设置xmpp服务器地址
        [self.xmppStream setHostPort:JBXMPP_PORT]; //设置xmpp端口,默认5222
        [self.xmppStream addDelegate:self delegateQueue:dispatch_get_main_queue()];
        [self.xmppStream setKeepAliveInterval:30]; //心跳包时间

        //允许xmpp在后台运行
        self.xmppStream.enableBackgroundingOnSocket=YES;
        
        //接入断线重连模块
        _xmppReconnect = [[XMPPReconnect alloc] init];
        [_xmppReconnect setAutoReconnect:YES];
        [_xmppReconnect activate:self.xmppStream];
        
        //接入流管理模块,用于流恢复跟消息确认,在移动端很重要
        _storage = [XMPPStreamManagementMemoryStorage new];
        _xmppStreamManagement = [[XMPPStreamManagement alloc] initWithStorage:_storage];
        _xmppStreamManagement.autoResume = YES;
        [_xmppStreamManagement addDelegate:self delegateQueue:dispatch_get_main_queue()];
        [_xmppStreamManagement activate:self.xmppStream];
        
        //接入好友模块,可以获取好友列表
        _xmppRosterMemoryStorage = [[XMPPRosterMemoryStorage alloc] init];
        _xmppRoster = [[XMPPRoster alloc] initWithRosterStorage:_xmppRosterMemoryStorage];
        [_xmppRoster activate:self.xmppStream];
        [_xmppRoster addDelegate:self delegateQueue:dispatch_get_main_queue()];
        
        //接入消息模块,将消息存储到本地
        _xmppMessageArchivingCoreDataStorage = [XMPPMessageArchivingCoreDataStorage sharedInstance];
        _xmppMessageArchiving = [[XMPPMessageArchiving alloc] initWithMessageArchivingStorage:_xmppMessageArchivingCoreDataStorage dispatchQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 9)];
        [_xmppMessageArchiving activate:self.xmppStream];
    }
}
登陆
登陆过程包括初始化流、TLS握手和SASL验证等,想要了解各个阶段服务端跟客户端之间交互的内容查看。XMPPFramework将整个复杂的登陆过程都封装起来了,客户端调用connectWithTimeout:(NSTimeInterval)timeout error:(NSError **)errPtr连接服务器,然后在xmppStreamDidConnect代理方法输入密码验证登陆,这里我们使用在搭建服务器时创建的两个用户,user1和user2。
 
  
#define JBXMPP_DOMAIN @"lujiangbin.local" -( void)loginWithName:( NSString *)userName andPassword:( NSString *)password { _myJID = [XMPPJID jidWithUser:userName domain:JBXMPP_DOMAIN resource: @"iOS"]; self.myPassword = password; [ self.xmppStream setMyJID:_myJID]; NSError *error = nil; [_xmppStream connectWithTimeout:XMPPStreamTimeoutNone error:&error]; } #pragma mark -- connect delegate //输入密码验证登陆 - ( void)xmppStreamDidConnect:(XMPPStream *)sender { NSError *error = nil; [[ self xmppStream] authenticateWithPassword:_myPassword error:&error]; } //登陆成功 - ( void)xmppStreamDidAuthenticate:(XMPPStream *)sender { NSLog( @"%s",__func__); //发送在线通知给服务器,服务器才会将离线消息推送过来 XMPPPresence *presence = [XMPPPresence presence]; // 默认"available" [[ self xmppStream] sendElement:presence]; //启用流管理 [_xmppStreamManagement enableStreamManagementWithResumption: YES maxTimeout: 0]; } //登陆失败 - ( void)xmppStream:(XMPPStream *)sender didNotAuthenticate:( NSXMLElement *)error { NSLog( @"%s",__func__); }
获取好友列表
通过XMPPRoster去获取好友列表,使用 XMPPRosterMemoryStorage将好友存储在内存中,在实际场景你可以将好友存储在XMPPRosterCoreDataStorage,xmppframework使用coredata将好友保存到本地,可以在初始化xmpp流的时候设置。为了获取好友列表,只需调用fetchRoster方法:
//获取服务器好友列表
    [[[JBXMPPManager sharedInstance] xmppRoster] fetchRoster];
消息 
消息发送 只需要调用xmpp的sendElement:方法,由于xmpp只支持文本,所以假如你想发送二进制的文件,比如语音图片等,可以先压缩然后用base64编码,接收方收到再做解码工作,比如语音可以压缩成amr格式,amr格式安卓可以直接播放,iOS需要在解压成wav格式,可以参考demo
 
  
- (void)sendMessage:(NSString *)message to:(XMPPJID *)jid { XMPPMessage* newMessage = [[XMPPMessage alloc] initWithType:@"chat" to:jid]; [newMessage addBody:message]; //消息内容 [_xmppStream sendElement:newMessage]; }
消息接收
当收到消息的时候,xmppframework会调用didReceiveMessage:代理方法,由于我们在初始化流的时候将消息设置存储到本地,可以看到XMPPMessageArchiving在didReceiveMessage收到消息的时候将消息存储起来。

// XMPPMessageArchiving.m
- (void)xmppStream:(XMPPStream *)sender didSendMessage:(XMPPMessage *)message
{
     if ([self shouldArchiveMessage:message outgoing:YES xmppStream:sender])
     {
         [xmppMessageArchivingStorage archiveMessage:message outgoing:YES     xmppStream:sender];
     }
}
消息确认为了防止发出去的消息丢失了,可以接入消息回执模块(XEP-184),这样对方每收到一条消息的时候都会返回一条确认的消息,如果没收到该条确认消息可以认为发送失败,确认消息的格式如下:
 <message to="[email protected]">
    <received xmlns="urn:xmpp:receipts" id="消息ID"/>
  </message>
不过这种方法也有些弊端,比如每次收到一条消息都必须回复,一定程度上会浪费流量以及影响服务器的性能,所以一般采用流管理来实现消息确认。 
 流关闭
 当退出程序的时候,最好能给服务器发送关闭流的通知,也就是发送结束流,服务器收到之后开始将后续发给该对象的消息收集到离线仓库中,当客户端重新上线的时候,服务端会主动将离线消息推送过来,这样不会丢失消息。由于客户端的操作经常是切到后台然后直接关掉程序,因此可以监听UIApplicationWillTerminateNotification消息,然后手动关闭流。
 
  
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationWillTerminate) name:UIApplicationWillTerminateNotification object:nil]; #pragma mark -- terminate /** * 申请后台更多的时间来完成关闭流的任务 */ -(void)applicationWillTerminate { UIApplication *app=[UIApplication sharedApplication]; UIBackgroundTaskIdentifier taskId; taskId=[app beginBackgroundTaskWithExpirationHandler:^(void){ [app endBackgroundTask:taskId]; }]; if(taskId==UIBackgroundTaskInvalid){ return; } [_xmppStream disconnectAfterSendingEndStream]; }
流管理 Stream Management是为了流恢复跟节确认而增加的。理想情况下,客户端发送关闭流的通知给服务器,服务器将后续的消息存储到离线仓库,等客户端再登陆上线的时候推送过来,但是在移动端网络可能随时断掉,这时候服务器并不会马上察觉(只能依靠TCP超时或者服务器自己的心跳包),它会认为对方还在线,将后续的消息发送过去,这样到服务器知道对方掉线的这段时间,期间的消息就丢失了,所以需要流管理来处理。 
 节确认(stanza acknowledgement) 用来确认一段时间内节(包括,,,不是 ,,或这样的stanzas不会在流管理中被确认跟计数的)是否被对方接收,客户端跟服务端都各自有有两个h值用来维护这些信息。从客户端来看,其中一个h值用于记录收到的节,比如当收到服务推送的消息时,会将该h值加1;另一个h值用于记录发出去的节,当发出一条消息时该h值也加1,所以为了确认消息是否被收到其实都是在比较双方的两个h值。 为了查询这些h值,
xmpp定义了<a/>和<r/>两个元素,<r/>用户请求节的确认消息,<a/>用于回答节的确认消息,必须携带自己已处理的h值。
服务端: <r xmlns='urn:xmpp:sm:3'/>
客户端: <a xmlns='urn:xmpp:sm:3' h='3'/>
比如服务端发送请求,客户端返回自己接受收到的h值(3),然后服务端会根据这个h值跟它自己记录发出去的节的h值做比较,假如小的话会重新发送剩下的节,来防止节丢失。
 流恢复 
由于移动网络可能随时down掉,所以在我们重连上来的时候需要的是快速恢复上一次的流,而不是重新新建一个流,roster的检索以及状态的广播,流管理可以通过上一次的流id(当启用流管理的时候,服务端会生成一个id来表示一个流)以及双方的h值来完成流的快速恢复以及这期间的节确认,发送未被确认的节。  
开启流管理 
要想启用流管理,客户端发送元素给服务端,服务端返回元素表示该流已经被管理了,同时有一个id值来标示这个流,xmppframework开启流管理只需要调用 enableStreamManagementWithResumption: maxTimeout:接口:
 
  
客户端: <enable xmlns= 'urn:xmpp:sm:3' resume= 'true'/> 服务端: <enabled xmlns= 'urn:xmpp:sm:3' id= '流id' resume= 'true'/> - ( void)xmppStreamDidAuthenticate:(XMPPStream *)sender { //登陆完成后,启用流管理 [_xmppStreamManagement enableStreamManagementWithResumption:YES maxTimeout: 0]; }
请求流恢复 
当客户端想要恢复一个流的时候,需要发送元素以及一个previd值,也就是想要恢复的上一次的流id,当流可以恢复的时候,服务端会返回元素,双方都会携带一个h值用于节确认。
客户端: <resume xmlns='urn:xmpp:sm:3' h='客户端接收的h值' previd='流id'/>
服务端: <resumed xmlns='urn:xmpp:sm:3' h='服务端接收的h值' previd='流id'/>
xmppframework将这部分逻辑封装在内部,不过这些h跟流id的值是存储在内存中,当程序退出的时候这些值就没了,也就无法恢复流。所以实际应用的时候需要将这些值保存到本地,比如demo里的XMPPStreamManagementPersistentStorage。
 xmpp注意点 
文件http上传 
由于xmpp只支持文本,所以类似音频这种二进制文件需要用base64转成文本形式,但更好的方式是采用http上传文件,消息体保存的是文件对应的URL。 
登陆改进
 xmpp的登陆涉及到始化流、TLS握手和SASL验证等,步骤比较繁琐,可以根据情况简化流程。
 TLS加密 
假如我们的im需要加密,可以开启TLS,不过iOS的TLS不支持压缩。 GCDAsyncSocket内部已经帮我们封装协商的过程,不过我们可能会收到错误:kCFStreamErrorDomainSSL Code=-9807,这是由于服务器证书并不是正式的证书,所以需要手动去认证:
 
  
//设置手动认证证书 NSMutableDictionary *settings = [ NSMutableDictionary dictionary]; [settings setObject:@YES forKey:GCDAsyncSocketManuallyEvaluateTrust]; [asyncSocket startTLS:settings]; - ( void)socketDidSecure:(GCDAsyncSocket *)sock { // 开始接收数据 [sock readDataWithTimeout:TIMEOUT_XMPP_READ_STREAM tag:TAG_XMPP_READ_STREAM]; } //在delegate方法中,手动信任 -( void)xmppStream:(XMPPStream *)sender didReceiveTrust:(SecTrustRef)trust completionHandler:( void (^)( BOOL))completionHandler { if (completionHandler) completionHandler( YES); }
一个简单的demo工程可以在这里找到。

猜你喜欢

转载自blog.csdn.net/weixin_40873814/article/details/78986557
今日推荐