XMPP框架地址: https://github.com/robbiehanson/XMPPFramework
eJabberd服务器:https://www.process-one.net/en/ejabberd/downloads
Spark: http://www.igniterealtime.org/downloads/download-landing.jsp?file=spark/spark_2_7_7.dmg
- 一、安装mySql
- https://segmentfault.com/a/1190000007838188
- https://www.jianshu.com/p/fd3aae701db9
- https://www.cnblogs.com/yajunLi/p/6251808.html
- http://blog.youkuaiyun.com/qq55214/article/details/50396656
启动mysql服务
1、如果你已经安装了MySQLStartupItem.pkg,重新启动电脑即可。
2、如果你有安装MySQLStartupItem.pkg或者不想启动电脑,运行:应用程序-实用工具-终端,在终端中输入命令:sudo /Library/StartupItems/MySQLCOM/MySQLCOM start,然后输入你的系统管理员密码即可。
终端中输入命令:sudo /Library/StartupItems/MySQLCOM/MySQLCOM stop,然后输入你的系统管理员密码即可。
更改mysql root账户密码
终端中输入命令:/usr/local/mysql/bin/mysqladmin -u root password 新密码
终端登录mysql
方法1:绝对路径
终端中输入命令:/usr/local/mysql/bin/mysql
提示:输入你的新密码
方法2:(推荐)相对路径
终端中输入命令:
查看路径中有没有需要的路径:
终端中输入命令:echo $PATH
没有,继续
添加需要路径:PATH="$PATH":/usr/local/mysql/bin
-
在终端登录到MySQL的命令如下:mysql -u root -p
然后输入密码,如果没有设置密码,直接按enter键。
- 二、搭建Openfire服务器
-
-
- 也可以搭建eJabbered服务器
- https://www.jianshu.com/p/d47a2fa85009
- https://www.jianshu.com/p/7c80a61c8de6
- 三、客户端搭建
-
-
一个XMPP客户端必须支持的功能有: 1. 通过TCP套接字与XMPP服务器进行通信; 2. 解析组织好的XML信息包; 3. 理解消息数据类型。 所以,只要客户端满足上述三个功能,就可以实现基于XMPP的即时通信。 安装完spark后,通过账户创建一个新的账户进行登入,可以从服务器(用户/组)看到,新创建的账户处于在线状态,而admin账户由于没有登入,所以就处于离线状态。
-
我们还可以在XMPP官网下载Adium等客户端软件进行调试
- 其实Mac自带的iMessage也能调试,只要客户端满足之前所述XMPP客户端所必须支持的三个功能,那么任何两个客户端都能够实现即时通信;接下来我们就实现下iMessage和spark之间进行即时通信; 打开iMessage->添加账户->其他“信息”账户->继续;在账户类型里选择Jabber(注:XMPP前身),用户名填写格式为:用户名@服务器名称,服务器名称可以在服务器的属性查看,填入密码,勾选“自动查找服务器和端口”,点击登入;登入之后我们可以添加一个在spark中登入的账户,添加之后就可以进行通话了,效果如下图所示

