https://www.jianshu.com/p/84d15683b61e

https://www.cnblogs.com/lurenq/p/7026983.html

1. xmpp简介

XMPP 是一组基于 XML 的技术。用于实时应用程序。最初。XMPP 作为一个框架开发。目标是支持企业环境内的即时消息传递和联机状态应用程序。详细简介

xmpp客户端:

iOS端:

  • 单聊:xmpp单聊是基于服务器端好友设置来进行聊天的,类似于微信,你可以添加好友,经过同意以后就可以进行聊天。
  • 群聊: xmpp 群聊类似于聊天室的类型,你可以创建一个房间,可以邀请其他人加入这个房间,也可以主动加入群,当房间里面大于等于两个人,就可以进行群聊。不过跟微信不同的是,用户在退出这个聊天,性质就跟退出这个群。如果下次再进入这个群,需要在加入这个群,这样会通过传给服务器的最后一次登录时间,服务器会把这个时间段以后的消息,全部推送给你。
xmpp服务器:

Openfire + Spark 简介:下载地址及简单介绍:
openfire + Spark这边服务器端了解不多,只是知道通过下载这个应用,通过配置来创建好友和创建房间,进行简单的单聊群聊。如果做服务器的话,肯定不止这么少东西,如果要学习的话还是自己查资料吧。 Openfire下载地址

Openfire + Saprk 简单介绍

2. xmpp聊天原理

 
xmpp原理图.png

xmpp是基于xml类型进行消息传递的。所有的消息传递类型都是xmpp类型的。
(1) 发送消息类型:

<message type="chat" to="110095@domain" id="d851f47818db46b58abf4e982327ab36"><request xmlns="urn:xmpp:receipts"></request><body>{
"messageId" : "d851f47818db46b58abf4e982327ab36", //messageid
"content" : "你好", //消息内容
"fromUserName" : "张三", //发送人
"isSecret" : 0, //单聊是否是密聊消息 0:正常聊天 1:密聊
"timeLen" : 0,
"isAt" : 0, //群聊是否@人
"timeSend" : 1522034423, //发送时间
"type" : 1 //消息类型
}</body></message>

(2) 接收消息类型:

<message xmlns="jabber:client" id="cdc6007ca03b401f8c9e4a2ccb75d8d5" type="chat" to="111751@domain" from="109965@domain/resource"><request xmlns="urn:xmpp:receipts"></request><body>{
"messageId" : "cdc6007ca03b401f8c9e4a2ccb75d8d5",
"content" : "天津",
"fromUserName" : "李四",
"isSecret" : 0,
"timeLen" : 0,
"isAt" : 0,
"timeSend" : 1522034878,
"type" : 1
}</body></message>

(3)发送回执消息类型:

<message to="109965@domain/resource" type="chat"><received xmlns="urn:xmpp:receipts" id="cdc6007ca03b401f8c9e4a2ccb75d8d5"/></message>

(4)接收回执消息类型:

<message xmlns="jabber:client" type="chat" to="111751@domain/resource" from="109965@domain/resource"><received xmlns="urn:xmpp:receipts" id="626b61f461eb485fa99177b69512273e"></received></message>

流程:客户端A给客户端B发送一条消息,消息先从客户端A传到服务器,服务器通过判断客户端B是否在线,如果在线的就直接把消息推送给客户端B,客户端B收到消息以后,则发送后回执,服务器收到回执以后再推给客户端A。
如果客户端B不在线,则消息在服务器端保存,服务器发送回回执告诉客户端A发送消息成功,等客户端B上线以后再把消息推送给客户端B。

问题1: 当客户端A给B推送消息时,客户端B正好此时进入后台。由于后台心跳检测还没有检测到B已经离线,这个时候服务器会把消息推送给客户端B,并且服务器没有保存这条消息。而此时客户端B已经进入后台,无法接收到消息,也就无法发送回执。会造成客户端A发送消息失败。
解决方法:服务器每次收到客户端A都会发送的消息,都会由服务器发送回执告诉A已经发送成功。客户端首先保存聊天信息,再客户端B是否在线。如果在线,则发送消息,接受回执,如果没有收到回执,则当成不在线,保存聊天消息为未发送状态,下次等客户端B连线后推送。

3. xmpp IOS端使用架构

封装的xmpp类图

 
xmppHelper类图

