文章背景
大部分的APP都是盈利为目的而开发,与消费者互动必成重要的一个功能,有互动就有潜在客户,而且大多数老板都认为自己的APP没有IM功能就会觉得不上档次。目前市面上比较好点的即时通讯第三方有环信、融云等。我不采用第三方的原因主要三点:1、第三方前期免费开放,有可能在圈用户,不排除后期收费;2、其实第三方服务器经常蛋机,总有正在维护升级,请耐心等待啥啥的,总感觉受之于人;3、第三方sdk服务器都是好多人在共享,难免消息延迟。

文件目标
1、搭建XMPP服务器
2、实现XMPP即时通讯

什么是OpenFire
您可以使用它轻易的构建高效率的即时通信服务器.
Openfire安装和使用都非常简单,并利用Web进行管理。单台服务器可支持上万并发用户。
由于是采用开放的XMPP协议,您可以使用各种支持XMPP协议的IM客户端软件登陆服务.

1、什么是XMPP
1-1、XMPP(可扩展消息处理现场协议)是基于可扩展标记语言(XML)的协议,它用于即时消息(IM)以及在线现场探测。
1-2、XMPP的前身是Jabber,一个开源形式组织产生的网络即时通信协议

XMPP体系架构
XMPP的基本网络结构 ,xmpp定义了3个角色
Client
Server
Gateway
通信能够在这三者的任意两个之间双向发生。服务器同时承担了客户端信息记录,连接管理和信息的路由功能。网关承担着与异构即时通信系统的互联互通,异构系统可以包括SMS(短信),MSN,ICQ等。基本的网络形式是单客户端通过TCP/IP连接到单服务器,然后在之上传输XML。
客户端利用xmpp(基于TCP/IP)访问server,传输的是XML
Client--------Server----Client
TCP TCP TCP

XMPP server:其内核是一个XMPP路由器,完成基本组件间的数据包交换和路由。
功能:
1.会话管理器:负责客户端会话认证,在线状态,用户联系表等
2.数据存储器(XDB):连接数据库系统,保持用户信息、通信日志等
3.连接器管理器:管理与客户端之间的连接
4.服务器连接器:管理xmpp服务器之间的连接
5.传输器:建立xmpp服务器与非xmpp服务器通信

XMPP工作原理图

XMPP工作原理说明
所有从一个client到另一个client的jabber消息和数据都要通过xmpp server。
1.client连接到server
2.server利用本地目录系统的证书对其认证
3.client制定目标地址,让server告知目标状态
4.server查找,连接并进行相互认证
5.client间进行交互

好了好了,原理就先说到这里吧,想必实现才是我们最关心的的,下面就开始吧。

2、快速入门
step
2-1、准备数据库

2-2、安装openfire服务
2-2-1、
服务下载地址:http://www.igniterealtime.org/projects/openfire/

得到安装文件:

2-2-2、安装openfire
2-2-2-1、双击可执行安装文件,点击下一步傻瓜式安装即可。
安装成功效果如下:

在安装路径我们可以看到生成的相关数据库各种版本脚本任你选择。

2-2-2-2、执行脚本得到openfire服务对应的数据库

2-3、配置openfire

2-3-1、第一次启动openfire会出现配置界面

next

next

next

next

next

next

next

看见没,至此openfire可以正常工作啦!为XMPP客户端提供了服务器的能力。

2-4、XMPP实现

2-4-1、XMPPFramework下载
git下载址:https://github.com/dechengyang/XMPPFramework
官网地址:https://xmpp.org/
2-4-2、导入XMPPFramework框架

2-4-3、添加相关动态库

至此XMPPFramework框架已经导入完毕。

2-4-4、XMPP工具封装类
2-4-4-1、WCUserInfo类
.h文件

import <Foundation/Foundation.h>
#import "Singleton.h"
static NSString *domain = @"ydc.local";
@interface WCUserInfo : NSObjectsingleton_interface(WCUserInfo);@property (nonatomic, copy) NSString *user;//用户名
@property (nonatomic, copy) NSString *pwd;//密码/***  登录的状态 YES 登录过/NO 注销*/
@property (nonatomic, assign) BOOL  loginStatus;@property (nonatomic, copy) NSString *registerUser;//注册的用户名
@property (nonatomic, copy) NSString *registerPwd;//注册的密码
@property (nonatomic, copy) NSString *jid;/***  从沙盒里获取用户数据*/
-(void)loadUserInfoFromSanbox;/***  保存用户数据到沙盒*/
-(void)saveUserInfoToSanbox;
@end

