1. 程式人生 > 其它 >openfire即時聊天軟體搭建

openfire即時聊天軟體搭建

  • 將下載好的xampp-osx根據提示進行安裝並開啟。 F4648516-D77F-4DF4-9031-F7BED0F8C13F.png
  • 點選Manage Servers,啟動所有服務,如果MySQL Database無法啟動,可開啟終端執行如下命令: sudo /Applications/XAMPP/xamppfiles/bin/mysql.server start 1B424F11-55A2-4F2C-B188-89059822D4CD.png
  • 點選Go To Application,配置資料庫 A
  • 資料庫新建成功後,需要匯入資料庫格式。可使用下載好的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.png 3C705089-8D87-44E8-BDCA-AB87D0F18484.png
  • 點選Open Admin Console,到網站中配置Openfire。 7FDF4D15-E4E8-4AA9-AC5C-2D448EACB2AB.png 6477BDE5-63A9-483A-A333-AFED5A64C13C.png D31A34D9-945C-40C6-BF02-0AE5ACCBC123.png 833FB7BA-32F5-403C-9F78-B21389322536.png 0602DDA6-5F09-4254-82CF-9E8E3404D753.png 7B7802D1-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)
      }
  }
}



XMPP: Openfire + Spark 實現即時通訊 - 簡書 (jianshu.com)