即时通讯

1.XMPP(http://xmpp.org)
1.概念
XMPP是一种基于标准通用标记语言的子集XML的协议,它继承了在XML环境中灵活的发展性。因此,基于XMPP的应用具有超强的可扩展性。经过扩展以后的XMPP可以通过发送扩展的信息来处理用户的需求,以及在XMPP的顶端建立如内容发布系统和基于地址的服务等应用程序。而且,XMPP包含了针对服务器端的软件协议,使之能与另一个进行通话,这使得开发者更容易建立客户应用程序或给一个配好系统添加功能
补充:

- XMPP是一个基于个Socket通过的网络协议,目的是为了保存长连接,以实现即时通讯功能
- XMPP的客户端是使用一个XMPPFramework框架实现
- XMPP的服务器是使用Openfire,一个开源的服务器
- 客户端获取到服务器发送过来的好友消息,客户端需要对XML进行解析,使用的解析框架的KissXML框架,而不是NSXMLParser/GDataXML
- XMPP是一个即时通讯的协议,它规范了用于即时通信在网络上数据传输格式的,比如登录,获取好友列表等等的格式。XMPP在网络传输的数据是XML格式。比如登录:把用户名和密码放在xml的标签中,传输到服务器

2.XMPP的使用
讲解以下内容:
1.导入XMPP框架
2.登录 & 注销
3.注册
4.用户信息
5.好友
6.消息
7.文件传送(图片,音频)
1.导入XMPP框架
1.1 下载XMPPFramework框架
GitHub: XMPPFramework
1.2导入依赖框架

1.3导入一下文件夹

**1.4导入XMPP扩展框架 **

2.登录&注销
2.1 实现用户登录的步骤如下:
1. 实例化XMPPStream并设置代理,同时添加代理到工作队列
2. 使用JID连接至服务器,默认端口为5222,JID字符串中需要包含服务器的域名
3. 在完成连接的代理方法中验证用户密码,连接完成后XMPPStream的isConnect属性为YES
4. 在验证代理方法中判断用户是否登录成功
5. 上线或者下线成功后,向服务器发送Presence数据,以更新用户在服务器的状态
2.2 各部分的实现代码如下:
- 初始化 XMPPStream 并设置代理:
-(void)setupXMPPStream{
_xmppStream = [[XMPPStream alloc] init];
// 设置代理
[_xmppStream addDelegate:self delegateQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)];
}
- 连接到服务器
-(void)connectToHost{
NSLog(@"开始连接到服务器");
if (!_xmppStream) {
[self setupXMPPStream];
}
// 设置登录用户JID
//resource 标识用户登录的客户端 iphone android
XMPPJID *myJID = [XMPPJID jidWithUser:@"aaa" domain:@"bourne-mbp.local" resource:@"iphone" ];
_xmppStream.myJID = myJID;
// 设置服务器域名
_xmppStream.hostName = @"bourne-mbp.local";//不仅可以是域名,还可是IP地址
// 设置端口 如果服务器端口是5222,可以省略
_xmppStream.hostPort = 5222;
// 连接
NSError *err = nil;
if(![_xmppStream connectWithTimeout:XMPPStreamTimeoutNone error:&err]){
NSLog(@"%@",err);
}
}
- 连接成功后发送密码验证
-(void)sendPwdToHost{
NSLog(@"再发送密码授权");
NSError *err = nil;
[_xmppStream authenticateWithPassword:@"123456" error:&err];
if (err) {
NSLog(@"%@",err);
}
}
- 授权成功后,发送 在线 消息
#pragma mark 授权成功后,发送"在线" 消息
-(void)sendOnlineToHost{
NSLog(@"发送 在线 消息");
XMPPPresence *presence = [XMPPPresence presence];
NSLog(@"%@",presence);
[_xmppStream sendElement:presence];
}
2.3 需要实现的几个代理方法
#pragma mark 与主机连接成功
-(void)xmppStreamDidConnect:(XMPPStream *)sender{
NSLog(@"与主机连接成功");
// 主机连接成功后,发送密码进行授权
[self sendPwdToHost];
}
#pragma mark 与主机断开连接
-(void)xmppStreamDidDisconnect:(XMPPStream *)sender withError:(NSError *)error{
// 如果有错误,代表连接失败
NSLog(@"与主机断开连接 %@",error);
}
#pragma mark 授权成功
-(void)xmppStreamDidAuthenticate:(XMPPStream *)sender{
NSLog(@"授权成功");
[self sendOnlineToHost];
}
#pragma mark 授权失败
-(void)xmppStream:(XMPPStream *)sender didNotAuthenticate:(DDXMLElement *)error{
NSLog(@"授权失败 %@",error);
}
2.4 注销登录
- 发送离线信息
- 断开连接
-(void)logout{
// 1." 发送 `离线` 消息"
XMPPPresence *offline = [XMPPPresence presenceWithType:@"unavailable"];
[_xmppStream sendElement:offline];
// 2. 与服务器断开连接
[_xmppStream disconnect];
}
3.注册
- 与登录一样,首先发送帐号建立连接
- 连接成功后,发送注册的密码
- 注册成功后,框架会通知代理
实现以下代理方法
- (void)xmppStreamDidRegister:(XMPPStream *)sender {
NSLog(@"注册成功");
if (_resultBlock) {
_resultBlock(BWXMPPLoginResultSuccessed);
}
}
- (void)xmppStream:(XMPPStream *)sender didNotRegister:(DDXMLElement *)error {
if (_resultBlock) {
_resultBlock(BWXMPPLoginResultFailure);
}
}
4 .用户信息
XMPP是面向模块的,每一个大的动能都属于某一个模块,需要使用时,就在头文件中将其暴露出来(原本是被注释了的)
1.工作原理:
添加用户信息模块之后,XMPPFramework框架会自动从服务器获取用户信息,并使用CoreData保存到本地的数据库中,使用XMPPvCardTempModule可以访问数据
2.在XMPPFramework.h中将以下的头文件前面的注释去掉:
// 电子名片模块
#import "XMPPvCardTempModule.h"
#import "XMPPvCardCoreDataStorage.h"
// 头像模块
#import "XMPPvCardAvatarModule.h"
3.初始化模块
//添加电子名片模块
_vCardStorage = [XMPPvCardCoreDataStorage sharedInstance];
_vCard = [[XMPPvCardTempModule alloc] initWithvCardStorage:_vCardStorage];
//激活
[_vCard activate:_xmppStream];
//添加头像模块
_avatar = [[XMPPvCardAvatarModule alloc] initWithvCardTempModule:_vCard];
[_avatar activate:_xmppStream];
4.应用
//xmpp提供了一个方法,直接获取个人信息
XMPPvCardTemp *myVCard =[WCXMPPTool sharedWCXMPPTool].vCard.myvCardTemp;
// 设置头像
if(myVCard.photo){
self.haedView.image = [UIImage imageWithData:myVCard.photo];
}
// 设置昵称
self.nicknameLabel.text = myVCard.nickname;
5.好友
与用户信息模块相似,添加相应的好友花名册模块即可。
1.头文件
// 花名册模块
#import "XMPPRoster.h"
#import "XMPPRosterCoreDataStorage.h"
2.初始化
// 添加花名册模块【获取好友列表】
_rosterStorage = [[XMPPRosterCoreDataStorage alloc] init];
_roster = [[XMPPRoster alloc] initWithRosterStorage:_rosterStorage];
[_roster activate:_xmppStream];
3.应用
//使用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) {
WCLog(@"%@",err);
}
注意:使用NSFetchedResultsController并设置代理,如果数据库的内容发生了变化,这个类会自动通知代理,就可以设置界面的数据,做到实时更新。
6.消息
1.头文件
- 注意:这几个头文件没在XMPPFramework.h文件中,需要自己添加
// 消息模块
#import "XMPPMessageArchiving.h"
#import "XMPPMessageArchivingCoreDataStorage.h"
2.初始化
// 添加聊天模块
_msgStorage = [[XMPPMessageArchivingCoreDataStorage alloc] init];
_msgArchiving = [[XMPPMessageArchiving alloc] initWithMessageArchivingStorage:_msgStorage];
[_msgArchiving activate:_xmppStream];
3.应用
// 上下文
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) {
WCLog(@"%@",err);
}
7.文件传送(图片、音频)
1.原理分析
- 使用base64将文件转化为字符串,然后再通过XMPPFramework传输。
- 先将文件上传到服务器,再将文件网址通过XMPPFramework转输给好友,好友收到后再自行下载文件。
2.难点解析
- 需要给XMPPFramework的``数据体添加一个信息类型字段。
XMPPMessage *msg = [XMPPMessage messageWithType:@"chat" to:self.friendJid];
//text 纯文本
//image 图片
[msg addAttributeWithName:@"bodyType" stringValue:bodyType];
// 设置内容
[msg addBody:text];
NSLog(@"%@",msg);
[[WCXMPPTool sharedWCXMPPTool].xmppStream sendElement:msg];
- 根据消息类型解析消息
XMPPMessageArchiving_Message_CoreDataObject *msg = _resultsContr.fetchedObjects[indexPath.row];
// 判断是图片还是纯文本
NSString *chatType = [msg.message attributeStringValueForName:@"bodyType"];
if ([chatType isEqualToString:@"image"]) {
//下图片显示
[cell.imageView sd_setImageWithURL:[NSURL URLWithString:msg.body] placeholderImage:[UIImage imageNamed:@"DefaultProfileHead_qq"]];
cell.textLabel.text = nil;
} else if ([chatType isEqualToString:@"text"]){
//显示消息
if ([msg.outgoing boolValue]) {//自己发
cell.textLabel.text = msg.body;
}else{//别人发的
cell.textLabel.text = msg.body;
}
cell.imageView.image = nil;
}

