demo https://github.com/gwh111/testxmpp
xmpp介绍
中文官方http://wiki.jabbercn.org/%E9%A6%96%E9%A1%B5
ios库下载https://github.com/robbiehanson/XMPPFramework
重连问题https://www.jianshu.com/p/d9de0267c52a
推荐使用pod
pod 'XMPPFramework', '~> 3.7.0'
导入
import XMPPFramework // swift
@import XMPPFramework; //objective-c
Module 'XMPPFramework' not found
XEP-0198: Stream Management
Acks
Acks 重连 启动流管理
启动流管理(stream management)之后,客户端或者服务端可以在任意时间通过流发送ack元素。ack元素可以是:<a/>元素用于回答一个确认收到的请求或者发送一个未被请求的ack。
<r/>元素用于请求收到节(stanzas)的确认信息。
属性定义如下:
'h'属性标识最后处理的节(即,服务器确认收到最后一节)。
一个<a/>元素必须具备一个”h“属性。
<r/>元素没有已定义属性。
定义:确认一条之前收到的ack元素,节是被服务器处理后再发送出去的。对于服务端的处理,我们是指服务端接管一条或多条节(如,直接处理节,将节传给本地的一个实体,比如相同服务器下的另一个客户端,或者将节发送给其他服务器的远程实体);一个节在确认被服务端处理之前,发送方都要对节负责(例如,如果没有被服务端确认处理,客户端要重发这个节或者生成一个错误)。
收到一个<r/>元素的回执并不意味着新的节已经传送给对方,只有收到一个<a/>元素的回执,并且包含“h”属性已经增长了,才说明这个新的节已经被处理过。
“h”值在流管理中被启动或被请求启动时,是从零开始的。当第一个节被处理的时候,“h”值增加为1,并随着接下来新的节被处理,“h”值不断增加。在一些极端情况下,在流管理对话中被处理的节的数量,超出了无符号整型数所表示的最大数(2的32次方)XML Schema Part 2 [10],“h”值应该被置为0,而不是2的32次方。
注意:任何给定的流,都有两个“h”值,一个被客户端用于跟踪节是否被服务端处理,另一个被服务端用于跟踪节是否被处理自客户端。当客户端向服务端发送<enable/>时,客户端初始化它的“h”值为零,同样服务端发送<enable/>给客户端时,也初始化它的“h”值为零(服务端会立即回复<enable/>,同时设置它的计数器为零)。初始化后,客户端根据处理的节的数目不断增加“h”值,服务端也相应不断增加“h”值。
- The <a/> element is used to answer a request for acknowledgement or to send an unrequested ack.
- The <r/> element is used to request acknowledgement of received stanzas
- The 'h' attribute identifies the last handled stanza (i.e., the last stanza that the server will acknowledge as having received).
Resumption
参考https://blog.youkuaiyun.com/yuedong56/article/details/38120101
h文件
#import <Foundation/Foundation.h>
@interface XMPPHelper : NSObject
//@property(nonatomic,assign) BOOL isConnecting;
+ (instancetype)getInstance;
- (BOOL)initWithUserName:(NSString *)userName andPassword:(NSString *)password andHostName:(NSString *)hostName andDomain:(NSString*)domain andHostPort:(UInt16)hostPort andInfoDic:(NSDictionary *)infoDic;
- (BOOL)connect;
- (void)disconnect;
- (BOOL)isConnected;
@end
初始化参数说明
userName 用户名
password 密码
hostName 主机名 如10.5.190.1
domain 域名 如qpmjcore-98-1/smack
hostPort 端口号 如5222
类说明
@property(nonatomic,retain) XMPPJID *jid;
@property(nonatomic,retain) XMPPStream *xmppStream;
@property(nonatomic,retain) XMPPStreamManagement *xmppStreamManagement;
@property(nonatomic,retain) XMPPStreamManagementMemoryStorage *storage;
@property(nonatomic,retain) XMPPReconnect *xmppReconnect;
XMPPJID 根据初始化数据构造的发送地址
XMPPStream 输入输出流
XMPPStreamManagement 输入输出流的管理
XMPPStreamManagementMemoryStorage 数据永久存储
XMPPReconnect 重连模块
接收消息和发送消息通过
- (void)xmppStream:(XMPPStream *)sender didReceiveMessage:(XMPPMessage *)message{
NSString *messageBody = [[message elementForName:@"body"] stringValue];
// [ShareCallback xmppPushMsg:messageBody];
// int su=[_xmppStream supportsStreamManagement];
// [self sendMessage:@"aaa" to:_jid];
//
// NSXMLElement *enable = [NSXMLElement elementWithName:@"r" xmlns:@"urn:xmpp:sm:3"];
// [[self xmppStream] sendElement:enable];
}
- (void)sendMessage:(NSString *)message to:(XMPPJID *)jid
{
XMPPMessage* newMessage = [[XMPPMessage alloc] initWithType:@"chat" to:jid];
[newMessage addBody:message]; //消息内容
[_xmppStream sendElement:newMessage];
}
- (void)xmppStream:(XMPPStream *)sender didSendMessage:(XMPPMessage *)message{
}
在建立连接,验证通过后开启流管理enableStreamManagementWithResumption
这里有一个XEP-0198协议 https://xmpp.org/extensions/xep-0198.html
If YES, the resume attribute will be included. E.g. <enable resume='true'/>
在客户端就是通过发送一个element来开启,开启后在每接收到一定消息后会通过上面提到的Acks /r和/a的标签来验证消息
可以通过
- (void)xmppStream:(XMPPStream *)sender didSendCustomElement:(NSXMLElement *)element{
NSLog(@"didSendCustomElement%@",element);
}
- (void)xmppStream:(XMPPStream *)sender didReceiveCustomElement:(NSXMLElement *)element{
NSLog(@"didReceiveCustomElement%@",element);
}
来查看/r和/a的标签
demo https://github.com/gwh111/testxmpp
更新:
xmpp断线重连模块
//接入断线重连模块
_xmppReconnect = [[XMPPReconnect alloc] init];
_xmppReconnect.reconnectTimerInterval=5;
_xmppReconnect.reconnectDelay=0;
[_xmppReconnect setAutoReconnect:YES];
[_xmppReconnect activate:self.xmppStream];
[_xmppReconnect addDelegate:self delegateQueue:dispatch_get_global_queue(0, 0)];
#pragma mark - XMPPReconnectDelegate
- (void)xmppReconnect:(XMPPReconnect *)sender didDetectAccidentalDisconnect:(SCNetworkConnectionFlags)connectionFlags {
NSLog(@"xmpp意外断开连接。");
[ShareCallback xmppCallback:@"disconnect"];
[self disconnect];
}
- (BOOL)xmppReconnect:(XMPPReconnect *)sender shouldAttemptAutoReconnect:(SCNetworkConnectionFlags)connectionFlags {
return YES;
}
xmpp顶号 在另一台设备登录
- (void)xmppStream:(XMPPStream *)sender didReceiveError:(id)error
{
NSLog(@"收到错误消息%@",error);
if ([error isKindOfClass:[DDXMLElement class]]) {
DDXMLElement *er=error;
if ([er.description containsString:@"conflict"]) {
[ShareCallback xmppCallback:@"resign"];
[self disconnect];
return;
}
}
[ShareCallback xmppCallback:@"fail"];
//<stream:error xmlns:stream="http://etherx.jabber.org/streams"><conflict xmlns="urn:ietf:params:xml:ns:xmpp-streams"/></stream:error>
// [ShareCallback xmppPushError:error];
}
xmpp的ping
XMPPAutoPing *xmppAutoPing = [[XMPPAutoPing alloc] init];
xmppAutoPing.pingInterval = 10.0;
[xmppAutoPing activate:_xmppStream];
[xmppAutoPing addDelegate:self delegateQueue:dispatch_get_global_queue(0, 0)];
#pragma mark- XMPPAutoPingDelegate
- (void)xmppAutoPingDidReceivePong:(XMPPAutoPing *)sender{
}
- (void)xmppAutoPingDidTimeout:(XMPPAutoPing *)sender {
[ShareCallback xmppCallback:@"fail"];
[self disconnect];
}