iOS:protocolBuffer + ysocket 实现即时通讯

protocolBuffer + ysocket 实现即使通讯

  • ysocket : 在Swift中使用socket编程使用的库,基于BSD-socket(一个C语言写的)
  • protocolBuffer(PB) : 一种数据交换方式,在即时通讯传输数据的过程当中使用JSON因为反序列化和序列化比较繁琐,使用这种方式可以直接由data->对象,对象->data,并且消息大小只有json的十分之一.并且通过proto文件可以跨平台生成其它多种类型对象文件.
  1. 服务端

      class ServerManager {
     // 传入IP和端口号,控制服务开启接受的类
     fileprivate lazy var serverSocket = TCPServer(addr: "0.0.0.0", port: 7878)
     // 一个服务对应多个客户端,要有一个存储客户端的数组
     fileprivate lazy var clientMrs = [ClientManager]()
     // 服务器是否开启
     fileprivate var isServerRunning = false
     }
    

开启服务的函数

func startRunning() {
    serverSocket.listen()
    isServerRunning = true
    DispatchQueue.global().async {
        while self.isServerRunning {
            if let client = self.serverSocket.accept() {
                DispatchQueue.global().async {
                    let clientManager = ClientManager(client: client)
                    self.clientMrs.append(clientManager)
                }
            }
        }
    }
}

1.serverSocket.listen()

2.self.serverSocket.accept() 此方法要放到一个死循环里,时刻接收连接到服务器的客户端TCPClient.并且加入到数组中..注意这里一定要在gloabl并发队列里,因为还要继续接收其它的客户端的接入.

3.拿到clint.调用client.read(expectlen: Int)按照字节长度获取信息..第一次获取到的是[UInt8],然后转换为Data,再然后是String.

  1. 客户端

    在客户端要创建一个socket类来管理发送与接收消息的方法

     class HYSocket {
     fileprivate var tcpClient : TCPClient
     init(addr : String, port : Int) {
         tcpClient = TCPClient(addr: addr, port: port)
      }
     }
    

TCPClient有一个方法 tcpClient.connect(timeout: 5)
返回一个布尔值,如果端口和IP存在,即可连接成功.

发送消息可以通过string也可以data
tcpClient.send(data:Data)
在客户端调用这个方法的时候,服务端的accept中就会监听到发送消息的client,read所携带的信息.

这样最简单的一个字符串就可以做到了.但是实际项目中的情况要复杂很多.主要是要传输的数据复杂,并且在服务器接受到消息之后还要再发送给客户端.一个用户加入了直播间,其它用户也要看到..在用户加入直播间时候还要告诉服务器是哪一个用户,用户的姓名,等级,头像等等.发礼物时候要告诉服务器礼物的名称,个数.

这些信息用一个json转成data当然可以进行传输,但是序列化与反序列化以及再用json转对象的过程复杂而且信息量大.

ProtocolBuffer使用

接下来这件事不一定是由前端开发人员自己来做的.最终我们需要一个关于IM中的对象以及对应属性的swift文件.

新建一个后缀名为.proto的文件,protocolBuffer有其规范来声明数据类型,方便转换传输,此次直播项目中的proto文件中是这样的.

syntax = "proto2";

message UserInfo {
    required int64 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;
}

接着在终端 protoc person.proto --swift_out="./"
生成一个.swift文件. 加入到工程中..打开一看1000多行代码.

接下来在其它类中我们可以就有了UserInfo.ChatMessage以及GiftMessage这三个类并且可以对其属性赋值了.

还有一个点就是: 服务端在读取socket传来的消息的时候需要指定读取的字节长度,读少了会读取不全,读多了会产生多余消耗.所以在客户端发送消息的时候回传三个东西: "消息长度" + "消息类型(文本/礼物/进入房间)" + "消息对象"

在服务端读取的时候也要按照这个规定进行有序读取,消息长度和类型一般固定字节数,这与客户端发送消息时候的字节数要统一.

发送消息(客户端):

封装一个统一的发送函数

   private func sendMsgToServer(data:Data, type: Int) {

    // 消息长度
    var length = data.count
    let lengthData = Data(bytes: &length, count: 4)
    //消息类型
    var type = type
    let typeData = Data(bytes: &type, count: 2)
    
    //总消息
    let totalData = lengthData + typeData + data
    client.send(data: totalData)
}

接下来是发送文本消息的函数

func sendTextMsg(text: String) {
    let chatMsg = ChatMessage.Builder()
    chatMsg.user = (try! userinfo.build())
    chatMsg.text = text
    let msgData = (try! chatMsg.build()).data()
    sendMsgToServer(data: msgData, type: 2)
}