2.环信
1.概念



2.集成环信的准备工作
- 注册成为环信开发者:http://www.easemob.com
- 在开发者后台创建APP获取Key
- 官方SDKDEMO:http://www.easemob.com/docs/ios/IOSSDKPrepare/
- 下载环信SDK:http://www.easemob.com/download/im
- 推送证书的创建(如果不需要离线推送消息可不必创建)
3.有关推送证书的内容
- 3.1 创建推送证书的步骤:
step1. 打开[苹果开发者网站:https://developer.apple.com/cn/]

step2. 从Member Center进入Certificates, Identifiers & Profiles

step3. 选择要制作的推送证书

对于开发环境(sandbox)的推送证书, 请选择 Apple Push Notification service SSL (Sandbox)
对于生产环境(production)的推送证书, 请选择 Apple Push Notification service SSL (Production)
step4. 选择对应的APP ID (环信示例使用ChatDemoUI, 所以此处选择com.easemob.enterprise.demo.ui)

step5. 根据Certificate Assistant的提示, 创建Certificate Request

step6. 上传上一步中创建的Certificate Request文件

step7. 上传完毕后, 推送证书就被正确生成了, 之后我们下载下来这个证书, 并双击导入系统

- 3.2 上传推送证书的步骤:
step1. 打开Application –> Utilities –> Keychain Access应用, 我们会看到有刚刚我们制作好的推送证书

step2. 选中证书对应的私钥(或者展开后选中证书), 点右键, 选择导出, 并设定密码(本步导出的证书使用的电脑务必与制作证书时step5中使用的是一台电脑。)

step3. 登陆环信管理后台

step4. 输入了正确的账号后, 选择对应的APP(环信示例为ChatDemoUI, 点击ChatDemoUI)

step5. 填写的证书名称
这个名称是个有意义的名字, 对推送直接相关, 稍后会在源码的修改里继续用到这个名字. 上传之前导出的P12文件, 密码则为此P12文件的密码, 证书类型请根据具体情况选择
(创建的是Apple Push Notification service SSL Sandbox请选择开发环境; Apple Push Notification service SSL Production请选择生产环境)
step6. 上传

请注意正确选择是生产环境还是测试环境的证书(我选的是开发环境,如果报错就选择生产环境)
4.集成环信SDK
集成SDK有两种方法,一种是用cocoaPods直接下载到自己的项目,一种是从官网下载SDK然后自己导入,不管哪种都需要导入第三方依赖库。
1.导入SDK
将下载好的SDK文件夹(EaseMobSDK)拖入到项目中,并勾选上Destination

2.设置工程属性
2.1. 向Build Phases → Link Binary With Libraries 中添加依赖库


2.2. 向Build Settings → Linking → Other Linker Flags 中 添加-ObjC(注意大小写)

2.3. 如果项目中使用-ObjC有冲突,可以添加-force_load来解决。
格式为: -force_load[空格]EaseMobSDK/lib/libEaseMobClientSDKLite.a(静态库的路径)(导入SDK过后会自动添加,如果没有就需要手动添加了,路径在EaseMobSDK-->lib中)。
- step1. 先添加一个-force_load

step2. 将静态库拖动到上一步添加的-force_load下面

step3. 最终效果

3.编译工程(根据情况修改所报的错误)
以上步骤进行完后,编译工程,如果没有报错,恭喜你,集成sdk成功,可以进行下一步了。
我们集成聊天功能的时候很多东西其实都不用我们动手,例如聊天页面等,我们可以直接从demo中拖过来,如果有不如意的地方,可以根据自己的喜好适当修改,下面我就说一下需要从环信3.0中搜索下面文件直接导入:

编译过后会报错如下图:

这是因为没有导入EaseUI头文件导致的,在这里我们可以创建一个PCH文件(注意配置路径在

)
在pch文件中导入EaseUI.h头文件,编译成功。但是有可能出现报错这里介绍两种常见的报错
第一种:

这种报错解决办法是在环信3.0的demo中导入FixFopen.c文件即可,如果仍然报错那么在自己的pch文件中加入如下代码即可:

第二种:
属于第三方库的冲突报错,这种的话可以将环信中的第三方删除,其中需要注意的两个第三方库分别是:
1). EMSDWebImage,这是环信自己加了前缀,删除这个第三方库过后要在相应的代码中删除EM前缀删除;
2). MJRefresh,它使用的是老版本的,在新版本中一些老的方法已经删除,所以只能用EaseUI中的MJRefresh,后期环信应该会更新的。
5.使用环信
1.初始化应用,有两个方法
/*
*registerSDKWithAppKey:区别app的标识,开发者注册及管理后台apnsCertName:iOS中推送证书名称。制作与上传推送证书
*/
//环信的初始化
// [[EaseMob sharedInstance] registerSDKWithAppKey:@"MG#MGChat"
ap sCertName:@””];
//环信的初始化
并隐藏日志输出
[[EaseMob sharedInstance] registerSDKWithAppKey:@"MG#MGChat" apnsCertName:@"" otherConfig:@{kSDKConfigEnableConsoleLogger:@(NO)}];