- 基于XMPP的8大特性(开放、标准、证实可用、分散、安全、可扩展、弹性佳、多样性),被开发出基于XMPP的软件越来越多;XMPP客户端需要支持的功能:
- 1).通过TCP套接字与XMPP服务器进行通信;
- 2).解析组织好的XML信息包;
- 3).理解消息数据类型。
- 四、xmpp使用
- 1.新建项目,使用cocoapods 导入XMPP框架。安卓平台可以使用Smack.
- 包含:
- CocoaLumberjack。这是在整个项目中使用的日志框架。
- CocoaAsyncSocket依赖性。这是底层的网络代码所使用的框架。
- 需要添加引用库 1、CFNetwork framework
- 2、Security framework
- KissXML。在Mac OS X上,苹果提供了处理XML元素的NSXML类集群(NSXMLDocument,NSXMLElement NSXMLNode)。然而,他们不提供这些类在iOS。KissXML旨在替代苹果的iOS集群在NSXML类
2.初始化xmpp流
#define JBXMPP_HOST @"lujiangbin.local"
#define JBXMPP_PORT 5222
- (void)setupStream
{
if (!_xmppStream) {
_xmppStream = [[XMPPStream alloc] init];
[self.xmppStream setHostName:JBXMPP_HOST]; //设置xmpp服务器地址
[self.xmppStream setHostPort:JBXMPP_PORT]; //设置xmpp端口,默认5222
[self.xmppStream addDelegate:self delegateQueue:dispatch_get_main_queue()];
[self.xmppStream setKeepAliveInterval:30]; //心跳包时间
//允许xmpp在后台运行
self.xmppStream.enableBackgroundingOnSocket=YES;
//接入断线重连模块
_xmppReconnect = [[XMPPReconnect alloc] init];
[_xmppReconnect setAutoReconnect:YES];
[_xmppReconnect activate:self.xmppStream];
//接入流管理模块,用于流恢复跟消息确认,在移动端很重要
_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];
}
}
登陆登陆过程包括初始化流、TLS握手和SASL验证等,想要了解各个阶段服务端跟客户端之间交互的内容:查看。XMPPFramework将整个复杂的登陆过程都封装起来了,客户端调用connectWithTimeout:(NSTimeInterval)timeout error:(NSError **)errPtr连接服务器,然后在xmppStreamDidConnect代理方法输入密码验证登陆,这里我们使用在搭建服务器时创建的两个用户,user1和user2。
#define JBXMPP_DOMAIN @"lujiangbin.local"
-(
void)loginWithName:(
NSString *)userName andPassword:(
NSString *)password
{
_myJID = [XMPPJID jidWithUser:userName domain:JBXMPP_DOMAIN resource:
@"iOS"];
self.myPassword = password;
[
self.xmppStream setMyJID:_myJID];
NSError *error =
nil;
[_xmppStream connectWithTimeout:XMPPStreamTimeoutNone error:&error];
}
#pragma mark -- connect delegate
//输入密码验证登陆
- (
void)xmppStreamDidConnect:(XMPPStream *)sender
{
NSError *error =
nil;
[[
self xmppStream] authenticateWithPassword:_myPassword error:&error];
}
//登陆成功
- (
void)xmppStreamDidAuthenticate:(XMPPStream *)sender
{
NSLog(
@"%s",__func__);
//发送在线通知给服务器,服务器才会将离线消息推送过来
XMPPPresence *presence = [XMPPPresence presence];
// 默认"available"
[[
self xmppStream] sendElement:presence];
//启用流管理
[_xmppStreamManagement enableStreamManagementWithResumption:
YES maxTimeout:
0];
}
//登陆失败
- (
void)xmppStream:(XMPPStream *)sender didNotAuthenticate:(
NSXMLElement *)error
{
NSLog(
@"%s",__func__);
}
//获取服务器好友列表
[[[JBXMPPManager sharedInstance] xmppRoster] fetchRoster];
- (void)sendMessage:(NSString *)message to:(XMPPJID *)jid
{
XMPPMessage* newMessage = [[XMPPMessage alloc] initWithType:@"chat" to:jid];
[newMessage addBody:message]; //消息内容
[_xmppStream sendElement:newMessage];
}
消息接收当收到消息的时候,xmppframework会调用didReceiveMessage:代理方法,由于我们在初始化流的时候将消息设置存储到本地,可以看到XMPPMessageArchiving在didReceiveMessage收到消息的时候将消息存储起来。
// XMPPMessageArchiving.m
- (void)xmppStream:(XMPPStream *)sender didSendMessage:(XMPPMessage *)message
{
if ([self shouldArchiveMessage:message outgoing:YES xmppStream:sender])
{
[xmppMessageArchivingStorage archiveMessage:message outgoing:YES xmppStream:sender];
}
}
消息确认为了防止发出去的消息丢失了,可以接入消息回执模块(XEP-184),这样对方每收到一条消息的时候都会返回一条确认的消息,如果没收到该条确认消息可以认为发送失败,确认消息的格式如下: <message to="user2@lujiangbin.local">
<received xmlns="urn:xmpp:receipts" id="消息ID"/>
</message>
不过这种方法也有些弊端,比如每次收到一条消息都必须回复,一定程度上会浪费流量以及影响服务器的性能,所以一般采用流管理来实现消息确认。
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationWillTerminate) name:UIApplicationWillTerminateNotification object:nil];
#pragma mark -- terminate
/**
* 申请后台更多的时间来完成关闭流的任务
*/
-(void)applicationWillTerminate
{
UIApplication *app=[UIApplication sharedApplication];
UIBackgroundTaskIdentifier taskId;
taskId=[app beginBackgroundTaskWithExpirationHandler:^(void){
[app endBackgroundTask:taskId];
}];
if(taskId==UIBackgroundTaskInvalid){
return;
}
[_xmppStream disconnectAfterSendingEndStream];
}
流管理 Stream Management是为了流恢复跟节确认而增加的。理想情况下,客户端发送关闭流的通知给服务器,服务器将后续的消息存储到离线仓库,等客户端再登陆上线的时候推送过来,但是在移动端网络可能随时断掉,这时候服务器并不会马上察觉(只能依靠TCP超时或者服务器自己的心跳包),它会认为对方还在线,将后续的消息发送过去,这样到服务器知道对方掉线的这段时间,期间的消息就丢失了,所以需要流管理来处理。
服务端: <r xmlns='urn:xmpp:sm:3'/>
客户端: <a xmlns='urn:xmpp:sm:3' h='3'/>
客户端: <enable xmlns=
'urn:xmpp:sm:3' resume=
'true'/>
服务端: <enabled xmlns=
'urn:xmpp:sm:3' id=
'流id' resume=
'true'/>
- (
void)xmppStreamDidAuthenticate:(XMPPStream *)sender
{
//登陆完成后,启用流管理
[_xmppStreamManagement enableStreamManagementWithResumption:YES maxTimeout:
0];
}
客户端: <resume xmlns='urn:xmpp:sm:3' h='客户端接收的h值' previd='流id'/>
服务端: <resumed xmlns='urn:xmpp:sm:3' h='服务端接收的h值' previd='流id'/>
//设置手动认证证书
NSMutableDictionary *settings = [
NSMutableDictionary dictionary];
[settings setObject:@YES forKey:GCDAsyncSocketManuallyEvaluateTrust];
[asyncSocket startTLS:settings];
- (
void)socketDidSecure:(GCDAsyncSocket *)sock
{
// 开始接收数据
[sock readDataWithTimeout:TIMEOUT_XMPP_READ_STREAM tag:TAG_XMPP_READ_STREAM];
}
//在delegate方法中,手动信任
-(
void)xmppStream:(XMPPStream *)sender didReceiveTrust:(SecTrustRef)trust completionHandler:(
void (^)(
BOOL))completionHandler
{
if (completionHandler)
completionHandler(
YES);
}