资源下载: https://pan.baidu.com/s/1ge3cajh 密码: 6ytk
安装XMPP集成环境
-
将下载好的xampp-osx根据提示进行安装并打开。F4648516-D77F-4DF4-9031-F7BED0F8C13F.png
-
点击Manage Servers,启动所有服务,如果MySQL Database无法启动,可打开终端执行如下命令: sudo /Applications/XAMPP/xamppfiles/bin/mysql.server start1B424F11-55A2-4F2C-B188-89059822D4CD.png
-
点击Go To Application,配置数据库A3FE18F4-5CE0-4E8A-8BB6-A7AF97F216D2.png
-
数据库新建成功后,需要导入数据库格式。可使用下载好的openfire_mysql.sq文件,也可用另外途径:前往/usr/local,找到文件夹openfire,但此时的openfire文件夹无法打开。选中后右键查看简介,将下面属性修改为读与写。这时可以打开openfire文件夹,查找路径/usr/local/openfire/resources/database下,找到openfire_mysql.sq文件,将其拷贝到桌面,然后打开数据库配置页面。C51E4113-4627-4076-A8AB-0C5A4A092378.png
Openfire服务器搭建
可以使用它轻易的构建高效率的技师通讯服务器,Openfire的安装和使用也是非常的简单,并利用Web进行后台管理。单台服务器可支持上万并发用户。由于是采用开放的XMPP协议,我们可以使用各种支持XMPP协议的IM客户端软件登陆服务(在这里我就使用了Spark)。
- 下载Openfire,并根据提示进行安装。
-
安装完成后,打开系统设置。2337F0EA-21E2-4864-8322-976E3D084138.png3C705089-8D87-44E8-BDCA-AB87D0F18484.png
-
点击Open Admin Console,到网站中配置Openfire。7FDF4D15-E4E8-4AA9-AC5C-2D448EACB2AB.png6477BDE5-63A9-483A-A333-AFED5A64C13C.pngD31A34D9-945C-40C6-BF02-0AE5ACCBC123.png833FB7BA-32F5-403C-9F78-B21389322536.png0602DDA6-5F09-4254-82CF-9E8E3404D753.png7B7802D1-FFB2-4D77-A5ED-6462D71E1577.png
- 到这一步,基本上openfire就已经配置完毕了。
安装Spark客户端
-
按照提示安装客户端,完成后打开
E24D0A1D-BC30-4901-8845-D84B40F25EBA.png -
高级配置5D1E4204-7E6A-47EB-9219-65DFDA664C7A.png
- 然后以管理员身份登录,登录成功后刷新openfire管理界面,可以看到头像变亮了,说明环境配置成功。接下来就可以撸码了。
代码实现
- 主要实现这些功能:
- 注册、登录、退出登录;
- 添加好友、好友请求
- 发送消息、接收消息
- 消息记录
- 新建一个管理类XMPPManager,创建以下对象:
import UIKit
import XMPPFramework
// 枚举:连接服务器的目的
enum ConnectServerPurpose : Int{
case connectServerToLogin // 登录
case connectServerToRegister // 注册
}
class XMPPManager: NSObject {
deinit {
NotificationCenter.default.removeObserver(self)
}
fileprivate var password : String?
fileprivate var userName : String?
fileprivate var connectServerPurpose : ConnectServerPurpose = .connectServerToLogin
// 通信通道对象
var xmppStream : XMPPStream?
// JID
var xmppJID : XMPPJID?
// 好友花名册管理对象
var xmppRoster : XMPPRoster?
// 花名册数据存储对象
var xmppRosterCoreDataStorage : XMPPRosterCoreDataStorage?
// 信息归档对象
var xmppMessageArchiving : XMPPMessageArchiving?
// 信息存储对象
var xmppMessageArchivingCoreDataStorage : XMPPMessageArchivingCoreDataStorage?
var friendsListResultController : NSFetchedResultsController<NSFetchRequestResult>?
var chatRecordsResultController : NSFetchedResultsController<NSFetchRequestResult>?
// 好友请求
var xmppPresence : XMPPPresence?
// 单例
static let manager : XMPPManager = {
let manager = XMPPManager.init()
// 创建通信通道对象
manager.xmppStream = XMPPStream.init()
// 设置服务器IP地址
manager.xmppStream?.hostName = kHostName
// 设置服务器端口
manager.xmppStream?.hostPort = kHostPort
// 添加代理
manager.xmppStream?.addDelegate(manager, delegateQueue: DispatchQueue.main)
// 花名册数据存储对象
manager.xmppRosterCoreDataStorage = XMPPRosterCoreDataStorage.sharedInstance()
manager.xmppRoster = XMPPRoster.init(rosterStorage: manager.xmppRosterCoreDataStorage)
manager.xmppRoster?.activate(manager.xmppStream)
manager.xmppRoster?.addDelegate(manager, delegateQueue: DispatchQueue.main)
// 信息存储对象
manager.xmppMessageArchivingCoreDataStorage = XMPPMessageArchivingCoreDataStorage.sharedInstance()
manager.xmppMessageArchiving = XMPPMessageArchiving.init(messageArchivingStorage: manager.xmppMessageArchivingCoreDataStorage, dispatchQueue: DispatchQueue.main)
// 激活通信通道对象
manager.xmppMessageArchiving?.activate(manager.xmppStream)
return manager
}()
// 连接服务器
func connectToServer(withUserName userName: String) {
// 创建XMPPJID对象
self.xmppJID = XMPPJID.init(user: userName, domain: kDomin, resource: kResource)
// 设置通信通道对象的JID
self.xmppStream?.myJID = self.xmppJID
// 发送请求
if self.xmppStream?.isConnected() == true || self.xmppStream?.isConnecting() == true {
// 先退出登录状态
self.exitLogin()
}
// 连接服务器
do {
try self.xmppStream?.connect(withTimeout: -1)
}catch let error as NSError{
print("连接服务器失败: " + error.description)
}
}
}
// MARK:- 登录方法
extension XMPPManager {
// 登录方法
func login(widthUserName userName: String?, andPassword password: String?) {
guard let userName = userName, let password = password else {
print("用户名和密码不能为空")
return
}
// 记录连接服务器的目的是登录
connectServerPurpose = .connectServerToLogin
// 记录登录信息
self.password = password
self.userName = userName
connectToServer(withUserName: userName)
}
// 退出登录
func exitLogin() {
// 先发送下线状态
let presence = XMPPPresence.init(type: "unavailable")
self.xmppStream?.send(presence)
// 断开连接
self.xmppStream?.disconnect()
}
}
// MARK:- 注册方法
extension XMPPManager {
func register(widthUserName userName: String?, andPassword password: String?) {
guard let userName = userName, let password = password else {
print("用户名和密码不能为空")
return
}
// 记录连接服务器的目的是注册
connectServerPurpose = .connectServerToRegister
// 记录密码
self.password = password
connectToServer(withUserName: userName)
}
}
// MARK:- XMPPStreamDelegate
extension XMPPManager : XMPPStreamDelegate {
// 连接成功
func xmppStreamDidConnect(_ sender: XMPPStream!) {
print("连接成功")
switch connectServerPurpose {
case .connectServerToLogin:
// 验证密码
do {
try self.xmppStream?.authenticate(withPassword: self.password)
} catch let error as NSError {
print("登录时验证密码失败: " + error.description)
}
break
case .connectServerToRegister:
do {
try self.xmppStream?.register(withPassword: self.password)
} catch let error as NSError {
print("注册时验证密码失败: " + error.description)
}
break
}
}
// 连接超时
func xmppStreamConnectDidTimeout(_ sender: XMPPStream!) {
print("连接超时")
}
// 登录成功
func xmppStreamDidAuthenticate(_ sender: XMPPStream!) {
print("登录成功 ", #line, #function)
// 发送上线状态
let presence = XMPPPresence.init(type: "available")
XMPPManager.manager.xmppStream?.send(presence)
}
// 已经断开连接
func xmppStreamDidDisconnect(_ sender: XMPPStream!, withError error: Error!) {
print("++++++++++")
}
}
// MARK:- 好友
extension XMPPManager {
// 添加好友
func addNewFriends(userName: String?) {
guard let name = userName else { return }
let friendJID = XMPPJID.init(string: "\(name)@\(kDomin)")
self.xmppRoster?.subscribePresence(toUser: friendJID)
}
// 获取好友列表
func getFriendsList() -> Array<Any>? {
guard let context = self.xmppRosterCoreDataStorage?.mainThreadManagedObjectContext else {
return nil
}
let request = NSFetchRequest<NSFetchRequestResult>.init(entityName: "XMPPUserCoreDataStorageObject")
let userInfo = String.init(format: "%@@%@", self.userName ?? "", kDomin)
// 谓词
let predicate = NSPredicate.init(format: "streamBareJidStr = %@", userInfo)
request.predicate = predicate
// 排序
let sort = NSSortDescriptor.init(key: "displayName", ascending: true)
request.sortDescriptors = [sort]
friendsListResultController = NSFetchedResultsController.init(fetchRequest: request, managedObjectContext: context, sectionNameKeyPath: nil, cacheName: nil)
friendsListResultController?.delegate = self
do {
try friendsListResultController?.performFetch()
} catch let error as NSError {
print("获取好友聊天记录失败: " + error.description)
}
return friendsListResultController?.fetchedObjects
}
// 获取与某个好友的聊天记录
func getChatRecords(withFriendName friendName: String) ->Array<Any>?{
guard let context = self.xmppMessageArchivingCoreDataStorage?.mainThreadManagedObjectContext else { return nil}
let request = NSFetchRequest<NSFetchRequestResult>.init(entityName: "XMPPMessageArchiving_Message_CoreDataObject")
let userInfo = String.init(format: "%@@%@", self.userName ?? "", kDomin)
let friendsInfo = String.init(format: "%@@%@", friendName, kDomin)
// 谓词
let predicate = NSPredicate.init(format: "streamBareJidStr=%@ AND bareJidStr=%@", userInfo, friendsInfo)
request.predicate = predicate
// 排序
let sort = NSSortDescriptor.init(key: "timestamp", ascending: true)
request.sortDescriptors = [sort]
chatRecordsResultController = NSFetchedResultsController.init(fetchRequest: request, managedObjectContext: context, sectionNameKeyPath: nil, cacheName: nil)
chatRecordsResultController?.delegate = self
do {
try chatRecordsResultController?.performFetch()
} catch let error as NSError {
print("获取好友聊天记录失败: " + error.description)
}
guard let fetchedObjects = chatRecordsResultController?.fetchedObjects else { return nil }
var messageArray = Array<Any>()
for obj in fetchedObjects {
if let _ = (obj as? XMPPMessageArchiving_Message_CoreDataObject)?.body {
messageArray.append(obj)
}
}
return messageArray
}
}
// MARK:- XMPPRosterDelegate
extension XMPPManager : XMPPRosterDelegate {
func xmppRoster(_ sender: XMPPRoster!,
didReceiveRosterItem item: DDXMLElement!) {
print(#line, #function)
}
func xmppRosterDidBeginPopulating(_ sender: XMPPRoster!,
withVersion version: String!) {
print(#line, #function)
}
func xmppRosterDidEndPopulating(_ sender: XMPPRoster!) {
print(#line, #function)
}
// 收到好友请求
func xmppRoster(_ sender: XMPPRoster!,
didReceivePresenceSubscriptionRequest presence: XMPPPresence!) {
self.xmppPresence = presence
// 弹框
let alert = UIAlertView.init(title: "好友请求", message: "\(presence.from().user)请求添加你为好友", delegate: self, cancelButtonTitle:
"拒绝", otherButtonTitles: "同意")
alert.show()
}
}
// MARK:- 好友请求弹框
extension XMPPManager : UIAlertViewDelegate {
func alertView(_ alertView: UIAlertView,
clickedButtonAt buttonIndex: Int) {
switch buttonIndex {
case 0:
// 拒绝
let jid = XMPPJID.init(string: self.xmppPresence?.from().user)
self.xmppRoster?.rejectPresenceSubscriptionRequest(from: jid)
break
case 1:
// 同意
let jid = XMPPJID.init(string: self.xmppPresence?.from().user)
self.xmppRoster?.acceptPresenceSubscriptionRequest(from: jid, andAddToRoster: true)
break
default: break
}
}
}
// MARK:- NSFetchedResultsControllerDelegate
extension XMPPManager : NSFetchedResultsControllerDelegate {
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>,
didChange anObject: Any,
at indexPath: IndexPath?,
for type: NSFetchedResultsChangeType,
newIndexPath: IndexPath?) {
if anObject is XMPPUserCoreDataStorageObject {
// 好友列表数据库发生变化
NotificationCenter.default.post(name: NSNotification.Name(rawValue: notificationName_friendsListDidChange), object: nil)
}else if anObject is XMPPMessageArchiving_Message_CoreDataObject{
// 聊天记录数据库发生变化
NotificationCenter.default.post(name: NSNotification.Name(rawValue: notificationName_chatRecordsDidChange), object: nil)
}
}
}