刚好最近在做一个项目,里面有用到XMPP通讯协议,其实相较于其他通讯协议,XMPP比较厚重,不适合以即时通讯为主的APP,只适用于小型,用户量少的APP。
下面简单讲一下XMPP的实现过程:
首页,导入XMPPFramework这个库,如果是cocoapods导入的话,记得在pod‘XMPPFramework’库之前加一句,use_frameworks!,这样就不会有import报错的情况
然后,创建一个XMPPSocket的单例
接着就是XMPP的相关操作
1:创建XMPPStream流
2:定时发送心跳包XMPPAutoPing,心跳包的作用就是告诉服务器客户端还在连接中
3:重连机制,在Scoket断开连接后,立刻去重新连接,同时也要检测当前网络状况,如果是弱网或者是断网的情况下,就不用去响应重新连接的方法,网络正常的话就要去重新连接,给一个int,重连int++,int超过十次就断开连接,当然你也可以选择一直练,但是这样是不建议的,
4:流管理,
XMPPStreamManagementMemoryStorage和XMPPStreamManagement
接入流管理模块,是用于流恢复和消息确认
5:好友模块,XMPPRoster,获取好友列表,添加好友,获取好友节点,获取好友当前状态(上线,离开,忙碌,下线)
6:消息模块,XMPPMessageArchivingCoreDataStorage,用于本地储存消息,服务器只是一个中介,只会存储离线消息,所以消息记录还是需要我们来做本地数据库的缓存,当然我们最常用的就是FMDB框架,所以你可以根据需要自己去封装一个工具类
到这里,常用的一些类都已经提到,接下来就要写一些常用方法,例如登录,发送消息,添加好友,退出登录这样的方法,还有一个更重要的就是,写一个代理方法,收到消息就执行这个代理,可以在项目里任意地方需要更新UI的地方去遵循这个代理。
这样XMPPSocket的工具类就写的差不多了,当然如果你有更多的需要也可以慢慢添加。
#define DO_MAIN @"timeremind.cn"
@interfaceXMPPSocketMannager()
{
/** 重新登录次数,初始化为0 */
NSIntegerreloginCount;
/** 重连次数,初始化为0 */
NSIntegerreconnectCount;
/** ping超时次数,初始化为0 */
NSIntegerpingTimeoutCount;
/** 收消息的队列,是一个串行队列 */
dispatch_queue_t_streamQueue;
}
//没有网络的时候检测网络定时器
@property(nonatomic,strong)NSTimer*netWorkTestingTimer;
@end
@implementationXMPPSocketMannager
#pragma mark -私有方法
#pragma mark 初始化XMPPStream
static XMPPSocketMannager *sInstance;
// 单例
+ (instancetype) sharedManager {
staticdispatch_once_tonceToken;
dispatch_once(&onceToken, ^{
sInstance = [XMPPSocketMannager new];
// 设置日志打印
[DDLog addLogger:[DDTTYLogger sharedInstance]];
});
return sInstance;
}
// 懒加载XMPPStream
- (XMPPStream*)xmppStream {
if(nil==_xmppStream) {
// 创建
_xmppStream = [[XMPPStream alloc] init];
// 设置属性
// ip
_xmppStream.hostName=DO_MAIN;
// 端口
//XMPPStreamStartTLSPolicyRequired
//XMPPStreamStartTLSPolicyAllowed
_xmppStream.hostPort=5222;
_xmppStream.startTLSPolicy = XMPPStreamStartTLSPolicyAllowed;
// 设置代理--多播代理
[_xmppStream addDelegate:self delegateQueue:dispatch_get_main_queue()];
//心跳包
_xmppAutoPing = [[XMPPAutoPing alloc] init];
_xmppAutoPing.pingInterval = 10.f; // 心跳包间隔
[_xmppAutoPing activate:_xmppStream];
[_xmppAutoPing addDelegate:self delegateQueue:dispatch_get_global_queue(0, 0)];
//重连机制
_xmppReconnect = [[XMPPReconnect alloc] init];
_xmppReconnect.autoReconnect = YES;
_xmppReconnect.reconnectDelay = 0.f;// 一旦失去连接,立马开始自动重连,不延迟
_xmppReconnect.reconnectTimerInterval = 3.f;// 每隔3秒自动重连一次
[_xmppReconnect activate:_xmppStream];
[_xmppReconnect addDelegate:self delegateQueue:dispatch_get_global_queue(0, 0)];
//接入流管理模块,用于流恢复跟消息确认
_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];
}
return _xmppStream;
}
// 登陆
- (void)loginWithName:(NSString*)name andPassword:(NSString*)password {
// 创建一个JID
XMPPJID *jid = [XMPPJID jidWithUser:name domain:DO_MAIN resource:@"eyetouch"];
// 设置MyJID
[self.xmppStreamsetMyJID:jid];
// 设置密码
self.password= password;
// 连接到服务器
[self connectServer];
}
// 连接到服务器
- (void)connectServer {
// 异常信息
NSError*error =nil;
// 连接服务器
[self.xmppStream connectWithTimeout:15.0f error:&error];
// 判断是否连接成功
if(error) {
XPLog(@"登录失败,这里可以实现掉线自动重连,要注意以下几点");
XPLog(@"1.判断当前网络环境,如果断网了就不要连了,等待网络到来,在发起重连");
XPLog(@"3.连接次数限制,如果连接失败了,重试10次左右就可以了");
//判断网络环境
if (AFNetworkReachabilityManager.sharedManager.networkReachabilityStatus == AFNetworkReachabilityStatusNotReachable){
//没有网络
//开启网络检测定时器
[self noNetWorkStartTestingTimer];
}else{
//有网络
//连接失败就重连
[self reconnectImmediately];
}
}
}
//这里是因为服务器用的是自签证书,并且需要我们加密登录,也就是在服务端那里能看见一个锁的图标
- (void)xmppStream:(XMPPStream*)sender willSecureWithSettings:(NSMutableDictionary*)settings
{
settings[GCDAsyncSocketManuallyEvaluateTrust] = @(YES);
[settingssetObject:self.xmppStream.myJID.domain forKey:(NSString *)kCFStreamSSLPeerName];
}
- (void)xmppStream:(XMPPStream*)sender
didReceiveTrust:(SecTrustRef)trust
completionHandler:(void(^)(BOOLshouldTrustPeer))completionHandler
{
completionHandler(YES);
}
#pragma mark 代理方法,连接成功之后回调
- (void)xmppStreamDidConnect:(XMPPStream*)sender {
XPLog(@"🍎xmpp连接服务器成功");
// 错误
NSError*error =nil;
// 登陆
[self.xmppStream authenticateWithPassword:self.password error:&error];
}
#pragma mark 连接失败
-(void)xmppStreamDidDisconnect:(XMPPStream*)sender withError:(NSError*)error
{
XPLog(@"🍎xmpp连接服务器失败,失败原因:%@",error);
}
- (void)xmppStreamConnectDidTimeout:(XMPPStream*)sender
{
XPLog(@"🍎xmpp连接服务器超时");
[self reconnectImmediately];
}
//认证失败后的回调
-(void)xmppStream:sender didNotAuthenticate:(DDXMLElement*)error
{
XPLog(@"🍎xmpp认证失败,用户名或密码错误:%@",error);
}
#pragma mark 代理方法,登陆成功之后告诉服务器我要出席
//认证成功后的回调
- (void)xmppStreamDidAuthenticate:(XMPPStream*)sender {
XPLog(@"🍎xmpp认证成功");
//如果成功,可以出席
XMPPPresence *presence = [XMPPPresence presence];
//添加出席状态
[presenceaddChild:[DDXMLElement elementWithName:@"status" stringValue:@"在线"]];
[presenceaddChild:[DDXMLElement elementWithName:@"priority" stringValue:@"1"]];
//告诉服务器我要出席
[self.xmppStreamsendElement:presence];
}
#pragma mark - 心跳包
#pragma mark - XMPPAutoPingDelegate
- (void)xmppAutoPingDidReceivePong:(XMPPAutoPing*)sender{
XPLog(@"🍎xmpp接收心跳:%@",sender);
// 如果至少有1次超时了,再收到pong包,则清除超时次数
if (pingTimeoutCount > 0) {
pingTimeoutCount = 0;
}
}
- (void)xmppAutoPingDidTimeout:(XMPPAutoPing*)sender {
// 收到两次超时,就disconnect吧
pingTimeoutCount++;
if (pingTimeoutCount >= 2) {
[selflogout];
}
}
- (void)xmppAutoPingDidSendPing:(XMPPAutoPing*)sender
{
XPLog(@"🍎xmpp发送心跳:%@",sender);
}
#pragma mark - XMPPReconnectDelegate
- (void)xmppReconnect:(XMPPReconnect*)sender didDetectAccidentalDisconnect:(SCNetworkConnectionFlags)connectionFlags {
XPLog(@"%u",connectionFlags);
XPLog(@"🍎xmpp意外断开连接。");
[self reconnectImmediately];
}
- (BOOL)xmppReconnect:(XMPPReconnect*)sender shouldAttemptAutoReconnect:(SCNetworkConnectionFlags)connectionFlags {
reconnectCount++;
XPLog(@"🍎xmpp自动重连...第%@次",@(reconnectCount));
if (reconnectCount >10) {
[self.xmppReconnectstop];
}
return YES;
}
#pragma mark - 重连
- (void)reconnectImmediately {
self.xmppReconnect.reconnectTimerInterval = 3.f;
reconnectCount = 0;
[self.xmppReconnect stop];
[self.xmppReconnect manualStart];
}
#pragma mark - 下线操作
- (void)xmppStream:(XMPPStream*)sender didReceiveError:(NSXMLElement*)error {
//
NSString *conflict = [[error elementForName:@"conflict"] stringValue];
if(conflict) {
//下线操作
[self.xmppStreamdisconnect];
[SVProgressHUD showSuccessWithStatus:@"您已下线,请确认账户安全"];
//跳转登录页
[AcountItem removesid];
AppDelegate *application = (AppDelegate *)[UIApplication sharedApplication].delegate;
[applicationpushToLoginVC];
}else{
//其他原因连接失败随即重连
[self reconnectImmediately];
}
}
// 退出登陆
- (void) logout {
// 用户状态类
XMPPPresence *presence = [XMPPPresence presenceWithType:@"unavailable"];
// 向服务器发送离线通知
[self.xmppStreamsendElement:presence];
// 断开连接
[self.xmppStream disconnect];
}
/*
接收消息
*/
- (void)xmppStream:(XMPPStream*)sender didReceiveMessage:(XMPPMessage*)message
{
XPLog(@"🍎xmpp接收消息:%@",message);
//消息处理
dispatch_async(dispatch_get_main_queue(), ^{
[MessageSocketMannager MessageChangeType:message];
});
if([self.delegaterespondsToSelector:@selector(XMPPSocketMannagerDidReceiveMessageWithString:)]) {
[self.delegate XMPPSocketMannagerDidReceiveMessageWithString:message];
}
}
/*
发送消息
*/
- (void)sendMessage:(NSString*)message toUser:(NSString*)user WithType:(NSString*)type{
NSDictionary *dict=@{@"content":message,@"sendTime":@([[FDefultTool GetNowTimes] longLongValue]),@"type":@([type integerValue]),@"headImage":[AcountItem getuseheadImgUrl],@"UserName":[AcountItem getuserName]};
XMPPJID *jid = [XMPPJID jidWithUser:user domain:DO_MAIN resource:@"eyetouch"];
XMPPMessage * messagedata=[XMPPMessage messageWithType:@"chat" to:jid];
[messagedataaddBody:[FDefultToolconvertToJsonData:dict]];
//没有网络
if (AFNetworkReachabilityManager.sharedManager.networkReachabilityStatus == AFNetworkReachabilityStatusNotReachable)
{
//开启网络检测定时器
[self noNetWorkStartTestingTimer];
}
else//有网络
{
if(self.xmppStream!=nil)
{
// 只有长连接OPEN开启状态才能调 send 方法,不然会Crash
if([self.xmppStreamisConnected])
{
[self.xmppStreamsendElement:messagedata];
}
elseif([self.xmppStreamisConnecting])//正在连接
{
[SVProgressHUD showInfoWithStatus:@"在连接服务器中,重连后会去自动同步数据"];
}
elseif([self.xmppStreamisConnecting] || [self.xmppStreamisDisconnected])//断开连接
{
//调用 reConnectServer 方法重连,连接成功后 继续发送数据
[selfreconnectImmediately];
}
}
else
{
[selfconnectServer];//连接服务器
}
}
}
//没有网络的时候开始定时 -- 用于网络检测
- (void)noNetWorkStartTestingTimer{
__weak __typeof(self)weakSelf = self;
dispatch_main_async_safe(^{
weakSelf.netWorkTestingTimer = [NSTimer scheduledTimerWithTimeInterval:1.0target:weakSelf selector:@selector(noNetWorkStartTesting) userInfo:nilrepeats:YES];
[[NSRunLoop currentRunLoop] addTimer:weakSelf.netWorkTestingTimer forMode:NSDefaultRunLoopMode];
});
}
//定时检测网络
- (void)noNetWorkStartTesting{
//有网络
if(AFNetworkReachabilityManager.sharedManager.networkReachabilityStatus != AFNetworkReachabilityStatusNotReachable)
{
//关闭网络检测定时器
[self destoryNetWorkStartTesting];
//开始重连
[self reconnectImmediately];
}else{
[selflogout];
}
}
//取消网络检测
- (void)destoryNetWorkStartTesting{
__weak __typeof(self)weakSelf = self;
dispatch_main_async_safe(^{
if(weakSelf.netWorkTestingTimer)
{
[weakSelf.netWorkTestingTimer invalidate];
weakSelf.netWorkTestingTimer =nil;
}
});
}
/*
好友状态
available 上线
away 离开
do not disturb 忙碌
unavailable 下线
*/
- (void)xmppStream:(XMPPStream*)sender didReceivePresence:(XMPPPresence*)presence {
NSString*presenceType = [presencetype];
NSString*presenceFromUser = [[presencefrom]user];
if(![presenceFromUserisEqualToString:[[sendermyJID]user]]) {
if([presenceTypeisEqualToString:@"available"]) {
XPLog(@"上线");
}elseif([presenceTypeisEqualToString:@"away"]) {
XPLog(@"离开");
}elseif([presenceTypeisEqualToString:@"do not disturb"]) {
XPLog(@"忙碌");
}elseif([presenceTypeisEqualToString:@"unavailable"]) {
XPLog(@"下线");
}
}
}
// 收到好友请求执行的方法
-(void)xmppRoster:(XMPPRoster*)sender didReceivePresenceSubscriptionRequest:(XMPPPresence*)presence{
// UIAlertView *alert = [[UIAlertView alloc]initWithTitle:@"提示:有人添加你" message:presence.from.user delegate:self cancelButtonTitle:@"拒绝" otherButtonTitles:@"OK", nil];
// [alert show];
}
#pragma mark 开始检索好友列表的方法
-(void)xmppRosterDidBeginPopulating:(XMPPRoster*)sender{
NSLog(@"🍎xmpp开始检索好友列表");
}
#pragma mark 正在检索好友列表的方法
-(void)xmppRoster:(XMPPRoster*)sender didRecieveRosterItem:(DDXMLElement*)item{
NSMutableArray *array=[[NSMutableArray alloc]init];
XPLog(@"🍎xmpp每一个好友都会走一次这个方法");
//获得item的属性里的jid字符串,再通过它获得jid对象
NSString *jidStr = [[item attributeForName:@"jid"] stringValue];
XMPPJID *jid = [XMPPJID jidWithString:jidStr];
//是否已经添加
if([arraycontainsObject:jid]) {
return;
}
//将好友添加到数组中去
[arrayaddObject:jid];
}
#pragma mark 好友列表检索完毕的方法
-(void)xmppRosterDidEndPopulating:(XMPPRoster*)sender{
XPLog(@"🍎xmpp好友列表检索完毕");
}
#pragma mark ReceiveIQ
- (BOOL)xmppStream:(XMPPStream*)sender didReceiveIQ:(XMPPIQ*)iq
{
NSMutableArray *dataArr=[[NSMutableArray alloc]init];
XPLog(@"%@: %@", THIS_FILE, THIS_METHOD);
if ([@"result" isEqualToString:iq.type]) {
NSXMLElement*query = iq.childElement;
if([@"query"isEqualToString:query.name]) {
NSArray*items = [querychildren];
for(NSXMLElement*iteminitems) {
NSString *jid = [item attributeStringValueForName:@"jid"];
XMPPJID*xmppJID = [XMPPJIDjidWithString:jid];
[dataArraddObject:xmppJID];
}
}
}
if([self.delegaterespondsToSelector:@selector(XMPPSocketMannagerDidFriendWithArr:)]) {
[self.delegate XMPPSocketMannagerDidFriendWithArr:dataArr];
}
return NO;
}
//添加好友
- (void)XMPPAddFriendSubscribe:(NSString*)userName
{
XMPPJID *jid = [XMPPJID jidWithString:[NSString stringWithFormat:@"%@@%@",userName,DO_MAIN]];
[self.xmppRoster subscribePresenceToUser:jid];
}
//删除好友
- (void)XMPPremoveFriendWithName:(NSString*)userName
{
XMPPJID *jid = [XMPPJID jidWithString:[NSString stringWithFormat:@"%@@%@",userName,DO_MAIN]];
// 停止监听好友
[self.xmppRoster unsubscribePresenceFromUser:jid];
[self.xmppRosterremoveUser:jid];
}
暂时就是这些,另外如果有什么不明白可以评论问我
网友评论