Protobuf入門到高手教程

Protobuf全稱Protocol Buffers,簡稱GPB、PB,是QQ等IM采用的協(xié)議,比XML、XMPP(環(huán)信)、JSON、結(jié)構(gòu)體等所有傳輸效果都高的一種傳輸協(xié)議,由谷歌發(fā)明,其效率一般是XML XMPP的20倍以上,JSON的10倍以上,是一種游戲中普遍采用的IM消息協(xié)議,所以你非常有必要認真讀一下本博文的入門教程,并運行作者的Demo

本Demo以直播聊天室為假設(shè)的Demo,一般消息類型有50到100種左右,簡單起見,這里舉5種消息類型:

enum MsgType : Int {
case join = 0
case leave = 1
case text = 2
case gift = 3
case heartBeat = 8   
}  

分別表示進入主播室離開主播室 文本消息 禮物圖片或GIF動畫消息及心跳包消息,其他如廣告、系統(tǒng)廣播等不在本Demo演示這是一個代碼區(qū)塊。

(本文要求讀者有一定的socket和 swift3基礎(chǔ),再往下閱讀)

摘要

1、制作協(xié)議格式

syntax = "proto2";
message UserInfo {
required int32 level = 1;
required string name = 2;
required string iconURL = 3;
}

2、制作協(xié)議的對象數(shù)據(jù)

fileprivate lazy var user : UserInfo.Builder = {
    let user = UserInfo.Builder()
    user.level = Int32(arc4random_uniform(24))
    user.name = "targetcloud\(arc4random_uniform(10))"
    user.iconUrl = "icon\(arc4random_uniform(2))"
    return user
}()

3、剩下的IM核心代碼其實只有兩行,要發(fā)送消息時

let data = (try! user.build()).data()

收到消息時

UserInfo.parseFrom(data : msgData)

詳細使用

1、環(huán)境安裝
找到Github
https://github.com/alexeyxo/protobuf-Swift