(1) XMPPStream 类主要是设置主机名(HostName), 端口号(port), 链接xmpp服务器,及获取其delegate,来获取登录状态及输入xmppPassword来进行登录

(2)XMPPReconnect来设置重新连接的时间,及激活xmppStream

(3) xmppRoster 获取好友花名册

(4)xmppRosterStorage 来通过xmpp自带的coreData数据库保存用户数据

(5)roomPool是保存用户进入房间的池子,可以通过池子中的所有房间,获取群聊房间的离线消息和退出登录时,退出所有群聊房间。

APPDelegate中调用xmpp的流程

  • applicationWillEnterForeground方法中[[NTXmpp sharedInstance] login];
  • applicationDidEnterBackground方法中[[NTXmpp sharedInstance] disconnect];
  • 登录成功以后[[NTXmpp sharedInstance] performSelector:@selector(login) withObject:nil afterDelay:0.5];//0.5秒后执行xmpp登录

XMPPHelper方法代码

+ (NTXmpp*)sharedInstance
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedManager = [[NTXmpp alloc] init];
[sharedManager setupStream];
});
return sharedManager;
} //开始配置xmppstream
#pragma mark--------配置XML流---------
- (void)setupStream
{
NSAssert(xmppStream == nil, @"Method setupStream invoked multiple times"); xmppStream = [[XMPPStream alloc] init]; #if !TARGET_IPHONE_SIMULATOR
{
xmppStream.enableBackgroundingOnSocket = YES;
}
#endif xmppReconnect = [[XMPPReconnect alloc] init];
xmppReconnect.reconnectTimerInterval = 10; //xmppRosterStorage = [[XMPPRosterCoreDataStorage alloc] init]; //xmppRoster = [[XMPPRoster alloc] initWithRosterStorage:xmppRosterStorage]; //xmppRoster.autoFetchRoster = YES;
//xmppRoster.autoAcceptKnownPresenceSubscriptionRequests = YES; [xmppReconnect activate:xmppStream];
//[xmppRoster activate:xmppStream]; // Add ourself as a delegate to anything we may be interested in [xmppStream addDelegate:self delegateQueue:dispatch_get_main_queue()];
[xmppReconnect addDelegate:self delegateQueue:dispatch_get_main_queue()]; [xmppStream setHostName:g_config.XMPPHost];
[xmppStream setHostPort:5222]; // You may need to alter these settings depending on the server you're connecting to
//allowSelfSignedCertificates = NO;
//allowSSLHostNameMismatch = NO;
self.roomPool = [[NTRoomPool alloc] init];
} //xmpp登陆
- (void)login
{
if (isLogined == login_status_yes)
return;
if (![self connect]) {
[HUDNotificationCenter showMessage:@"服务连接失败" hideAfter:2.0f];
};
} - (BOOL)connect
{
if (![xmppStream isDisconnected])
return YES; NSString* myJID = g_loginUser.userId;
//NSString *myPassword = [[NSUserDefaults standardUserDefaults] stringForKey:kMY_USER_PASSWORD]; 112003
NSString* myPassword = g_loginUser.xmppPassword; if (myJID == nil || myPassword == nil) {
return NO;
}
self.isLogined = login_status_ing;
[self notify];
[xmppStream setMyJID:[XMPPJID jidWithString:[NSString stringWithFormat:@"%@@%@/%@", myJID, g_config.XMPPDomain, XMPP_RESOURCE]]];
password = myPassword; NSError* error = nil;
if (![xmppStream connectWithTimeout:10 error:&error]) {
self.isLogined = login_status_no;
[self notify];
return NO;
}
return YES;
} - (void)disconnect
{
[self goOffline];
[xmppStream disconnect];
}
#xmppStreamDelegate