2.注册
[[EaseMob
sharedInstance].chatManager asyncRegisterNewAccount:”MG” password:”123456” withCompletion:^(NSString *username, NSString *password,EMError *eror) {
NSLog(@"error:%@,username:%@,pwd:%@",error,username,password);
} onQueue:nil];
3. 登录
- 3.1 自动登录

- 3.2 掉线自动登录
如果网络不通过,用户应该自动连接到服务器,以及时接收消息;
此功能无需程序员自己做,环信框架已实现,环信SDK会调用自动连接的代理方法来通知应用程序
/*!
@method
@brief 将要发起自动重连操作时发送该回调
@discussion
@result
*/
- (void)willAutoReconnect;
/*!
@method
@brief 自动重连操作完成后的回调(成功的话,error为nil,失败的话,查看error的错误信息)
@discussion
@result
*/
- (void)didAutoReconnectFinishedWithError:(NSError*)error;
4.好友

5.聊天
环信消息发送的流程:
1.先把记录保存到Conversation表
2.接着发送网络请求,API如下:
[[EaseMob sharedInstance].chatManager asyncSendMessage:message progress:self prepare:^(EMMessage *message, EMError *error) {
NSLog(@"prepare %@",message.messageBodies);
} onQueue:nil completion:^(EMMessage *message, EMError *error) {
NSLog(@"完成%@",message.messageBodies);
} onQueue:nil];