在命令行中依次執(zhí)行下面代碼

ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
brew install libtool
brew link libtool
brew install automake
brew install protobuf
brew install protobuf-[swift](http://lib.csdn.net/base/swift)

如下圖

2、導(dǎo)入cocoapod

在podfile中加入
pod 'ProtocolBuffers-Swift'
安裝pod:pod install

如下圖

3、編寫IMMessage.proto

syntax = "proto2";

message UserInfo {
required int32 level = 1;
required string name = 2;
required string iconURL = 3;
}

message ChatMessage {
required UserInfo user = 1;
required string text = 2;
}

message GiftMessage {
required UserInfo user = 1;
required string giftname = 2;
required string giftURL = 3;
required int32 giftcount = 4;
}

4、再去命令行編繹生成

進入到.proto所在文件目錄
cd /Users/targetcloud/Desktop/app/TGClient/TGClient/IMClient 
編繹生成XXX.proto.swift
protoc  IMMessage.proto --swift_out="./"

5、把生成好的Immessage.proto.swift拖到工程中去(服務(wù)端和客戶端可以同時使用同一個編繹出來的proto.swift)
如果服務(wù)端使用其他語言的話,則需要在第4步這里生成相應(yīng)語言的文件,此例,我們服務(wù)端也是cocoapod集成

6、使用
一般情況下,IM服務(wù)器收到各個客戶端的消息后,是將這些消息再轉(zhuǎn)發(fā)給各個客戶端(聊天室)或某個客戶端(私聊)
心跳包的設(shè)計:服務(wù)端每隔一秒看一下有沒有客戶端的心跳包,而客戶端是每隔10秒發(fā)一個心跳包,服務(wù)端因為連接的客戶端數(shù)量大等壓力原因所以開始子線程,而客戶端就自己一個,所以沒有必要開子線程
服務(wù)器處理各種消息也處理心跳包,針對每個客戶端連接,如果服務(wù)端持續(xù)10秒以上(>10)沒有再得到客戶端的心跳包,那么就從服務(wù)器移除此客戶端的消息監(jiān)聽并停止和銷毀此客戶端監(jiān)聽定時器

removeClientCallback?(self)
        self.delegate?.removeClient(self)
        timer?.invalidate()
        timer = nil
        queue = nil

7、代碼如下:

服務(wù)端代碼
(1)

//  
//  IMServerManager.swift  
//  TGServer  
//  
//  Created by targetcloud on 2017/3/27.  
//  Copyright ? 2017年 targetcloud. All rights reserved.  
//  

import Cocoa  

class IMServerManager: NSObject {  
fileprivate var tcpServer : TCPServer?  
/* 
fileprivate lazy var serverSocket : TCPServer = TCPServer(addr:"0.0.0.0",port: 9898) 
*/  
fileprivate var isServerRunning : Bool = false  
fileprivate lazy var clientMgrs : [IMClientManager] = [IMClientManager]()//所有連接上服務(wù)器的客戶端  
}  

/* 
extension IMServerManager { 
func startRunning_() { 
    isServerRunning = true 
    serverSocket.listen() 
    DispatchQueue.global().async { 
        while self.isServerRunning{ 
            let tcpClient = self.serverSocket.accept() 
             
            let lengthBytes = (tcpClient?.read(4))! 
            let data = Data(bytes: lengthBytes, count: 4) 
            var length : Int = 0 
            (data as NSData).getBytes(&length, length: 4) 
             
            //print(length) 
             
            let dataBytes = (tcpClient?.read(length))! 
            let resultStr = String(bytes: dataBytes, encoding: .utf8) 
             
            print("長度 \(length) 內(nèi)容 \(resultStr ?? "")") 
             
        } 
    } 
} 
} 
 */  

extension IMServerManager {  
func startRunning(_ address : String, _ port : Int) {  
    tcpServer = TCPServer(addr: address, port: port)  
    tcpServer?.listen()  
    isServerRunning = true  
    DispatchQueue.global().async {  
        while self.isServerRunning {  
            if let client = self.tcpServer?.accept() {  
                DispatchQueue.global().async {  
                    self.handleClient(client)  
                }  
            }  
        }  
    }  
}  
  
func stopRunning() {  
    isServerRunning = false  
}  
  
func handleClient(_ client : TCPClient) {  
    let clientMgr = IMClientManager(tcpClient: client)  
    clientMgrs.append(clientMgr)  
    //clientMgr.delegate = self//MARK:- 代理使用 1 <成為代理>  
    //IMServerManager(self)->clientMgrs->client->forwardMsgCallback->IMServerManager(self.clientMgrs)  
    clientMgr.forwardMsgCallback = {[weak self] (client , msgData, isLeave ) in  
        if isLeave {  
            if let index = self?.clientMgrs.index(of: client){  
                client.tcpClient.close()  
                self?.clientMgrs.remove(at: index)//點客戶端的離開房間會調(diào)用此句,離開消息不會回傳給此客戶端  
            }  
        }  
          
        for c in (self?.clientMgrs ?? []) {  
            c.sendMsg(msgData)  
        }  
    }  
      
    clientMgr.removeClientCallback = {[weak self] (client) in  
        print(" 客戶端連接數(shù)由 \(self?.clientMgrs.count ?? 0) -> ")  
        if let index = self?.clientMgrs.index(of: client){  
            client.tcpClient.close()  
            self?.clientMgrs.remove(at: index)//斷開后客戶端要做的處理  
            print(" \(self?.clientMgrs.count ?? 0) ")  
        }  
    }  
      
    clientMgr.startReadMsg()  
}  
  
}  


extension IMServerManager : IMClientManagerDelegate {//MARK:- 代理使用 2 <遵守>  
//MARK:- 代理使用 3 <實現(xiàn)代理方法>  
func removeClient(_ client: IMClientManager) {  
    guard let index = clientMgrs.index(of: client) else { return }  
    client.tcpClient.close()  
    clientMgrs.remove(at: index)  
}  
  
func forwardMsg(_ client: IMClientManager, msgData: Data, isLeave: Bool) {  
    if isLeave {  
        /* 
        if let index = clientMgrs.index(of: client) { 
            clientMgrs.remove(at: index) 
        } 
        */  
        removeClient(client)  
    }  
      
    for client in clientMgrs {  
        client.sendMsg(msgData)  
    }  
}  
}  

(2)

//  
//  IMClientManager.swift  
//  TGServer  
//  
//  Created by targetcloud on 2017/3/27.  
//  Copyright ? 2017年 targetcloud. All rights reserved.  
//  

import Cocoa  

protocol IMClientManagerDelegate : class {//MARK:- 代理 1 <定義協(xié)議>  
func removeClient(_ client : IMClientManager)  
func forwardMsg(_ client : IMClientManager, msgData : Data, isLeave : Bool)  
}  

class IMClientManager: NSObject {  
weak var delegate : IMClientManagerDelegate?//MARK:- 代理 2 <定義屬性>  
  
//MARK:- 1 也可以使用閉包解決方案來實現(xiàn)服務(wù)器消息轉(zhuǎn)發(fā)  
var forwardMsgCallback : ((_ client : IMClientManager,_ data : Data,_ isLeave : Bool)->())?//var forwardMsgCallback : ((IMClientManager,Data,Bool)->())?  
var removeClientCallback : ((_ client : IMClientManager) -> ())?  
  
var tcpClient : TCPClient  
fileprivate var isClientRunning : Bool = false  
fileprivate var beatCounter : Int = 0  
fileprivate var queue : DispatchQueue?  
fileprivate var timer : Timer?  
  
init(tcpClient : TCPClient) {  
    self.tcpClient = tcpClient  
}  
}  

extension IMClientManager {  
func startReadMsg() {  
    isClientRunning = true  
    queue = DispatchQueue.global()  
    queue?.async {  
        //timer放在最后面或者包在DispatchQueue.global().async{此處}  
        self.timer = Timer(fireAt: Date(), interval: 1, target: self, selector: #selector(self.heartBeat), userInfo: nil, repeats: true)  
        RunLoop.current.add(self.timer!, forMode: RunLoopMode.commonModes)  
        //timer.fire()  
        RunLoop.current.run()  
    }  
      
      while self.isClientRunning {  
        // 1.長度  
        if let lengthBytes = self.tcpClient.read(4) {  
            let lengthData = Data(bytes: lengthBytes, count: 4)  
            var length : Int = 0  
            (lengthData as NSData).getBytes(&length, length: 4)  
            //print(length)  
              
            // 2.類型  
            guard let typeBytes = self.tcpClient.read(2) else {  
                continue  
            }  
            var type : Int = 0  
            let typedata = Data(bytes: typeBytes, count: 2)  
            (typedata as NSData).getBytes(&type, length: 2)  
            //print(type)  
              
            // 3.消息  
            guard let dataBytes = self.tcpClient.read(length) else {  
                continue  
            }  
            let msgData = Data(bytes: dataBytes, count: length)  
              
            /* 
            let resultStr = String(bytes: dataBytes, encoding: .utf8) 
            print(" --- 長度 \(length) 類型 \(type) 內(nèi)容 \(resultStr ?? "") --- ") 
            */  
              
            //各種Data轉(zhuǎn)proto,反序列化  
            switch type {  
                case 0:  
                    let userInfo = try! UserInfo.parseFrom(data : msgData)  
                    print(length,type,userInfo.name, userInfo.level, userInfo.iconUrl)  
                case 1:  
                    let user = try! UserInfo.parseFrom(data: msgData)  
                    print(length,type,user.name, user.level, user.iconUrl)  
                case 2:  
                    let chatMsg = try! ChatMessage.parseFrom(data: msgData)  
                    print(length,type,chatMsg.user.name, chatMsg.user.level, chatMsg.user.iconUrl,chatMsg.text)  
                case 3:  
                    let giftMsg = try! GiftMessage.parseFrom(data: msgData)  
                    print(length,type,giftMsg.user.name, giftMsg.user.level, giftMsg.user.iconUrl,giftMsg.giftUrl, giftMsg.giftname, giftMsg.giftcount)  
                case 8:  
                    print(" --- 心跳包 長度\(length), 類型 \(type) \(Thread.current)--- ")  
                    queue?.async {  
                        self.beatCounter = 0//有心跳包,計數(shù)器清0  
                    }  
                    //關(guān)鍵代碼  
                    continue//加這一句心跳包不需要轉(zhuǎn)發(fā)給客戶端  
                default:  
                    print("其他")  
            }  
              
            // 4.消息轉(zhuǎn)發(fā)出去  
            self.delegate?.forwardMsg(self, msgData: lengthData + typedata + msgData, isLeave: type == 1)//MARK:- 代理 3 <使用代理>  
              
            //MARK:- 2 閉包方案  
            self.forwardMsgCallback?(self,lengthData + typedata + msgData,type == 1)  
        } else {//當關(guān)閉客戶端時會調(diào)用這里,不是離開房間,是斷線  
            self.isClientRunning = false  
            //不離開房間,直接斷線,那么也要移除此客戶端  
            self.removeClientCallback?(self)  
            self.delegate?.removeClient(self)  
            print(" --- 客戶端主動斷開了連接 --- ")  
        }  
      }  
}  
  
func sendMsg(_ data : Data) {  
    _ = tcpClient.send(data: data)  
}  
}  

extension IMClientManager {  
@objc fileprivate func heartBeat(){  
    print(" --- server heartBeat \(beatCounter) \(Thread.current)--- ")  
    beatCounter += 1  
    if beatCounter>10{  
        removeClientCallback?(self)  
        self.delegate?.removeClient(self)  
          
        //停止定時器  
        timer?.invalidate()  
        timer = nil  
        queue = nil  
    }  
}  
}  

(3)

//  
//  ViewController.swift  
//  TGServer  
//  
//  Created by targetcloud on 2017/3/27.  
//  Copyright ? 2017年 targetcloud. All rights reserved.  
//  

import Cocoa  

class ViewController: NSViewController {  

@IBOutlet weak var startBtn: NSButton!  
@IBOutlet weak var tipLabel: NSTextField!  
@IBOutlet weak var stopBtn: NSButton!  
  
fileprivate lazy var mgr : IMServerManager = IMServerManager()  
  
override func viewDidLoad() {  
    super.viewDidLoad()  

    stopBtn.isEnabled = false  
}  

override var representedObject: Any? {  
    didSet {  
    // Update the view, if already loaded.  
    }  
}  

@IBAction func startRunning(_ sender: NSButton) {  
    tipLabel.stringValue = "正在監(jiān)聽客戶端連接請求。。。"  
    mgr.startRunning("0.0.0.0", 9898)  
    stopBtn.isEnabled = true  
    startBtn.isEnabled = false  
}  
  
  
@IBAction func stopRunning(_ sender: NSButton) {  
    tipLabel.stringValue = "停止監(jiān)聽新的客戶端連接請求。"  
    mgr.stopRunning()  
    stopBtn.isEnabled = false  
}  

}  

客戶端代碼
(1)

//  
//  TGSocketClient.swift  
//  TGClient  
//  
//  Created by targetcloud on 2017/3/27.  
//  Copyright ? 2017年 targetcloud. All rights reserved.  
//  

import UIKit  

enum MsgType : Int {  
case join = 0  
case leave = 1  
case text = 2  
case gift = 3  
case heartBeat = 8  
}  

protocol TGSocketClientDelegate : class {//傳遞的事件比較多或各種情況分別處理時一般用代理設(shè)計模式  
func socket(_ socket : TGSocketClient,joinRoom userInfo :UserInfo)  
func socket(_ socket : TGSocketClient,leaveRoom userInfo :UserInfo)  
func socket(_ socket : TGSocketClient,sendChatMsg chatMsg :ChatMessage)  
func socket(_ socket : TGSocketClient,sendGift giftMsg :GiftMessage)  
}  

class TGSocketClient {  
weak var delegate : TGSocketClientDelegate?  
  
fileprivate var tcpClient : TCPClient  
fileprivate var isConnected : Bool = false  
fileprivate lazy var user : UserInfo.Builder = {  
    let user = UserInfo.Builder()  
    user.level = Int32(arc4random_uniform(24))  
    user.name = "targetcloud\(arc4random_uniform(10))"  
    user.iconUrl = "icon\(arc4random_uniform(2))"  
    return user  
}()  
  
init(address:String , prot : Int){  
    tcpClient = TCPClient(addr: address, port: prot)  
}  
}  

extension TGSocketClient{  
func connectServer(_ timeout : Int) -> Bool {  
    isConnected = tcpClient.connect(timeout: timeout).0  
    if isConnected {  
        startReadMsg()  
        let timer = Timer(fire: Date(), interval: 10, repeats: true, block: { (timer:Timer) in  
            self.sendHeartBeats()  
        })  
        RunLoop.main.add(timer, forMode: .commonModes)  
    }  
    return isConnected  
}  
  
fileprivate func sendHeartBeats(){  
    let heartMsg = "this is a heartBeat message "  
    sendMsg(MsgType.heartBeat.rawValue, msgData: heartMsg.data(using: .utf8)!)  
}  
  
func startReadMsg() {//一直讀消息,讀到后由代理處理  
    DispatchQueue.global().async {//TCPClient.read是阻塞式的,應(yīng)該放在全局中去執(zhí)行  
        while self.isConnected {  
            if let lengthMsg = self.tcpClient.read(4) {  
                let lData = Data(bytes: lengthMsg, count: 4)  
                var length : Int = 0  
                (lData as NSData).getBytes(&length, length: 4)  
                  
                guard let typeMsg = self.tcpClient.read(2) else {  
                    continue  
                }  
                var type : Int = 0  
                let tdata = Data(bytes: typeMsg, count: 2)  
                (tdata as NSData).getBytes(&type, length: 2)  
                  
      
                guard let msg = self.tcpClient.read(length) else {  
                    continue  
                }  
                let msgData = Data(bytes: msg, count: length)  
                  
                DispatchQueue.main.async {//UI處理放main處理(由delegate中轉(zhuǎn))  
                    self.handleMsg(type, msgData: msgData)  
                }  
            }else{  
                print("有情況,服務(wù)器當了")  
            }  
        }  
    }  
}  
  
fileprivate func handleMsg(_ type : Int, msgData : Data) {//讀到后由代理處理  
    switch type {  
    case MsgType.join.rawValue:  
        let user = try! UserInfo.parseFrom(data: msgData)//反序列化成對象  
        print(user.name, user.level, user.iconUrl)  
        delegate?.socket(self, joinRoom: user)//返回給VC(控制器)顯示等  
    case MsgType.leave.rawValue:  
        let user = try! UserInfo.parseFrom(data: msgData)  
        print(user.name, user.level, user.iconUrl)  
        delegate?.socket(self, leaveRoom: user)  
    case MsgType.text.rawValue:  
        let chatMsg = try! ChatMessage.parseFrom(data: msgData)  
        print(chatMsg.user.name, chatMsg.user.level, chatMsg.user.iconUrl,chatMsg.text)  
        delegate?.socket(self, sendChatMsg: chatMsg)  
    case MsgType.gift.rawValue:  
        let giftMsg = try! GiftMessage.parseFrom(data: msgData)  
        print(giftMsg.user.name, giftMsg.user.level, giftMsg.user.iconUrl,giftMsg.giftUrl, giftMsg.giftname, giftMsg.giftcount)  
        delegate?.socket(self, sendGift: giftMsg)  
    case MsgType.heartBeat.rawValue:  
        print("心跳包")  
    default:  
        print("其他類型消息")  
    }  
}  
}  

extension TGSocketClient{//序列化Data發(fā)送  
func sendJoinMsg() {  
    /* 
    let data = (try! user.build()).data() 
    */  
    guard let user =  try? user.build() else {  
        return  
    }  
    let data = user.data()  
    sendMsg(MsgType.join.rawValue, msgData: data)  
}  
  
func sendLeaveMsg() {  
    let data = (try! user.build()).data()  
    sendMsg(MsgType.leave.rawValue, msgData: data)  
}  
  
func sendTextMsg(_ text : String) {  
    let chatMsg = ChatMessage.Builder()  
    chatMsg.text = text  
    chatMsg.user = try! user.build()  
      
    let chatData = (try! chatMsg.build()).data()  
    sendMsg(MsgType.text.rawValue, msgData: chatData)  
}  
  
func sendGiftMsg(_ giftname : String, _ giftURL : String, _ giftcount : Int) {  
    let giftMsg = GiftMessage.Builder()  
    giftMsg.giftname = giftname  
    giftMsg.giftUrl = giftURL  
    giftMsg.giftcount = Int32(giftcount)  
    giftMsg.user = try! user.build()  
      
    let giftData = (try! giftMsg.build()).data()  
    sendMsg(MsgType.gift.rawValue, msgData: giftData)  
}  
  
fileprivate func sendMsg(_ type : Int , msgData :Data)  {//發(fā)送的消息處理后由tcpClient : TCPClient 正式發(fā)送  
    var length = msgData.count  
    let lengthData = Data(bytes: &length, count: 4)  
      
    var type = type  
    let typeData = Data(bytes: &type, count: 2)  
      
    tcpClient.send(data: lengthData + typeData + msgData)  
}  
}  

(2)IMMessage.proto

syntax = "proto2";  

message UserInfo {  
required int32 level = 1;  
required string name = 2;  
required string iconURL = 3;  
}  

message ChatMessage {  
required UserInfo user = 1;  
required string text = 2;  
}  

message GiftMessage {  
required UserInfo user = 1;  
required string giftname = 2;  
required string giftURL = 3;  
required int32 giftcount = 4;  
}  

(3)

//  
//  ViewController.swift  
//  TGClient  
//  
//  Created by targetcloud on 2017/3/27.  
//  Copyright ? 2017年 targetcloud. All rights reserved.  
//  

import UIKit  

class ViewController: UIViewController {  

fileprivate lazy var clientSocket : TGSocketClient = TGSocketClient(address: "192.168.1.103", prot: 9898)  
  
override func viewDidLoad() {  
    super.viewDidLoad()  
      
    if clientSocket.connectServer(5){  
        print("連接到服務(wù)器成功")  
        //clientSocket.startReadMsg() 此句放 connectServer 里面,連接成功就開始讀  
        clientSocket.delegate = self  
    }  
}  
  
@IBAction func sendMsg(_ sender: UIButton) {  
    switch sender.tag {  
    case MsgType.join.rawValue:  
        clientSocket.sendJoinMsg()  
    case MsgType.leave.rawValue:  
        clientSocket.sendLeaveMsg()  
    case MsgType.text.rawValue:  
        clientSocket.sendTextMsg("你好, targetcloud")  
    case MsgType.gift.rawValue:  
        clientSocket.sendGiftMsg("游艇", "http://blog.csdn.net/callzjy/article/details/66596736", 99)  
    default:  
        print("未識別消息")  
    }  
}  

}  

extension ViewController : TGSocketClientDelegate {  
func socket(_ socket : TGSocketClient,joinRoom userInfo :UserInfo){  
    //UI 請自由發(fā)揮  
}  
func socket(_ socket : TGSocketClient,leaveRoom userInfo :UserInfo){  
    //UI  
}  
func socket(_ socket : TGSocketClient,sendChatMsg chatMsg :ChatMessage){  
    //UI  
}  
func socket(_ socket : TGSocketClient,sendGift giftMsg :GiftMessage){  
    //UI  
}  
}  

注意點:

1、運行作者代碼前請修改IP
客戶端的ViewController.swift中的
fileprivate lazy var clientSocket : TGSocketClient = TGSocketClient(address: "192.168.1.103", prot: 9898)
先運行服務(wù)端,點界面的啟動,然后開啟客戶端

2、TCPClient.read是阻塞式的,應(yīng)該放在全局線程中去執(zhí)行

3、發(fā)送時都是序列化Data發(fā)送,讀到消息都是反序列成協(xié)議對象再處理,具體場景:某個客戶端送主播一游艇,客戶端把禮物消息序列化發(fā)送給服務(wù)器,服務(wù)器轉(zhuǎn)發(fā)給同一主播室的其他各個客戶端(讓大家看到某人送給主播一游艇),
** 各個客戶端收到消息后,則反序列化這一消息,并解析出其中的各個成員對象及屬性后呈現(xiàn)到界面上(某人的頭像,游艇的圖片,GIF動畫等)**

4、代理換成閉包形式設(shè)計時,注意閉包中的self的循環(huán)引用

clientMgr.forwardMsgCallback = {[weak self] (client , msgData, isLeave ) in  
        if isLeave {  
            if let index = self?.clientMgrs.index(of: client){  
                client.tcpClient.close()  
                self?.clientMgrs.remove(at: index)//點客戶端的離開房間會調(diào)用此句,離開消息不會回傳給此客戶端  
            }  
        }  
          
        for c in (self?.clientMgrs ?? []) {  
            c.sendMsg(msgData)  
        }  
    }  
      
    clientMgr.removeClientCallback = {[weak self] (client) in  
        print(" 客戶端連接數(shù)由 \(self?.clientMgrs.count ?? 0) -> ")  
        if let index = self?.clientMgrs.index(of: client){  
            client.tcpClient.close()  
            self?.clientMgrs.remove(at: index)//斷開后客戶端要做的處理  
            print(" \(self?.clientMgrs.count ?? 0) ")  
        }  
    }  

5、序列化時 let data =(try! user.build()).data()這樣的強行try 可以換成

guard let user =  try? user.build() else {
        return
    }
    let data = user.data()

如果為了簡便也可以使用try!但確保你對你的代碼有自信,新手建議用guard守護

6、代碼執(zhí)行過程
(1)
點擊客戶端的離開房間會調(diào)用閉包中的

self?.clientMgrs.remove(at: index)
clientMgr.forwardMsgCallback = {[weak self](client , msgData, isLeave) in
        if isLeave {
            if let index = self?.clientMgrs.index(of: client){
            self?.clientMgrs.remove**(**at: index**)**//**點客戶端的離開房間會調(diào)用此句
            }
        }
        
        for c in (self?.clientMgrs?? []) {
            c.sendMsg(msgData)
        }
    }

但是服務(wù)端收到客戶端的心跳包仍然繼續(xù)執(zhí)行,服務(wù)器會打印
** --- 心跳包****長度28,****類型 8 --- **
但不會回發(fā)消息(心跳包)給客戶端

(2)
當人為繼線(下線、關(guān)閉客戶端)時,會調(diào)用startReadMsg中的 isClientRunning = false
else {//當關(guān)閉客戶端時會調(diào)用這里,不是離開房間,是斷線

**                isClientRunning = false**
            delegate?.removeClient(self)

服務(wù)器也收就收不到此客戶端的心跳包,也不會打印心跳包

提醒:若在服務(wù)端startReadMsg中的心跳包處理處加上不轉(zhuǎn)發(fā)代碼 continue則客戶不顯示心跳包三個字,[測試]期間可以去掉下面的關(guān)鍵代碼
**//關(guān)鍵代碼**
**continue//加這一句心跳包不需要轉(zhuǎn)發(fā)給客戶端**

7、UI處理放main處理

**DispatchQueue.main.async** {
                    self.handleMsg(type, msgData: msgData)
                }

8、定時器一般要放在其他代碼的后面
如服務(wù)端的心跳包

    let timer = Timer(fireAt: Date(), interval: 2.0, target: self, selector: #selector(heartBeat), userInfo: nil, repeats: true)
    RunLoop.current.add(timer, forMode: RunLoopMode.commonModes)
    //timer.fire()
    RunLoop.current.run()
放在IMClientManager的startReadMsg的最后面

或者把上面一段代碼包在子線程中去,這樣可以放在startReadMsg的開始處

**DispatchQueue.global().async {**
    let timer = Timer(fireAt: Date(), interval: 2.0, target: self, selector: #selector(heartBeat), userInfo: nil, repeats: true)
    RunLoop.current.add(timer, forMode: RunLoopMode.commonModes)
    //timer.fire()
    RunLoop.current.run()
}

10、服務(wù)端在移除某個客戶端監(jiān)聽時

   self?.clientMgrs.remove(at: index)  
   一般需要同時關(guān)閉TCPClient,代碼如下
   client.**tcpClient.close()**

11、總結(jié)

客戶端做的事:
(1)建立與服務(wù)器的連接
(2)發(fā)送消息(data()序列化)
(3)收到消息回顯到UI上,進行處理(反序列消息成對象及屬性,呈現(xiàn)到客戶端的界面上)
(4)建立心跳包

服務(wù)端做的事:
轉(zhuǎn)發(fā)所有客戶端的消息,把心跳停止的客戶端移開

來源:http://blog.csdn.net/callzjy/article/details/67627020

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容