- (void)xmppStreamDidConnect:(XMPPStream*)sender
{
NTLog(@"xmpp connect success");
isXmppConnected = YES; NSError* error = nil; if (![xmppStream authenticateWithPassword:password error:&error]) {
self.isLogined = login_status_no;
[self notify];
//DDLogError(@"Error authenticating: %@", error);
}
} - (void)xmppStreamDidDisconnect:(XMPPStream*)sender withError:(NSError*)error
{
NTLog(@"xmpp did disconnect");
[self logout]; if (!isXmppConnected) {
//[xmppStream authenticateWithPassword:password error:&error];
}
} - (void)xmppStreamDidAuthenticate:(XMPPStream*)sender
{
NTLog(@"xmpp did authenticate");
//DDLogVerbose(@"%@: %@", THIS_FILE, THIS_METHOD);
[self doLogin]; // NSError* error;
//[xmppStream authenticateWithPassword:password error:&error];
} - (void)xmppStream:(XMPPStream*)sender didNotAuthenticate:(NSXMLElement*)error
{
NTLog(@"xmpp did not Autherticate");
NTLog(@"didNotAuthenticate :密码校验失败,登录不成功,原因是:%@", [error XMLString]);
self.isLogined = login_status_no;
[self notify];
//DDLogVerbose(@"%@: %@", THIS_FILE, THIS_METHOD);
}

4. xmpp发送消息

xmpp发送消息需要把消息包装成xml类型

#pragma mark------收发消息-------
- (void)sendMessage:(NTMessage*)msg roomName:(NSString*)roomName //TODO: 添加是否secret参数
{
XMPPMessage* aMessage;
NSString* from = [NSString stringWithFormat:@"%@@%@", msg.fromUserId, g_config.XMPPDomain];
if (roomName == nil) {
//单聊
NSString* to = [NSString stringWithFormat:@"%@@%@", msg.toUserId, g_config.XMPPDomain];
aMessage = [XMPPMessage messageWithType:@"chat" to:[XMPPJID jidWithString:to]];
to = nil;
}
else {
//群聊
NSString* roomJid = [NSString stringWithFormat:@"%@@muc.%@", roomName, g_config.XMPPDomain];
aMessage = [XMPPMessage messageWithType:@"groupchat" to:[XMPPJID jidWithString:roomJid]];
roomJid = nil;
}
[aMessage addAttributeWithName:@"id" stringValue:msg.messageId]; NSXMLElement* request = [NSXMLElement elementWithName:@"request" xmlns:@"urn:xmpp:receipts"];
[aMessage addChild:request];
request = nil; DDXMLNode* node = [DDXMLNode elementWithName:@"body" stringValue:jsonString];
[aMessage addChild:node];
[xmppStream sendElement:aMessage];
//[aMessage release]; [_poolSend setObject:msg forKey:msg.messageId];
//NSrunloop的一个方法
[self performSelector:@selector(onSendTimeout:) withObject:msg afterDelay:[msg getMaxWaitTime]]; from = nil;
}

5. xmpp 接收消息流程

 
接收消息流程
- (void)xmppStream:(XMPPStream*)sender didReceiveMessage:(XMPPMessage*)message
{
//analysis message
}

6 xmpp遇到的问题

  • 群组消息同时推送过来的压力

在xmpp登录以后,需要通过接口获取群列表,在使用for循环进行请求每个群的离线消息,如果距离上次时间太长,就会有很多离线消息等待解析,存储到数据库。
解决方法:采用异步请求服务器,获取离线消息,使用多线程来执行消息的数据库存储。使用主线程来把用户加入到当前群。

  • 当过多消息同时推送过来,内存耗用过多,cpu耗能过大造成卡顿。

主要是xmpp框架中有内置的coredata数据库,每条消息会存储到coreData中。并且这个是xmpp内部控制的。
解决方法。在xmpp框架中,手动设置信息不保存到coreData中。我们是用的fmdb来保存消息。