.m文件


#import "WCUserInfo.h"#define UserKey @"user"
#define LoginStatusKey @"LoginStatus"
#define PwdKey @"pwd"
@implementation WCUserInfosingleton_implementation(WCUserInfo)-(void)saveUserInfoToSanbox{NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];[defaults setObject:self.user forKey:UserKey];[defaults setBool:self.loginStatus forKey:LoginStatusKey];[defaults setObject:self.pwd forKey:PwdKey];[defaults synchronize];}-(void)loadUserInfoFromSanbox{NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];self.user = [defaults objectForKey:UserKey];self.loginStatus = [defaults boolForKey:LoginStatusKey];self.pwd = [defaults objectForKey:PwdKey];
}-(NSString *)jid{return [NSString stringWithFormat:@"%@@%@",self.user,domain];
}
@end

该类主要用来存放用户信息。

2-4-4-2、WCXMPPTool类
.h文件

#import <Foundation/Foundation.h>
#import "Singleton.h"
#import "XMPPFramework.h"typedef enum {XMPPResultTypeLoginSuccess,//登录成功XMPPResultTypeLoginFailure,//登录失败XMPPResultTypeNetErr,//网络不给力XMPPResultTypeRegisterSuccess,//注册成功XMPPResultTypeRegisterFailure//注册失败
}XMPPResultType;typedef void (^XMPPResultBlock)(XMPPResultType type);// XMPP请求结果的block@interface WCXMPPTool : NSObjectsingleton_interface(WCXMPPTool);@property (nonatomic, strong,readonly)XMPPStream *xmppStream;
@property (nonatomic, strong,readonly)XMPPvCardTempModule *vCard;//电子名片
@property (nonatomic, strong,readonly)XMPPRosterCoreDataStorage *rosterStorage;//花名册数据存储
@property (nonatomic, strong,readonly)XMPPRoster *roster;//花名册模块
@property (nonatomic, strong,readonly)XMPPMessageArchivingCoreDataStorage *msgStorage;//聊天的数据存储
/***  注册标识 YES 注册 / NO 登录*/
@property (nonatomic, assign,getter=isRegisterOperation) BOOL  registerOperation;//注册操作/***  用户注销*/
-(void)xmppUserlogout;
/***  用户登录*/
-(void)xmppUserLogin:(XMPPResultBlock)resultBlock;/***  用户注册*/
-(void)xmppUserRegister:(XMPPResultBlock)resultBlock;
@end

.m文件


