一、XMPP简介
XMPP = The Extensible Messaging and Presence Protocol(可扩展通讯和表示协议)
XMPP 协议是基于 XML 流同服务器以及客户端交互的, 首先就要用到 XMPPStream
导入 XMPPFramework 框架
XMPPFramework这个框架很大, 东西也很多, 打开他的文件目录就可以看到Core和Extension里面的一些 API
二、项目代码
XMPPManager.h
//枚举,用于区别登陆还是注册
typedef NS_ENUM(NSInteger , ConnectToServerPurpose) {
//枚举值
ConnectToServerPurposeLogin ,
ConnectToServerPurposeRegister
};
typedefvoid(^LoginResult)(NSString*loginResult);
typedefvoid(^RegisterResult)(NSString*RegisterResult);
typedefvoid(^Refresh)();
@interface XMPPManager : NSObject<XMPPStreamDelegate,XMPPRosterDelegate,XMPPRosterMemoryStorageDelegate,XMPPReconnectDelegate,XMPPvCardTempModuleDelegate>
@property(nonatomic,strong)TabBarViewController *tabbarVC;
@property (nonatomic,copy)LoginResult loginResult;
@property (nonatomic,copy)RegisterResult registerResult;
@property (nonatomic,strong)NSString *password;
//将枚举设成属性
@property (nonatomic,assign)ConnectToServerPurpose connectToServerPurpose;
//通信通道,用于数据传输,要引入XMPPFramework.h头文件
@property (nonatomic, strong) XMPPStream *xmpp_stream;
//花名册,用于获取好友
@property (nonatomic,strong)XMPPRoster *xmppRoster;
@property (nonatomic,strong)XMPPvCardTempModule *xmppvCardTempModule;
@property (nonatomic,strong)XMPPReconnect *xmppReconnect;
@property (nonatomic,strong)XMPPAutoPing *xmppAutoPing;
//好友列表数组
@property (nonatomic,strong)NSMutableArray *friendArr;
//刷新列表block
@property (nonatomic,copy)Refresh refresh;
//信息归档对象
@property (nonatomic,strong)XMPPMessageArchiving *xmppMessageArchiving;
//数据管理器(coredata)
@property (nonatomic,strong)NSManagedObjectContext *contextXMPP;
//单例
+ (XMPPManager*)shareInstance;
//登录
- (void)loginWithUserID:(NSString*)userIDwithPassword:(NSString*)passwordwithLoginResult:(LoginResult)loginResult;
//注册
- (void)registerWithUserID:(NSString*)userIDwithPassword:(NSString*)passwordwithRegisterResult:(RegisterResult)registerResult;
//添加好友
- (void)addFriendActionWithFriendName:(NSString *)friendName;
//上线
- (void)goOnline;
//离线
- (void)offline;
//修改密码
- (void)changePasswordNewPassword:(NSString*)newPassword;
XMPPManager.m
@implementation XMPPManager
//XMPP相关信息
#define kHostName @"www.----.com"
#define kHostPort 5222
#define kDomin @"www.----.com"
#define kResource @"Smack"
#define CHATTYPE @"chat" //消息类型
+ (XMPPManager*)shareInstance {
staticXMPPManager*manager =nil;
staticdispatch_once_tonceToken;
dispatch_once(&onceToken, ^{
manager = [[XMPPManageralloc]init];
});
returnmanager;
}
//懒加载好友列表
- (NSMutableArray *)friendArr {
if(_friendArr==nil) {
self.friendArr= [NSMutableArrayarray];
}
return _friendArr;
}
- (instancetype)init
{
self= [superinit];
if(self) {
//初始化通信通道
self.xmpp_stream = [[XMPPStream alloc]init];
//给通信通道设置服务器IP地址
self.xmpp_stream.hostName =kHostName;
//给通信通道设置端口
self.xmpp_stream.hostPort =kHostPort;
self.xmpp_stream.enableBackgroundingOnSocket =YES;
//给通信通道设置代理
[self.xmpp_stream addDelegate:selfdelegateQueue:dispatch_get_main_queue()];
//创建花名册数据存储对象
XMPPRosterCoreDataStorage *xmppRosterStorage = [XMPPRosterCoreDataStorage sharedInstance];
//创建花名册并指定了存储对象
self.xmppRoster = [[XMPPRoster alloc]initWithRosterStorage:xmppRosterStorage dispatchQueue:dispatch_get_main_queue()];
//激活通信通道
[self.xmppRoster activate:self.xmpp_stream];
//添加代理
[self.xmppRoster addDelegate:selfdelegateQueue:dispatch_get_main_queue()];
//添加自动重连接
[self xmppManageAutoPing];
//单例,信息归档存储对象
XMPPMessageArchivingCoreDataStorage *xmppMessageArchivingCoreDataStorage= [XMPPMessageArchivingCoreDataStorage sharedInstance];
//创建信息归档对象
self.xmppMessageArchiving = [[XMPPMessageArchiving alloc]initWithMessageArchivingStorage:xmppMessageArchivingCoreDataStorage dispatchQueue:dispatch_get_main_queue()];
//激活通信通道
[self.xmppMessageArchiving activate:self.xmpp_stream];
//创建数据管理器
self.contextXMPP = xmppMessageArchivingCoreDataStorage.mainThreadManagedObjectContext;
}
return self;
}
-(void)xmppManageAutoPing{
//1.autoPing 发送的时一个stream:ping 对方如果想表示自己是活跃的,应该返回一个pong
self.xmppAutoPing = [[XMPPAutoPing alloc] init];
//所有的Module模块,都要激活active
[self.xmppAutoPing activate:self.xmpp_stream];
//autoPing由于他回定时发送ping,要求对方返回pong,因此这个时间我们需要设置
[self.xmppAutoPing setPingInterval:1000];
//不仅仅是服务器来得响应;如果是普通的用户,一样会响应
[self.xmppAutoPing setRespondsToQueries:YES];
//这个过程是C---->S ;观察 S--->C(需要在服务器设置)
//2.autoReconnect 自动重连,当我们被断开了,自动重新连接上去,并且将上一次的信息自动加上去
self.xmppReconnect = [[XMPPReconnect alloc] init];
[self.xmppReconnect activate:self.xmpp_stream];
[self.xmppReconnect setAutoReconnect:YES];
[self.xmppReconnect addDelegate:selfdelegateQueue:dispatch_get_main_queue()];
}
// 重连机制的代理方法
-(BOOL)xmppReconnect:(XMPPReconnect *)sendershouldAttemptAutoReconnect:(SCNetworkConnectionFlags)connectionFlags{
DBLog(@"开始尝试自动连接:%u", connectionFlags);
return YES;
}
-(void)xmppReconnect:(XMPPReconnect *)senderdidDetectAccidentalDisconnect:(SCNetworkConnectionFlags)connectionFlags{
DBLog(@"检测到意外断开连接:%u",connectionFlags);
}
#pragma mark- 连接服务器 -
//与服务器链接是一个长连接
- (void)connectToServerWithUserID:(NSString *)userID{
//创建一个JID对象。每个JID对象代表的就是一个封装好的用户
XMPPJID *jidItem = [XMPPJID jidWithUser:userID domain:kDominresource:kResource];
//设置通信通道的JID
self.xmpp_stream.myJID = jidItem;
//如果通信通道正在连接或者已经连接
if([self.xmpp_stream isConnecting] || [self.xmpp_stream isConnected]) {
//1.发送下线状态
XMPPPresence *presence = [XMPPPresence presenceWithType:@"unavailable"];
[self.xmpp_stream sendElement:presence];
//2.断开连接
[self.xmpp_stream disconnect];
}
//向服务器发送请求。连接成功会走代理方法
NSError*error =nil;
[self.xmpp_stream connectWithTimeout:-1error:&error];
if(error !=nil) {
DBLog(@"连接错误");
}
}
#pragma mark - xmppStream的代理方法 -
//连接成功
- (void)xmppStreamDidConnect:(XMPPStream *)sender {
DBLog(@"连接成功");
if (self.connectToServerPurpose == ConnectToServerPurposeLogin) {
//连接成功。就可以进行登录操作了,验证账号和密码是否匹配
[self.xmpp_stream authenticateWithPassword:self.password error:nil];
}
if (self.connectToServerPurpose == ConnectToServerPurposeRegister) {
//连接成功。就可以进行注册操作了,
[self.xmpp_stream registerWithPassword:self.password error:nil];
}
}
//连接超时
- (void)xmppStreamConnectDidTimeout:(XMPPStream *)sender {
DBLog(@"连接超时");
}
#pragma mark- 验证的代理方法 -
//已经验证
- (void)xmppStreamDidAuthenticate:(XMPPStream *)sender {
DBLog(@"验证成功");
//发送一个上线状态
XMPPPresence *presence = [XMPPPresence presenceWithType:@"available"];
[self.xmpp_stream sendElement:presence];
self.loginResult(@"loginSuccess");
}
//验证失败
- (void)xmppStream:(XMPPStream *)senderdidNotAuthenticate:(DDXMLElement *)error {
DBLog(@"验证失败");
DBLog(@"%@",error);
self.loginResult(@"loginFail");
}
#pragma mark- 注册结果的代理方法 -
//注册成功了
- (void)xmppStreamDidRegister:(XMPPStream *)sender{
DBLog(@"注册成功");
self.registerResult(@"registerSuccess");
}
//注册失败了
- (void)xmppStream:(XMPPStream *)senderdidNotRegister:(DDXMLElement *)error{
DBLog(@"注册失败了");
self.registerResult(@"registerFail");
}
//登录方法
- (void)loginWithUserID:(NSString*)userIDwithPassword:(NSString*)passwordwithLoginResult:(LoginResult)loginResult {
self.connectToServerPurpose = ConnectToServerPurposeLogin;
self.loginResult= loginResult;
self.password= password;
//连接服务器
[self connectToServerWithUserID:userID];
self.friendArr=nil;
}
//注册方法
- (void)registerWithUserID:(NSString*)userIDwithPassword:(NSString*)passwordwithRegisterResult:(RegisterResult)registerResult {
self.connectToServerPurpose = ConnectToServerPurposeRegister;
self.password= password;
self.registerResult= registerResult;
//连接服务器
[self connectToServerWithUserID:userID];
}
#pragma mark- 花名册代理方法 -
//开始检索
- (void)xmppRosterDidBeginPopulating:(XMPPRoster *)sender{
DBLog(@"开始检索了");
[self.friendArr removeAllObjects];
XMPPJID *jiddata = [XMPPJID jidWithString:@""];
XMPPJID *jiddata1 = [XMPPJID jidWithString:@""resource:@""];
XMPPJID *jiddata2 = [XMPPJID jidWithUser:@""domain:@""resource:@""];
}
//结束检索
- (void)xmppRosterDidEndPopulating:(XMPPRoster *)sender{
DBLog(@"结束检索");
DBLog(@"%ld",self.friendArr.count);
}
// 添加好友同意后,会进入到此代理
- (void)xmppRoster:(XMPPRoster *)senderdidReceiveRosterPush:(XMPPIQ *)iq{
DBLog(@"didReceiveRosterPush");
DBLog(@"添加成功!!!didReceiveRosterPush -> :%@",iq.description);
DDXMLElement *query = [iq elementsForName:@"query"][0];
DDXMLElement *item = [query elementsForName:@"item"][0];
NSString*subscription = [[item attributeForName:@"subscription"] stringValue];
// 对方请求添加我为好友且我已同意
if([subscriptionisEqualToString:@"from"]) {// 对方关注我
DBLog(@"我已同意对方添加我为好友的请求");
}
// 我成功添加对方为好友
elseif([subscriptionisEqualToString:@"to"]) {// 我关注对方
DBLog(@"我成功添加对方为好友,即对方已经同意我添加好友的请求");
}elseif([subscriptionisEqualToString:@"remove"]) {
// 删除好友
DBLog(@"删除好友");
[selfloginWithUserID:[GVUserDefaults standardUserDefaults].Name withPassword:[GVUserDefaults standardUserDefaults].password withLoginResult:^(NSString *loginResult) {
if([loginResult isEqualToString:@"loginSuccess"]) {
DBLog(@"登录成功");
DBLog(@"%@",self.friendArr);
}else{
DBLog(@"登录失败");
}
}];
}
}
- (void)xmppRosterDidBeginPopulating:(XMPPRoster *)senderwithVersion:(NSString*)version{
DBLog(@"xmppRosterDidBeginPopulating");
}
- (void)xmppRoster:(XMPPRoster *)senderdidReceiveRosterItem:(NSXMLElement *)item{
DBLog(@"didReceiveRosterItem");
NSString*subscriptionValue = [[item attributeForName:@"subscription"]stringValue];
// 展示互为好友的信息
if([subscriptionValueisEqualToString:@"both"]) {
NSString*jidStr = [[item attributeForName:@"jid"]stringValue];
// 封装成XMPPJID
XMPPJID *friendJID = [XMPPJID jidWithString:jidStr];
// 如果数组中已经包含了这个好友对象,就不再进行添加了
if([self.friendArr containsObject:friendJID]) {
return;
}
// 添加到数据源数组中
[self.friendArr addObject:friendJID];
}
DBLog(@"%ld",self.friendArr.count);
}
#pragma mark- 添加好友 -
- (void)addFriendActionWithFriendName:(NSString *)friendName {
//把账号封装成JID对象
XMPPJID *addJID = [XMPPJID jidWithUser:friendName domain:kDominresource:kResource];
//发送好友请求
[self.xmppRoster addUser:addJID withNickname:nil];
// [self.xmppRoster subscribePresenceToUser:addJID];
}
#pragma mark- 接受到好友请求 -
- (void)xmppRoster:(XMPPRoster *)senderdidReceivePresenceSubscriptionRequest:(XMPPPresence *)presence{
DBLog(@"接受到好友请求");
DBLog(@"%@",presence);
DBLog(@"%@",presence.from);
}
// 如果不是初始化同步来的roster,那么会自动存入我的好友存储器
- (void)xmppRosterDidChange:(XMPPRosterMemoryStorage *)sender
{
DBLog(@"xmppRosterDidChange");
}
//收到对方取消定阅我得消息
- (void)xmppStream:(XMPPStream *)senderdidReceivePresence:(XMPPPresence *)presence
{
if([presence.type isEqualToString:@"unsubscribe"]) {
//从我的本地通讯录中将他移除
[self.xmppRoster removeUser:presence.from];
//将此好友从会话列表中移除
ReplyModel *model = [[ReplyModel alloc]init];
model.name = presence.from.user;
[[DBManager sharedInstance] deleteReply:model];
if(self.refresh) {
self.refresh();
}
}
}
#pragma mark - 接受了消息的代理方法 -
//接收了信息
- (void)xmppStream:(XMPPStream *)senderdidReceiveMessage:(XMPPMessage *)message{
DBLog(@"XMPPManager:接受了信息");
}
//上线
- (void)goOnline
{
// 发送一个 默认值avaliable 在线 服务器收到空的presence 也会认为是avaliable
// status ---自定义的内容,可以是任何的。
// show 是固定的,有几种类型 dnd、xa、away、chat,在方法XMPPPresence 的intShow中可以看到
XMPPPresence *presence = [XMPPPresence presence];
[presence addChild:[DDXMLNode elementWithName:@"status"stringValue:@"我上线咯"]];
[presence addChild:[DDXMLNode elementWithName:@"show"stringValue:@"xa"]];
[_xmpp_stream sendElement:presence];
}
//离线
- (void)offline
{
XMPPPresence *off = [XMPPPresence presenceWithType:@"unavailable"];
[_xmpp_stream sendElement:off];
//断开连接
[_xmpp_stream disconnect];
}
//删除好友
-(void)removeFriend:(XMPPJID*)jid{
XMPPPresence *presence = [XMPPPresence presenceWithType:@"unsubscribe"to:[jid bareJID]];
[presence addAttributeWithName:@"id"stringValue:jid.user];
[_xmpp_stream sendElement:presence];
}
//修改用户密码
-(void)changePasswordNewPassword:(NSString*)newPassword{
/*
bill
newpass
*/
NSXMLElement *iq = [NSXMLElement elementWithName:@"iq"];
[iq addAttributeWithName:@"type"stringValue:@"set"];
[iq addAttributeWithName:@"to"stringValue:@"ubuntu-dev"];
[iq addAttributeWithName:@"id"stringValue:@"change1"]; //@"change1"确定id唯一就行
NSXMLElement *queryElement = [NSXMLElement elementWithName:@"query"xmlns:@"jabber:iq:register"];
[iq addChild:queryElement];
NSString*userNameStr = [GVUserDefaults standardUserDefaults].Name;
NSXMLElement *username = [NSXMLElement elementWithName:@"username"];
[username setStringValue:userNameStr];
NSXMLElement *password = [NSXMLElement elementWithName:@"password"];
NSLog(@"newPassword_______:%@",newPassword);
[password setStringValue:newPassword];
[queryElement addChild:username];
[queryElement addChild:password];
//NSLog(@"iq__%@",iq.description);//打印下XML流检查是否拼接正确
[[XMPPManager shareInstance].xmpp_stream
sendElement:iq];//发送{XMPPStream被我封装进单例类XMPPService里}
// [[XMPPManager shareInstance].xmpp_stream addDelegate:self delegateQueue:dispatch_get_main_queue()];//设置代理
}
- (BOOL)xmppStream:(XMPPStream *)senderdidReceiveIQ:(XMPPIQ *)iq{
// NSLog(@"__%@",iq.description);
// <iq xmlns="jabber:client" type="result" id="change1" from="ubuntu-dev" to="13333333333@ubuntu-dev/870efdcd"></iq>
NSString*iqTypePWD = [[iq attributeForName:@"type"]stringValue];
NSString*iqIDPWD = [[iq attributeForName:@"id"]stringValue];
// NSLog(@"iqTypePWD:%@___iqTypePWD:%@",iqTypePWD,iqIDPWD);
if ([iqTypePWD isEqualToString:@"result"] && [iqIDPWD isEqualToString:@"change1"]) { //进行判断只有type="result" id="change1"时,密码修改成功
NSLog(@"XMPP密码修改成功!");
}
return YES;
}
在这个 demo 中
可以看出, xmppStream 初始化分三个步骤
1、初始化(创建流对象)
2、设置服务器 IP和端口号(告诉流往哪里连接)
3、添加代理(成功与否通过代理回调)
· 创建流对象成功后,发起连接之前,你还需要将 jid 绑定到流上,这样才算是沟通用户和服务器
· 但是无论账号填写什么,甚至是 nil,流也是会连接成功的,因为流连接只需要域名和端口号即可
· 那登录成功的密码验证呢? 则是在流连接成功后, 通过流发送验证请求, 如果验证通过,这通过代理回调, 在方法内部再调用 block, 实现C端(控制器端)的操作
· 在控制器端完成 block 的回调
消息发送
XMPPMessage *message = [XMPPMessage messageWithType:CHATTYPE to:self.friendJID];
[message addBody:@"发送的消息"];
[[XMPPManager shareInstance].xmpp_stream sendElement:message];