XMPP详解的更多相关文章

  1. 基于XMPP的即时通信系统的建立(二)— XMPP详解

    XMPP详解 XMPP(eXtensible Messaging and Presence Protocol,可扩展消息处理和现场协议)是一种在两个地点间传递小型结构化数据的协议.在此基础上,XMPP ...

  2. 基于XMPP的即时通信系统的建立 — XMPP IQ详解

    XMPP详解 XMPP(eXtensible Messaging and Presence Protocol,可扩展消息处理和现场协议)是一种在两个地点间传递小型结构化数据的协议.在此基础上,XMPP ...

  3. XMPP即时通讯协议使用(前传)——协议详解

    XMPP详解 XMPP(eXtensible Messaging and Presence Protocol,可扩展消息处理和现场协议)是一种在两个地点间传递小型结构化数据的协议.在此基础上,XMPP ...

  4. Android高效率编码-第三方SDK详解系列(三)——JPush推送牵扯出来的江湖恩怨,XMPP实现推送,自定义客户端推送

    Android高效率编码-第三方SDK详解系列(三)--JPush推送牵扯出来的江湖恩怨,XMPP实现推送,自定义客户端推送 很久没有更新第三方SDK这个系列了,所以更新一下这几天工作中使用到的推送, ...

  5. 理论经典:TCP协议的3次握手与4次挥手过程详解

    1.前言 尽管TCP和UDP都使用相同的网络层(IP),TCP却向应用层提供与UDP完全不同的服务.TCP提供一种面向连接的.可靠的字节流服务. 面向连接意味着两个使用TCP的应用(通常是一个客户和一 ...

  6. ActiveMQ基本详解与总结

    MQ简介: MQ全称为Message Queue, 消息队列(MQ)是一种应用程序对应用程序的通信方法.应用程序通过写和检索出入列队的针对应用程序的数据(消息)来通信,而无需专用连接来链接它们.消息传 ...

  7. 详解RPC远程调用和消息队列MQ的区别

    PC(Remote Procedure Call)远程过程调用,主要解决远程通信间的问题,不需要了解底层网络的通信机制. RPC框架 知名度较高的有Thrift(FB的).dubbo(阿里的). RP ...

  8. 了解iOS消息推送一文就够:史上最全iOS Push技术详解

    本文作者:陈裕发, 腾讯系统测试工程师,由腾讯WeTest整理发表. 1.引言 开发iOS系统中的Push推送,通常有以下3种情况: 1)在线Push:比如QQ.微信等IM界面处于前台时,聊天消息和指 ...

  9. 【转】Zabbix 3.0 从入门到精通(zabbix使用详解)

    [转]Zabbix 3.0 从入门到精通(zabbix使用详解) 第1章 zabbix监控 1.1 为什么要监控 在需要的时刻,提前提醒我们服务器出问题了 当出问题之后,可以找到问题的根源   网站/ ...

随机推荐

  1. 第三十九篇 入门机器学习——Numpy.array的基础操作——合并与分割向量和矩阵

    No.1. 初始化状态 No.2. 合并多个向量为一个向量 No.3. 合并多个矩阵为一个矩阵 No.4. 借助vstack和hstack实现矩阵与向量的快速合并.或多个矩阵快速合并 No.5. 分割 ...

  2. Hive文件与记录格式

    1. Hive文件与记录格式 Create table 有多种用法,例如STORED AS SEQUENCEFILE, ROW FORMAT DELIMITED, SERDE, INPUTFORMAT ...

  3. 题解【loj537】「LibreOJ NOIP Round #1」DNA 序列

    题目描述 \(NOIP\)复赛之前\(HSD\)桑进行了一项研究,发现人某条染色体上的一段\(DNA\)序列中连续的\(k\)个碱基组成的碱基序列与做题的 \(AC\) 率有关!于是他想研究一下这种关 ...

  4. 题解【UVA12097】Pie

    题目描述 输入格式 输出格式 输入输出样例 输入样例#1 3 3 3 4 3 3 1 24 5 10 5 1 4 2 3 4 5 6 5 4 2 输出样例#1 25.1327 3.1416 50.26 ...

  5. docker容器 - 宿主机和容器之间复制文件(cp)

    实验环境 CentOS 7.5 容器 容器是镜像的运行实例.不同的是,镜像是静态的只读文件,而容器带有运行时需要的可写文件层:同时,容器中的应用进程处于运行状态. 主机和容器之间复制文件 要想实现在主 ...

  6. c#中的栈(stack)与队列(queue)

    using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.T ...

  7. Docker Compose 使用示例

    一般步骤 1.定义Dockerfile,方便迁移到任何地方: 2.编写docker-compose.yml文件: 3.运行docker-compose up启动服务 示例 准备工作:提前下载好镜像: ...

  8. 闲来无事.gif

  9. android:Android 6.0权限控制代码封装

    新建的Activity类可以继承这个Activity,这个类封装了关于新版的权限处理相关的代码 使用方法: package com.glsite.phone; import android.conte ...

  10. const和defin区别

    (1)类型的安全性检查:const常量有数据类型,而define定义宏常量没有数据类型.则编译器可以对前者进行类型安全检查,而对后者只进行字符替换,没有类型安全检查(字符替换时可能会产生意料不到的错误 ...