服务器:
ejabberd:容易安装
open fire:功能强大,安装复杂
安装步骤:
xmpp.org -> xmpp software -> ejabberd -> download -> Mac OS X -> 下载之后双击安装
-> 若因安全问题无法安装则去settings -> security -> allow apps downloaded from "anywhere"即可
配置服务器:
安装好后,在application/ejabberd/bin文件夹,点start弹出服务器网页 -> launch -> 输入用户名admin@diana.local密码admin登录
Virtual Host -> Users -> 任意 Add Users -> 每个user里有roster(花名册)-> 通过jabber id 添加好友 -> 配置完成
客户端1
iMessage 添加好友 by jabber
客户端程序
建立XMPP
1. 导入XMPP
Github新版本编译会出现找不到libSimu的错误,如何解决见 http://blog.csdn.net/winer888/article/details/49636451
加桥接头文件 OC->swift
2. 构建管道 xmppStream是连接客户端和服务器的管道
xmppStream = XMPPStream()
xmppStream?.addDelegate(self, delegateQueue: dispatch_get_main_queue())
登录1. 登录逻辑
判断是否已登录(本地是否存有用户信息),是则自动登录,否则跳出登录界面,用户手动登录;
根据是否连接成功,是否登录成功(用户名密码是否正确)的代理,用notification通知LoginVC下一步动作,见5
(1)AppDelegate里面:
if NSUserDefaults.standardUserDefaults().stringForKey(USERNAME) != nil { self.login() }
(2)主页FriendListVC,viewDidLoad里面调用:func checkLogin(){
let userName = NSUserDefaults.standardUserDefaults().stringForKey(USERNAME)
if userName == nil{//未登录
self.navigationController?.pushViewController(LoginViewController(), animated: false)
}else{//已登录
let trimmedUserName = userName!.componentsSeparatedByString("@")[0]
self.lordUser = UserInfo(id: 0, name: trimmedUserName)
}
}
(3)手动登录在LoginViewController点击登录按钮时
先保存用户名和密码到本地,再调用 appDelegate.login()
2. 连接 AppDelegate login()的实现 {
if (xmppStream?.isConnected() == true) { return }
let lordUsername = NSUserDefaults.standardUserDefaults().stringForKey(USERNAME)
let lordPassword = NSUserDefaults.standardUserDefaults().stringForKey(PASSWORD)
if (lordUsername == nil || lordPassword == nil) { return }
xmppStream?.myJID = XMPPJID.jidWithString(lordUsername)
xmppStream?.hostName = "localhost" //因为服务器就在本机,若服务器在外网,则写其IP地址
do { try xmppStream!.connectWithTimeout(90) }
catch{ print("connect failed."); return }
}
3. 是否登录成功的代理
(1)连接成功 -> 检查用户名密码
func xmppStreamDidConnect(sender: XMPPStream!) {
let lordPassword = NSUserDefaults.standardUserDefaults().stringForKey(PASSWORD)
do { try xmppStream?.authenticateWithPassword(lordPassword) }
catch { print("authentication failed.") } }
(2)-> 用户名密码正确 -> goOnline()
func xmppStreamDidAuthenticate(sender: XMPPStream!) {
self.goOnline()
NSNotificationCenter.defaultCenter().postNotification(NSNotification(name: LOGINAUTHENTICATE, object: nil)) }
func goOnline(){
let presence = XMPPPresence()
xmppStream?.sendElement(presence) }
(3)-> 用户名或密码错误
func xmppStream(sender: XMPPStream!, didNotAuthenticate error: DDXMLElement!) {
NSNotificationCenter.defaultCenter().postNotification(NSNotification(name: LOGINDIDNOTAUTHENTICATE, object: nil)) }
(4)LoginVC 添加observer响应Notification
func loginAuthenticated() { //成功则返回主页,并传值给主页的lordUser变量
let friendListViewController = self.navigationController?.viewControllers[0] as! FriendListViewController
friendListViewController.lordUser = UserInfo(id: 0, name: trimmedUserName!)
self.navigationController?.popToViewController(friendListViewController, animated: false) }
func loginDidNotAuthenticate() //失败则给出提示
{ errorTip?.text = "用户名或密码错误" }
接收消息
1. 通信机制
NSNotification 或 自定义delegate 都可以解决通信问题,感觉notification逻辑更简单清晰 便于理解,参数通过object传递,
但苹果推荐关于UI的东西用delegate,和效率有关。
2. 状态消息 & 聊天消息
(1)protocol PresenceDelegate { func presneceChanged(friend: UserInfo) }
protocol MessageDelegate { func didReceiveMessage(messageInfo: MessageInfo) }
(2)AppDelegate 里声明变量
var presenceDelegate: PresenceDelegate?
var messageDelegate: MessageDelegate?
(3)AppDelegate XMPP代理 调用above two代理函数
func xmppStream(sender: XMPPStream!, didReceivePresence presence: XMPPPresence!) {
if (sender.myJID.user != presence.from().user){ //有几个好友就调用几次
let friend = UserInfo(id: 1, name: presence.from().user) //此user为截去域名后的name部分
self.presenceDelegate?.presneceChanged(friend) } }
func xmppStream(sender: XMPPStream!, didReceiveMessage message: XMPPMessage!) {
if message.isChatMessage() {//还有可能是语音图片消息
let senderName = message.from().user
let targetName = message.to().user
let sender = UserInfo(id: 1, name: senderName)
let target = UserInfo(id: 0, name: targetName)
if let messageBody = message.body(){
let messageInfo = MessageInfo(id: 0, body: messageBody, sender: sender, target: target)
self.messageDelegate?.didReceiveMessage(messageInfo) } } }
(4)继承以上两个Delegate:需要响应朋友变化(FriendListVC),需要响应消息变化(FriendListVC,ChatRoomVC)
(5)连接appDelegate和VC
appDelegate.presenceDelegate = self
appDelegate.messageDelegate = self
(6)FriendListVC实现代理函数
func presneceChanged(friend: UserInfo) {
for user in self.friendListDataSource{
if (user.name == friend.name) { self.friendListDataSource.removeObject(user) } }
self.friendListDataSource.insertObject(friend, atIndex: 0)
self.friendListTableView.reloadData() }
(7)ChatRoomVC实现代理函数
func didReceiveMessage(messageInfo: MessageInfo) {
if messageInfo.sender?.name == self.friendUser?.name {
//商业项目需要更新message ID, ejabberd并不需要
let lastID = (self.messageDataSource.lastObject as! MessageInfo).messageId! as Int
let IdUpdatedMsg = MessageInfo(id: lastID+1, body: messageInfo.messageBody!, sender: messageInfo.sender!, target: messageInfo.target!)
self.messageDataSource.addObject(IdUpdatedMsg)
self.messageTableView.reloadData()
let indexPath = NSIndexPath(forRow: messageDataSource.count-1, inSection: 0)
messageTableView.scrollToRowAtIndexPath(indexPath, atScrollPosition: UITableViewScrollPosition.Bottom, animated: true)
}
}
登出
1. 调用 appDelegate.disconnect()
{ let presence = XMPPPresence.elementWithName("unavailable")
xmppStream.sendElement(presence)
xmppStream.didconnect() }
2. 清楚用户信息
NSUserDefaults.standard...().remove...(USERNAME)
NSUserDefaults.standard...().remove...(USERNAME)
NSUserDefaults.standard...().syncronize()
3. 重新登录要返回到主页面
appDelegate.tabbarController.selectedIndex = 0