#import "WCXMPPTool.h"/** 在AppDelegate实现登录1. 初始化XMPPStream2. 连接到服务器[传一个JID]3. 连接到服务成功后,再发送密码授权4. 授权成功后,发送"在线" 消息*/
@interface WCXMPPTool ()<XMPPStreamDelegate>{XMPPResultBlock _resultBlock;XMPPReconnect *_reconnect;// 自动连接模块XMPPvCardCoreDataStorage *_vCardStorage;//电子名片的数据存储XMPPvCardAvatarModule *_avatar;//头像模块XMPPMessageArchiving *_msgArchiving;//聊天模块}// 1. 初始化XMPPStream
-(void)setupXMPPStream;// 2.连接到服务器
-(void)connectToHost;// 3.连接到服务成功后,再发送密码授权
-(void)sendPwdToHost;// 4.授权成功后,发送"在线" 消息
-(void)sendOnlineToHost;
@end@implementation WCXMPPToolsingleton_implementation(WCXMPPTool)#pragma mark  -私有方法
#pragma mark 初始化XMPPStream
-(void)setupXMPPStream{_xmppStream = [[XMPPStream alloc] init];
#warning 每一个模块添加后都要激活//添加自动连接模块_reconnect = [[XMPPReconnect alloc] init];[_reconnect activate:_xmppStream];//添加电子名片模块_vCardStorage = [XMPPvCardCoreDataStorage sharedInstance];_vCard = [[XMPPvCardTempModule alloc] initWithvCardStorage:_vCardStorage];//激活[_vCard activate:_xmppStream];//添加头像模块_avatar = [[XMPPvCardAvatarModule alloc] initWithvCardTempModule:_vCard];[_avatar activate:_xmppStream];// 添加花名册模块【获取好友列表】_rosterStorage = [[XMPPRosterCoreDataStorage alloc] init];_roster = [[XMPPRoster alloc] initWithRosterStorage:_rosterStorage];[_roster activate:_xmppStream];// 添加聊天模块_msgStorage = [[XMPPMessageArchivingCoreDataStorage alloc] init];_msgArchiving = [[XMPPMessageArchiving alloc] initWithMessageArchivingStorage:_msgStorage];[_msgArchiving activate:_xmppStream];// 设置代理[_xmppStream addDelegate:self delegateQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)];
}#pragma mark 释放xmppStream相关的资源
-(void)teardownXmpp{// 移除代理[_xmppStream removeDelegate:self];// 停止模块[_reconnect deactivate];[_vCard deactivate];[_avatar deactivate];[_roster deactivate];[_msgArchiving deactivate];// 断开连接[_xmppStream disconnect];// 清空资源_reconnect = nil;_vCard = nil;_vCardStorage = nil;_avatar = nil;_roster = nil;_rosterStorage = nil;_msgArchiving = nil;_msgStorage = nil;_xmppStream = nil;}
#pragma mark 连接到服务器
-(void)connectToHost{MyLog(@"开始连接到服务器");if (!_xmppStream) {[self setupXMPPStream];}// 设置登录用户JID//resource 标识用户登录的客户端 iphone android// 从单例获取用户名NSString *user = nil;if (self.isRegisterOperation) {user = [WCUserInfo sharedWCUserInfo].registerUser;}else{user = [WCUserInfo sharedWCUserInfo].user;}XMPPJID *myJID = [XMPPJID jidWithUser:user domain:@"ydc.local" resource:@"iphone" ];_xmppStream.myJID = myJID;// 设置服务器域名//_xmppStream.hostName = @"120.27.137.134";//不仅可以是域名,还可是IP地址//_xmppStream.hostName = @"ysindustrial.com";//不仅可以是域名,还可是IP地址_xmppStream.hostName = @"192.168.1.100";//不仅可以是域名,还可是IP地址//_xmppStream.hostName = @"192.168.0.100";// 设置端口 如果服务器端口是5222,可以省略_xmppStream.hostPort = 5222;// 连接NSError *err = nil;if(![_xmppStream connectWithTimeout:XMPPStreamTimeoutNone error:&err]){MyLog(@"%@",err);}}#pragma mark 连接到服务成功后,再发送密码授权
-(void)sendPwdToHost{MyLog(@"再发送密码授权");NSError *err = nil;// 从单例里获取密码NSString *pwd = [WCUserInfo sharedWCUserInfo].pwd;[_xmppStream authenticateWithPassword:pwd error:&err];if (err) {MyLog(@"%@",err);}
}#pragma mark  授权成功后,发送"在线" 消息
-(void)sendOnlineToHost{MyLog(@"发送 在线 消息");XMPPPresence *presence = [XMPPPresence presence];MyLog(@"%@",presence);[_xmppStream sendElement:presence];}
#pragma mark -XMPPStream的代理
#pragma mark 与主机连接成功
-(void)xmppStreamDidConnect:(XMPPStream *)sender{MyLog(@"与主机连接成功");if (self.isRegisterOperation) {//注册操作,发送注册的密码NSString *pwd = [WCUserInfo sharedWCUserInfo].registerPwd;[_xmppStream registerWithPassword:pwd error:nil];}else{//登录操作// 主机连接成功后,发送密码进行授权[self sendPwdToHost];}}
#pragma mark  与主机断开连接
-(void)xmppStreamDidDisconnect:(XMPPStream *)sender withError:(NSError *)error{// 如果有错误,代表连接失败// 如果没有错误,表示正常的断开连接(人为断开连接)if(error && _resultBlock){_resultBlock(XMPPResultTypeNetErr);}MyLog(@"与主机断开连接 %@",error);}#pragma mark 授权成功
-(void)xmppStreamDidAuthenticate:(XMPPStream *)sender{MyLog(@"授权成功");[self sendOnlineToHost];// 回调控制器登录成功if(_resultBlock){_resultBlock(XMPPResultTypeLoginSuccess);}}#pragma mark 授权失败
-(void)xmppStream:(XMPPStream *)sender didNotAuthenticate:(DDXMLElement *)error{MyLog(@"授权失败 %@",error);// 判断block有无值,再回调给登录控制器if (_resultBlock) {_resultBlock(XMPPResultTypeLoginFailure);}
}#pragma mark 注册成功
-(void)xmppStreamDidRegister:(XMPPStream *)sender{MyLog(@"注册成功");if(_resultBlock){_resultBlock(XMPPResultTypeRegisterSuccess);}}#pragma mark 注册失败
-(void)xmppStream:(XMPPStream *)sender didNotRegister:(DDXMLElement *)error{MyLog(@"注册失败 %@",error);if(_resultBlock){_resultBlock(XMPPResultTypeRegisterFailure);}}#pragma mark -公共方法
-(void)xmppUserlogout{// 1." 发送 "离线" 消息"XMPPPresence *offline = [XMPPPresence presenceWithType:@"unavailable"];[_xmppStream sendElement:offline];// 2. 与服务器断开连接[_xmppStream disconnect];// 3. 回到登录界面
//    UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"Login" bundle:nil];
//
//    self.window.rootViewController = storyboard.instantiateInitialViewController;//ydc 2016080808 报错注释//[UIStoryboard showInitialVCWithName:@"Login"];//4.更新用户的登录状态[WCUserInfo sharedWCUserInfo].loginStatus = NO;[[WCUserInfo sharedWCUserInfo] saveUserInfoToSanbox];}-(void)xmppUserLogin:(XMPPResultBlock)resultBlock{// 先把block存起来_resultBlock = resultBlock;//    Domain=XMPPStreamErrorDomain Code=1 "Attempting to connect while already connected or connecting." UserInfo=0x7fd86bf06700 {NSLocalizedDescription=Attempting to connect while already connected or connecting.}// 如果以前连接过服务,要断开[_xmppStream disconnect];// 连接主机 成功后发送登录密码[self connectToHost];
}-(void)xmppUserRegister:(XMPPResultBlock)resultBlock{// 先把block存起来_resultBlock = resultBlock;// 如果以前连接过服务,要断开[_xmppStream disconnect];// 连接主机 成功后发送注册密码[self connectToHost];
}-(void)dealloc{[self teardownXmpp];
}
@end

把XMPP核心功能都封装在该类中,方便统一调用。

2-4-4-3、Singleton类

// .h
#define singleton_interface(class) + (instancetype)shared##class;// .m
#define singleton_implementation(class) \
static class *_instance; \
\
+ (id)allocWithZone:(struct _NSZone *)zone \
{ \static dispatch_once_t onceToken; \dispatch_once(&onceToken, ^{ \_instance = [super allocWithZone:zone]; \}); \
\return _instance; \
} \
\
+ (instancetype)shared##class \
{ \if (_instance == nil) { \_instance = [[class alloc] init]; \} \
\return _instance; \
}

2-4-5、用户注册
2-4-5-1、注册流程
1.初始化XMPPStream
2.连接到服务器[传一个JID]
3.连接到服务成功后,再发送密码授权

2-4-5-2、注册控制器中的代码如下:


//ydc 20160809 openfire用户注册
-(void)XMPPRegisterUser:(NSString *)username :(NSString *)pwd{// 1.把用户注册的数据保存单例WCUserInfo *userInfo = [WCUserInfo sharedWCUserInfo];userInfo.registerUser =username;userInfo.registerPwd = openfirePassword;// 2.调用WCXMPPTool的xmppUserRegister[WCXMPPTool sharedWCXMPPTool].registerOperation = YES;// 提示[MBProgressHUD showMessage:@"正在注册中....." toView:self.view];__weak typeof(self) selfVc = self;[[WCXMPPTool sharedWCXMPPTool] xmppUserRegister:^(XMPPResultType type) {[selfVc handleResultType:type];}];
}/***  处理注册的结果*/
-(void)handleResultType:(XMPPResultType)type{dispatch_async(dispatch_get_main_queue(), ^{[MBProgressHUD hideHUDForView:self.view];switch (type) {case XMPPResultTypeNetErr:[MBProgressHUD showError:@"网络不稳定" toView:self.view];break;case XMPPResultTypeRegisterSuccess://[MBProgressHUD showError:@"注册成功" toView:self.view];[self showSVString:@"注册成功"];{__weak RegisterViewController *weakSelf = self;dispatch_time_t delayTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC));dispatch_after(delayTime, dispatch_get_main_queue(), ^{[weakSelf delayMethod];});}// 回到上个控制器/**[self dismissViewControllerAnimated:YES completion:nil];if ([self.delegate respondsToSelector:@selector(regisgerViewControllerDidFinishRegister)]) {[self.delegate regisgerViewControllerDidFinishRegister];}**/break;case XMPPResultTypeRegisterFailure:[MBProgressHUD showError:@"注册失败,用户名重复" toView:self.view];break;default:break;}});}

我们进入WCXMPPTool的xmppUserRegister方法如下:

-(void)xmppUserRegister:(XMPPResultBlock)resultBlock{// 先把block存起来_resultBlock = resultBlock;// 如果以前连接过服务,要断开[_xmppStream disconnect];// 连接主机 成功后发送注册密码[self connectToHost];
}

继续进入connectToHost方法


#pragma mark 连接到服务器
-(void)connectToHost{MyLog(@"开始连接到服务器");if (!_xmppStream) {[self setupXMPPStream];}// 设置登录用户JID//resource 标识用户登录的客户端 iphone android// 从单例获取用户名NSString *user = nil;if (self.isRegisterOperation) {user = [WCUserInfo sharedWCUserInfo].registerUser;}else{user = [WCUserInfo sharedWCUserInfo].user;}XMPPJID *myJID = [XMPPJID jidWithUser:user domain:@"ydc.local" resource:@"iphone" ];_xmppStream.myJID = myJID;// 设置服务器域名//_xmppStream.hostName = @"120.27.137.134";//不仅可以是域名,还可是IP地址//_xmppStream.hostName = @"ysindustrial.com";//不仅可以是域名,还可是IP地址_xmppStream.hostName = @"192.168.1.100";//不仅可以是域名,还可是IP地址//_xmppStream.hostName = @"192.168.0.100";// 设置端口 如果服务器端口是5222,可以省略_xmppStream.hostPort = 5222;// 连接NSError *err = nil;if(![_xmppStream connectWithTimeout:XMPPStreamTimeoutNone error:&err]){MyLog(@"%@",err);}}

当执行完上一步之后,XMPP框架会自动回调到下面的其中一个代理方法中

#pragma mark -XMPPStream的代理
#pragma mark 与主机连接成功
-(void)xmppStreamDidConnect:(XMPPStream *)sender{MyLog(@"与主机连接成功");if (self.isRegisterOperation) {//注册操作,发送注册的密码NSString *pwd = [WCUserInfo sharedWCUserInfo].registerPwd;[_xmppStream registerWithPassword:pwd error:nil];}else{//登录操作// 主机连接成功后,发送密码进行授权[self sendPwdToHost];}}
#pragma mark  与主机断开连接
-(void)xmppStreamDidDisconnect:(XMPPStream *)sender withError:(NSError *)error{// 如果有错误,代表连接失败// 如果没有错误,表示正常的断开连接(人为断开连接)if(error && _resultBlock){_resultBlock(XMPPResultTypeNetErr);}MyLog(@"与主机断开连接 %@",error);}

2-4-5-3、效果如下:

2-4-6、用户登录
2-4-6-1、登录流程
1.初始化XMPPStream
2.连接到服务器[传一个JID]
3.连接到服务成功后,再发送密码授权
4.授权成功后,发送"在线" 消息

2-4-6-1、登录控制器中的登录代码如下:

//ydc 20160809 openfire用户登录
- (void)XMPPlogin:(NSString *)username: (NSString*)pwd{// 保存数据到单例WCUserInfo *userInfo = [WCUserInfo sharedWCUserInfo];userInfo.user = username;userInfo.pwd = openfirePassword;//隐藏键盘[self.view endEditing:YES];// 登录之前给个提示[MBProgressHUD showMessage:@"正在登录中..." toView:self.view];[WCXMPPTool sharedWCXMPPTool].registerOperation = NO;__weak typeof(self) selfVc = self;[[WCXMPPTool sharedWCXMPPTool] xmppUserLogin:^(XMPPResultType type) {[selfVc handleResultType:type];}];
}-(void)handleResultType:(XMPPResultType)type{// 主线程刷新UIdispatch_async(dispatch_get_main_queue(), ^{[MBProgressHUD hideHUDForView:self.view];switch (type) {case XMPPResultTypeLoginSuccess:NSLog(@"登录成功");[self enterMainPage];[self tapAction];break;case XMPPResultTypeLoginFailure:NSLog(@"登录失败");[MBProgressHUD showError:@"用户名或者密码不正确" toView:self.view];break;case XMPPResultTypeNetErr:[MBProgressHUD showError:@"网络不给力" toView:self.view];default:break;}});}

登录只比注册多一个"授权成功后,发送"在线" 消息"步骤,代码如下:

#pragma mark  授权成功后,发送"在线" 消息
-(void)sendOnlineToHost{MyLog(@"发送 在线 消息");XMPPPresence *presence = [XMPPPresence presence];MyLog(@"%@",presence);[_xmppStream sendElement:presence];}

2-4-6-2、效果如下:

看见没,我们已经成功登陆到了openfire服务器啦!

2-4-7、添加好友
2-4-7-1、添加好友控制器中的添加好友代码如下:

-(BOOL)submitAction{// 添加好友// 1.获取好友账号NSString *user = _phoneNumTextfield.text;MyLog(@"%@",user);// 判断这个账号是否为手机号码if([CustomHelper checkTelNumber:_phoneNumTextfield.text]){}else{//提示[self showAlert:@"请输入正确的手机号码"];return YES;}//判断是否添加自己if([user isEqualToString:[WCUserInfo sharedWCUserInfo].user]){[self showAlert:@"不能添加自己为好友"];return YES;}NSString *jidStr = [NSString stringWithFormat:@"%@@%@",user,domain];XMPPJID *friendJid = [XMPPJID jidWithString:jidStr];//判断好友是否已经存在if([[WCXMPPTool sharedWCXMPPTool].rosterStorage userExistsWithJID:friendJid xmppStream:[WCXMPPTool sharedWCXMPPTool].xmppStream]){[self showAlert:@"当前好友已经存在"];return YES;}// 2.发送好友添加的请求// 添加好友,xmpp有个叫订阅[[WCXMPPTool sharedWCXMPPTool].roster subscribePresenceToUser:friendJid];return YES;
}

2-4-7-2、效果 如下:


看见没,我们已经把好友关系成功维护到了服务器端数据库中的好友关系表中

2-4-8、加载好友列表
2-4-8-1、两个核心类

#import "XMPPRoster.h"
#import "XMPPCoreDataStorage.h"

2-4-8-2、实现数据变化监听代理NSFetchedResultsControllerDelegate

#pragma mark 当数据的内容发生改变后,会调用 这个方法
-(void)controllerDidChangeContent:(NSFetchedResultsController *)controller{MyLog(@"数据发生改变");[self loadFriends2];//刷新表格[self.cityTableView reloadData];
}

2-4-8-3、获取好友列表核心代码如下:

-(void)loadFriends2{//使用CoreData获取数据// 1.上下文【关联到数据库XMPPRoster.sqlite】NSManagedObjectContext *context = [WCXMPPTool sharedWCXMPPTool].rosterStorage.mainThreadManagedObjectContext;// 2.FetchRequest【查哪张表】NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"XMPPUserCoreDataStorageObject"];// 3.设置过滤和排序// 过滤当前登录用户的好友NSString *jid = [WCUserInfo sharedWCUserInfo].jid;NSPredicate *pre = [NSPredicate predicateWithFormat:@"streamBareJidStr = %@",jid];request.predicate = pre;//排序NSSortDescriptor *sort = [NSSortDescriptor sortDescriptorWithKey:@"displayName" ascending:YES];request.sortDescriptors = @[sort];// 4.执行请求获取数据_resultsContrl = [[NSFetchedResultsController alloc] initWithFetchRequest:request managedObjectContext:context sectionNameKeyPath:nil cacheName:nil];_resultsContrl.delegate = self;NSError *err = nil;[_resultsContrl performFetch:&err];if (err) {MyLog(@"%@",err);}[self.view addSubview:self.cityTableView];}

2-4-8-4、效果如下:

2-4-9、发送消息
2-4-9-1、发送消息代码如下:

#pragma mark 发送聊天消息
-(void)sendMsgWithText:(NSString *)text{XMPPMessage *msg = [XMPPMessage messageWithType:@"chat" to:self.friendJid];// 设置内容[msg addBody:text];NSLog(@"%@",msg);[[WCXMPPTool sharedWCXMPPTool].xmppStream sendElement:msg];
}

2-4-9-2、效果如下:

看见没,我们把消息通过XMPP框架,再通过openfire服务成功发送到openfire服务对应的数据库中。openfire服务和数据有可能是在你本机上,也有可能部署在遥远的阿里云等服务器中。如果你高兴,可以使用Spark(现成的openfire客户端)与之通讯,完全no problem。
Spark下载地址:http://www.igniterealtime.org/downloads/index.jsp

2-4-10、加载聊天数据
2-4-10-1、实现数据变化监听代理NSFetchedResultsControllerDelegate

#pragma mark ResultController的代理
-(void)controllerDidChangeContent:(NSFetchedResultsController *)controller{// 刷新数据[_msgTableView reloadData];[self scrollToTableBottom];
}

2-4-10-2、加载聊天数据核心代码如下:

#pragma mark 加载XMPPMessageArchiving数据库的数据显示在表格
-(void)loadMsgs{// 上下文NSManagedObjectContext *context = [WCXMPPTool sharedWCXMPPTool].msgStorage.mainThreadManagedObjectContext;// 请求对象NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"XMPPMessageArchiving_Message_CoreDataObject"];// 过滤、排序// 1.当前登录用户的JID的消息// 2.好友的Jid的消息NSPredicate *pre = [NSPredicate predicateWithFormat:@"streamBareJidStr = %@ AND bareJidStr = %@",[WCUserInfo sharedWCUserInfo].jid,self.friendJid.bare];NSLog(@"%@",pre);request.predicate = pre;// 时间升序NSSortDescriptor *timeSort = [NSSortDescriptor sortDescriptorWithKey:@"timestamp" ascending:YES];request.sortDescriptors = @[timeSort];// 查询_resultsContr = [[NSFetchedResultsController alloc] initWithFetchRequest:request managedObjectContext:context sectionNameKeyPath:nil cacheName:nil];NSError *err = nil;// 代理_resultsContr.delegate = self;[_resultsContr performFetch:&err];NSLog(@"%@",_resultsContr.fetchedObjects);if (err) {MyLog(@"%@",err);}// 刷新数据[_msgTableView reloadData];[self scrollToTableBottom];
}

2-4-10-3、数据加载方式
xmpp框架其实考虑到性能问题,框架不会实时去openfire服务器读取数据,而是每一次用户登录成功之后数据同步到本地(每次同步本地没有的数据),然后界面层是去本地(SQLite)读取。

你可以在沙盒中看到几个数据库:

然后使用以下工具打开数据库查看:

源码下载地址:
http://download.csdn.net/download/xinanheishao/9925393
参考文档下载地址:
http://download.csdn.net/download/xinanheishao/9925853

Flutter电商实战项目:https://github.com/dechengyang/ydc_flutter_app

如果对你有帮助,赏我1元奶粉钱吧,多谢!
微信:

支付宝

openfire+XMPP实现即时通讯相关推荐

  1. android基于xmpp的即时通讯应用

    xmpp是一个通信协议.因为这是个开放的协议,为了节俭开发成本,很多即时应用都采用了这个协议.Android上最常用的组合asmack +openfire.Asmack是smack的android版, ...

  2. Android学习笔记--基于XMPP的即时通讯

    一.常见即时通讯实现 socket openfire+asmack 环信 信鸽 融云 二.XMPP优势 1. 开放性 XMPP协议是自由.开放.公开的,并且易于了解.而且在客户端.服务器.组件.源码库 ...

  3. java smack jar_Smack:一个开源的XMPP用于即时通讯的客户端类库

    原标题:Smack:一个开源的XMPP用于即时通讯的客户端类库 本资源由 伯乐在线- oneDay整理,您也想贡献一份力量?欢迎加入我们 » Smack是一个用于和XMPP服务器通信的类库,可以实现即 ...

  4. 基于Openfire Smack开发即时通讯应用、搭建Openfire服务器(一)

    一:Openfire是什么? Openfire 是基于XMPP 协议的IM 的服务器端的一个实现,虽然当两个用户连接后,可以通过点对点的方式来发送消息,但是用户还是需要连接到服务器来获取一些连接信息和 ...

  5. Openfire XMPP Smack RTC IM 即时通讯 聊天 MD

    Markdown版本笔记 我的GitHub首页 我的博客 我的微信 我的邮箱 MyAndroidBlogs baiqiantao baiqiantao bqt20094 baiqiantao@sina ...

  6. 即时通讯:XMPP基础

    即时通讯系列阅读 即时通讯基础 即时通讯:XMPP基础 即时通讯:XMPP项目实践-微聊 Smack类库最好的学习资料 1. XMPP 简介 XMPP(Extensible Messaging and ...

  7. 即时通讯:XMPP项目实践-微聊

    即时通讯系列阅读 即时通讯基础 即时通讯:XMPP基础 即时通讯:XMPP项目实践-微聊 Smack类库最好的学习资料 1. 项目简介 做一个类似QQ 的通讯工具,要求有注册.登录.添加好友.添加分组 ...

  8. 关于 IMPP/XMPP/SIMPLE 这几种即时通讯协议和NAT traversal

    IM(Instant Messaging)正在被广泛地采用,特别是在公司与它们的客户互动联接方案上.为了解决即时通讯的标准问题,IETF成立了专门的工作小组,研究和开发与IM相关的协议. 目前IM有四 ...

  9. (IM)即时通讯协议

    转载自点击打开链接 1.即时通讯技术 即时通讯(IM:Instant Messaging):又称实时通讯,支持用户在线实时交谈,允许两人或多人使用网络实时的传递文字消息.文件.语音与视频交流. 即时通 ...

  10. 基于开源jabber(XMPP)架设内部即时通讯服务的解决方案

    原文地址:基于开源jabber(XMPP)架设内部即时通讯服务的解决方案 作者:高傲的活着 Jabber 是著名的即时通讯服务服务器,它是一个自由开源软件,能让用户自己架即时通讯服务器,可以在Inte ...

最新文章

  1. 兴业银行与第四范式开启AI平台加速模式 毫秒级信用卡反欺诈系统上线
  2. pytorch 使用cpu_想读读PyTorch底层代码?这份内核机制简介送给你
  3. linux的静态编译elf无法调试,[翻译]自己动手编写一个Linux调试器系列之4 ELF文件格式与DWARF调试格式 by lantie@15PB...
  4. 新UI云开发壁纸小程序源码(新修复版带编译教程)
  5. 韭菜的自我修养-认知篇
  6. amf组网_【5G核心网】5G核心网SA组网方案及4G/5G互操作探讨
  7. MySQL主从同步延迟
  8. 一生温暖纯良,不舍爱与自由
  9. EF中使用数据库的标量值函数
  10. 基于VLC的视频播放器
  11. Linux学习笔记(4)
  12. 腾讯地图拾取坐标html,GitHub - cloydlau/coord-picker: 高德/腾讯地图坐标拾取器 / A coordinates picker for amap/tmap...
  13. 韩顺平Java基础入门笔记-第一章
  14. 基于树莓派的语音邮件收发
  15. scipy的安装教程
  16. 通达信 移动平均算法_中山证券通达信下载-中山证券通达信软件 v1.06 官方版
  17. java测试脚本怎么写_Jmeter测试脚本编写(初学者熟悉篇)
  18. 定义数字品牌资产,苏泽兰特CEO胡烜峰讲透元宇宙的价值与未来
  19. 获取手机IMSI,区分移动联通 电信,4G问题
  20. Swift - 使用EventKit操作提醒事项(2、新增、修改、删除提醒)

热门文章

  1. Docker容器 Cgroup资源分配(CPU和内存资源分配)
  2. 遗传算法实例(matlab编程实现)
  3. Quartz——CronTrigger触发器
  4. LCD1602液晶显示屏驱动文件
  5. openssl error while loading serial number
  6. css网页设计qq彩贝
  7. 社区计算机基础知识,社区计算机基础知识试题及答案.doc
  8. Symbian操作系统
  9. SysLoad3.exe木马病毒地分析及清除方法
  10. hibernate插件下载