步骤:

  1. 声明一个在Proto文件中声明的ChatMsg消息对象,对其赋值(requeired声明的属性必须要赋值.不然转data会失败. 2.protocolBuffer提供给了我们直接转化为Data的方法,再调用client.send,发送到服务器就可以了.

接收消息(客户端):

服务器会将某一用户进入房间的消息全部返回给这个房间里的客户端.
所以在客户端Socket类中也要有一个方法时刻接收服务器发来的消息

func startReadMsg() {
    DispatchQueue.global().async {
        while true {
            guard let lMsg = self.tcpClient.read(4) else {
                continue
            }
            // 1.读取长度的data
            let headData = Data(bytes: lMsg, count: 4)
            var length : Int = 0
            (headData as NSData).getBytes(&length, length: 4)
            
            // 2.读取类型
            guard let typeMsg = self.tcpClient.read(2) else {
                return
            }
            let typeData = Data(bytes: typeMsg, count: 2)
            var type : Int = 0
            (typeData as NSData).getBytes(&type, length: 2)
            
            // 2.根据长度, 读取真实消息
            guard let msg = self.tcpClient.read(length) else {
                return
            }
            let data = Data(bytes: msg, count: length)
            
            // 3.处理消息
            DispatchQueue.main.async {
                self.handleMsg(type: type, data: data)
            }
        }
    }
}

接受到的消息我们需要根据传过来的type进行区分是哪种对象.例如刚才传的ChatMessage的类型是2.那么判断在type==2的时候直接获取chatMessage对象.
let chatMsg = try! ChatMessage.parseFrom(data: data)
放在proto中的对象会有这个反序列的函数..

由此: 发送消息和接收消息都通过protocolBuffer方便的完成了.从data->对象,对象->data,接下来根据具体需求做一些对应的封装,将消息用代理发送到控制器里就好了

ProtocolBuffer环境安装

环境安装

客户端集成(通过cocoapods)

use_frameworks!

pod 'ProtocolBuffers-Swift'

ProtocolBuffer的使用

  • 创建proto文件
  • 在项目中,创建一个(或多个).proto文件
  • 之后会通过该文件, 自动帮我们生成需要的源文件(比如C++生成.cpp源文件, 比如java生成.java源文件, Swift就生成.swift源文件)
  • 代码规范
    syntax = "proto2";

    message Person {
     required int64 id = 1;
     required string name = 2;
     optional string email = 3;
    }
  • 具体说明
  • syntax = "proto2"; 为定义使用的版本号, 目前常用版本proto2/proto3
  • message是消息定义的关键字,等同于C++/Swift中的struct/class,或是Java中的class
  • Person为消息的名字,等同于结构体名或类名
  • required前缀表示该字段为必要字段,既在序列化和反序列化之前该字段必须已经被赋值
  • optional前缀表示该字段为可选字段, 既在序列化和反序列化时可以没有被赋值
  • repeated通常被用在数组字段中
  • int64和string分别表示整型和字符串型的消息字段
  • id和name和email分别表示消息字段名,等同于Swift或是C++中的成员变量名
  • 标签数字1和2则表示不同的字段在序列化后的二进制数据中的布局位置, 需要注意的是该值在同一message中不能重复

定义有枚举类型Protocol Buffer消息

enum UserStatus {
OFFLINE = 0;  //表示处于离线状态的用户
ONLINE = 1;   //表示处于在线状态的用户
}

message UserInfo {
    required int64 acctID = 1;
    required string name = 2;
    required UserStatus status = 3;
}

定义有类型嵌套

enum UserStatus {
OFFLINE = 0;
ONLINE = 1;
}
message UserInfo {
    required int64 acctID = 1;
    required string name = 2;
    required UserStatus status = 3;
}

message LogonRespMessage {
    required LoginResult logonResult = 1;
    required UserInfo userInfo = 2;
}

代码编写完成后, 生成对应语言代码

protoc person.proto --swift_out="./"

服务器集成

因为服务器使用Mac编写,不能直接使用cocoapods集成
因为需要将工程编译为静态库来集成
到Git中下载整个库

执行脚本: ./scripts/build.sh 添加: ./src/ProtocolBuffers/ProtocolBuffers.xcodeproj到项目中

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

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,566评论 18 139
  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 11,982评论 4 60
  • 点击查看原文 Web SDK 开发手册 SDK 概述 网易云信 SDK 为 Web 应用提供一个完善的 IM 系统...
    layjoy阅读 13,618评论 0 15
  • 那年秋天,我第一次见他 你还记得吗?你对我说“姑娘,你很蠢”? 第一回;秋日 风,萧瑟的吹着。人...
    残小梦camille米悠小姐阅读 485评论 1 1
  • 首先,开始学习React的时候,可以牛刀小试一下,在浏览器端,在官方网站https://facebook.gith...
    Simple_Learn阅读 910评论 0 0