Protobuf入门到高手教程

Protobuf全称Protocol Buffers,简称GPB、PB,是QQ等IM采用的协议,比XML、XMPP(环信)、JSON、结构体等所有传输效果都高的一种传输协议,由谷歌发明,其效率一般是XML XMPP的20倍以上,JSON的10倍以上,是一种游戏中普遍采用的IM消息协议,所以你非常有必要认真读一下本博文的入门教程,并运行作者的Demo

本Demo以直播聊天室为假设的Demo,一般消息类型有50到100种左右,简单起见,这里举5种消息类型:

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

分别表示进入主播室离开主播室 文本消息 礼物图片或GIF动画消息及心跳包消息,其他如广告、系统广播等不在本Demo演示这是一个代码区块。

(本文要求读者有一定的socket和 swift3基础,再往下阅读)

摘要

1、制作协议格式

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

2、制作协议的对象数据

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核心代码其实只有两行,要发送消息时

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

收到消息时

UserInfo.parseFrom(data : msgData)

详细使用

1、环境安装
找到Github
https://github.com/alexeyxo/protobuf-Swift

在命令行中依次执行下面代码

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、导入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拖到工程中去(服务端和客户端可以同时使用同一个编绎出来的proto.swift)
如果服务端使用其他语言的话,则需要在第4步这里生成相应语言的文件,此例,我们服务端也是cocoapod集成

6、使用
一般情况下,IM服务器收到各个客户端的消息后,是将这些消息再转发给各个客户端(聊天室)或某个客户端(私聊)
心跳包的设计:服务端每隔一秒看一下有没有客户端的心跳包,而客户端是每隔10秒发一个心跳包,服务端因为连接的客户端数量大等压力原因所以开始子线程,而客户端就自己一个,所以没有必要开子线程
服务器处理各种消息也处理心跳包,针对每个客户端连接,如果服务端持续10秒以上(>10)没有再得到客户端的心跳包,那么就从服务器移除此客户端的消息监听并停止和销毁此客户端监听定时器

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

7、代码如下:

服务端代码
(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]()//所有连接上服务器的客户端  
}  

/* 
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) 内容 \(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)//点客户端的离开房间会调用此句,离开消息不会回传给此客户端  
            }  
        }  
          
        for c in (self?.clientMgrs ?? []) {  
            c.sendMsg(msgData)  
        }  
    }  
      
    clientMgr.removeClientCallback = {[weak self] (client) in  
        print(" 客户端连接数由 \(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 <实现代理方法>  
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 <定义协议>  
func removeClient(_ client : IMClientManager)  
func forwardMsg(_ client : IMClientManager, msgData : Data, isLeave : Bool)  
}  

class IMClientManager: NSObject {  
weak var delegate : IMClientManagerDelegate?//MARK:- 代理 2 <定义属性>  
  
//MARK:- 1 也可以使用闭包解决方案来实现服务器消息转发  
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) 内容 \(resultStr ?? "") --- ") 
            */  
              
            //各种Data转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//有心跳包,计数器清0  
                    }  
                    //关键代码  
                    continue//加这一句心跳包不需要转发给客户端  
                default:  
                    print("其他")  
            }  
              
            // 4.消息转发出去  
            self.delegate?.forwardMsg(self, msgData: lengthData + typedata + msgData, isLeave: type == 1)//MARK:- 代理 3 <使用代理>  
              
            //MARK:- 2 闭包方案  
            self.forwardMsgCallback?(self,lengthData + typedata + msgData,type == 1)  
        } else {//当关闭客户端时会调用这里,不是离开房间,是断线  
            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 = "正在监听客户端连接请求。。。"  
    mgr.startRunning("0.0.0.0", 9898)  
    stopBtn.isEnabled = true  
    startBtn.isEnabled = false  
}  
  
  
@IBAction func stopRunning(_ sender: NSButton) {  
    tipLabel.stringValue = "停止监听新的客户端连接请求。"  
    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 {//传递的事件比较多或各种情况分别处理时一般用代理设计模式  
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是阻塞式的,应该放在全局中去执行  
        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中转)  
                    self.handleMsg(type, msgData: msgData)  
                }  
            }else{  
                print("有情况,服务器当了")  
            }  
        }  
    }  
}  
  
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发送  
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)  {//发送的消息处理后由tcpClient : TCPClient 正式发送  
    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("连接到服务器成功")  
        //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 请自由发挥  
}  
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)
先运行服务端,点界面的启动,然后开启客户端

2、TCPClient.read是阻塞式的,应该放在全局线程中去执行

3、发送时都是序列化Data发送,读到消息都是反序列成协议对象再处理,具体场景:某个客户端送主播一游艇,客户端把礼物消息序列化发送给服务器,服务器转发给同一主播室的其他各个客户端(让大家看到某人送给主播一游艇),
** 各个客户端收到消息后,则反序列化这一消息,并解析出其中的各个成员对象及属性后呈现到界面上(某人的头像,游艇的图片,GIF动画等)**

4、代理换成闭包形式设计时,注意闭包中的self的循环引用

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)//点客户端的离开房间会调用此句,离开消息不会回传给此客户端  
            }  
        }  
          
        for c in (self?.clientMgrs ?? []) {  
            c.sendMsg(msgData)  
        }  
    }  
      
    clientMgr.removeClientCallback = {[weak self] (client) in  
        print(" 客户端连接数由 \(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、代码执行过程
(1)
点击客户端的离开房间会调用闭包中的

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**)**//**点客户端的离开房间会调用此句
            }
        }
        
        for c in (self?.clientMgrs?? []) {
            c.sendMsg(msgData)
        }
    }

但是服务端收到客户端的心跳包仍然继续执行,服务器会打印
** --- 心跳包****长度28,****类型 8 --- **
但不会回发消息(心跳包)给客户端

(2)
当人为继线(下线、关闭客户端)时,会调用startReadMsg中的 isClientRunning = false
else {//当关闭客户端时会调用这里,不是离开房间,是断线

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

服务器也收就收不到此客户端的心跳包,也不会打印心跳包

提醒:若在服务端startReadMsg中的心跳包处理处加上不转发代码 continue则客户不显示心跳包三个字,[测试]期间可以去掉下面的关键代码
**//关键代码**
**continue//加这一句心跳包不需要转发给客户端**

7、UI处理放main处理

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

8、定时器一般要放在其他代码的后面
如服务端的心跳包

    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、服务端在移除某个客户端监听时

   self?.clientMgrs.remove(at: index)  
   一般需要同时关闭TCPClient,代码如下
   client.**tcpClient.close()**

11、总结

客户端做的事:
(1)建立与服务器的连接
(2)发送消息(data()序列化)
(3)收到消息回显到UI上,进行处理(反序列消息成对象及属性,呈现到客户端的界面上)
(4)建立心跳包

服务端做的事:
转发所有客户端的消息,把心跳停止的客户端移开

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,905评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,140评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,791评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,483评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,476评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,516评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,905评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,560评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,778评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,557评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,635评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,338评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,925评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,898评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,142评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,818评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,347评论 2 342

推荐阅读更多精彩内容