- 环信提供会话管理者(EMConversation)来管理未读消息数和历史聊天记录,具体代码如下:
总的未读消息数需要遍历conversations
// 1.获取所有历史会话
NSArray *conversations = [[EaseMob sharedInstance].chatManager conversations];
// 2.如果内存中,没有会话,从数据库中加载
if(conversations.count == 0)
{
conversations = [[EaseMob sharedInstance].chatManager loadAllConversationsFromDatabaseWithAppend2Chat:YES];
}

当进入聊天页面时,需要设置所有当前会话信息或者设置已经加载的消息为已读
// 设置当前会话所有消息都为已读
[self.conversation markAllMessagesAsRead:YES];
//设置某条消息为已读
[self.conversation markMessageWithId:<#(NSString *)#> asRead:<#(BOOL)#>]
6.语言消息
- 在录音前导入环信封装的两个录音框架,如图

- 刚才导入的两个框架,已经实现了录音Api
// 开始录音
[[EMCDDeviceManager
sharedInstance]
asyncStartRecordingWithFileName:fileName completion:^(NSError *error){
if(error) {
NSLog(@"failure to start recording");
}
}];
// 结束录音
[[EMCDDeviceManager sharedInstance] asyncStopRecordingWithCompletion:^(NSString *recordPath, NSInteger aDuration, NSError *error) {
NSLog(@"%@",recordPath);
}];
// 语音对象
EMChatVoice *voice = [[EMChatVoice alloc] initWithFile:filePath displayName:@"audio"];
//消息体
EMVoiceMessageBody *body = [[EMVoiceMessageBody alloc] initWithChatObject:voice];
EMMessage *message = [[EMMessage alloc] initWithReceiver:self.buddy.username bodies:@[body]];
message.messageType = eMessageTypeChat;// 私聊
// 不加密
message.requireEncryption = NO;
// 播放完成
[[EMCDDeviceManager sharedInstance] asyncPlayingWithPath:filePath completion:^(NSError *error) {
NSLog(@"播放完成%@",error);
}];
7.退出(异步方法)
[[EaseMobsharedInstance].chatManagerasyncLogoffWithUnbindDeviceToken:YEScompletion:^(NSDictionary *info, EMError *error) {
if (!error) {//退出成功
}else{//退出失败;
}
}
onQueue:nil];
Demo:
先来用户的登录和注册,由于只是搭建简单的基础聊天功能,我将这段代码写在appdelegate里面的,废话不多说,直接上图吧:

注释中间有说明如何聊天
现在最主要的就是如何才能实现聊天界面的配置了,其实也是相当简单的,下面我们说说具体是怎么做的吧!!!其实聊天界面在我们刚才导入的EaseUI中就已经搭建好了,我们只需要跳转过去就行了

这里我用的是button跳转,具体代码图片上面都有,下面给你们看下效果图吧!!!


集成基础聊天功能到此就结束了,有什么不明白的和技术问题可以参考环信官方文档和在线咨询
相关优秀博客推荐链接:
(基于环信实现在线聊天功能)http://www.jianshu.com/p/055069fc10f3#
(基于环信实现实时视频语音通话功能)http://www.jianshu.com/p/c5c46c2fa9c6
(上帝说:要约炮!于是有了XMPP)https://www.jianshu.com/p/c7bbbad90639
网友评论