swift-vapor socket编程之protobuf消息类
最近在研究iOS socket编程,由于我的服务器是基于vapor2.0搭配最新腾讯云低配unbuntu 16.04系统,只有1M带宽,理论上传/下载速度可以达到128kb/s,需要用到protocol降低传输数据体积,节省带宽
什么是protocolbuf?
protocol buffer是google的一个开源项目,它是用于结构化数据串行化的灵活、高效、自动的方法,例如XML,不过它比xml更小、更快、也更简单。你可以定义自己的数据结构,然后使用代码生成器生成的代码来读写这个数据结构。你甚至可以在无需重新部署程序的情况下更新数据结构。
这些都不是重点,重点是基于protocol buffer传输的二进制数据体积小,是一般json的1/5,实测小数据只有json的1/3,大数据未测试
那么问题来了, 基于1M带宽,下载速度128kb/s,这里我理解为服务器同时给客服端发送数据每秒最大128kb,如果建立一个聊天室,假设可以同时容纳300人在线流畅聊天,使用json传输数据,保证不卡,那么使用protoco 传输数据最低可以保证在1000人以上同时在线聊天,并且不卡。在不提升带宽的情况下,是不是很给力。
具体文档和安装方法:https://github.com/google/protobuf
前往谷歌的protobuf github首页,上面有详细的安装过程,
因为我做的是swift服务器开发,所以前后端用的是apple官方开源的swift-protobuf
具体文档和安装方法:https://github.com/apple/swift-protobuf
好了,最后来说下,我在做socket编程中遇到的坑
使用protobuf传输数据,如何确定消息类型?
起初的解决方案是这样的:
var p = Person()
p.id = 99
p.title = "0.9.903"
p.author = "cailingyun"
let data = try! p.serializedData()
sendMsg(data)
func sendMsg(data : Data, type : Int) {
// 1.将消息长度, 写入到data
var length = data.count
let headerData = Data(bytes: &length, count: 4)
// 2.消息类型
var tempType = type
let typeData = Data(bytes: &tempType, count: 2)
// 3.发送消息
let totalData = headerData + typeData + data
tcpClient.send(data: totalData)
}
将消息的长度和消息类型转换成Data 然后一并拼接成data发送给服务器
ws.onBinary = { ws, data in
var length : Int = 0
let ms = data.array
let headData = Data(bytes: ms, count: 4)
(headData as NSData).getBytes(&length, length: 4)
print(length)
var type : Int = 0
let typeArr = data.array as! NSMutableArray
let t = typeArr.subarray(with: NSMakeRange(4,2)) as! [Byte]
let typeData = Data(bytes: t, count: 2)
(typeData as NSData).getBytes(&type, length: 2)
print(type)
let msgArr = typeArr.subarray(with: NSMakeRange(6,ms.count-6)) as! [Byte]
let msgData = Data(bytes: msgArr, count: ms.count-6)
let person = try! Person(serializedData: msgData)
print(p)
let pdata = Data(bytes: data, count: data.array.count)
room.send(data: pdata)
}
然后解析的时候根据你自己当初设定的长度分别解析消息长度和消息类型,这个方案确实可以成功解析,但由于我的服务器是基于swift vapor 部署在Ubuntu,Ubuntu虽然安装swift3.0环境和vapor2.0环境,但并不支持Foundation,说明确点,是不支持NS系列,比如NSString,NSData等,既然不支持,那么上面的代码在Ubuntu上面就不会编译通过。
后来也是谷歌、百度找了很多方案,都不尽人意,没能解决
比如把Data转换成int,或者吧UInt8转换成Int等等,试了诸多方案都未能成功,这是我在Google找到的Data转Int,或者UInt8转int方案,在swift3.0中都已经不太适用,下面仅供参考
For NSData:
var values = [UInt8](repeating:0, count:data!.length)
data.getBytes(&values, length: data!.length)
For Data:
var values = [UInt8](repeating:0, count:data!.count)
data.copyBytes(to: &values, count: data!.count)
let p = UnsafeMutablePointer<UInt32>.allocate(capacity: ((headData.count) / 4))
let ptr = UnsafeMutableBufferPointer<UInt32>(start: p, count: ((headData.count) / 4))
_ = headData.copyBytes(to: ptr)
let contentLen = CFSwapInt32BigToHost(UInt32(p.pointee))
print(contentLen)
let value = UInt32(bigEndian: headData.withUnsafeBytes { $0.pointee })
let value = Int(bigEndian: headData.withUnsafeBytes { $0.pointee })
print(value)
let bigEndianValue = headerArr.withUnsafeBufferPointer {
($0.baseAddress!.withMemoryRebound(to: UInt32.self, capacity: 1) { $0 })
}.pointee
let value = UInt32(bigEndian: bigEndianValue)
print(value)
var value : Int = 0
for byte in headerArr {
value = value << 8
value = value | Int(byte)
}
print(value)
最终解决办法
syntax = "proto3";
message ProtocType {
int64 ptype = 1;
}
syntax = "proto3";
message Person {
int64 ptype = 1;
string title = 2;
string author = 3;
}
我新建了一个.proto文件,里面只有一个字段ptype,这个字段就是用来解析消息类型了,然后在你所有的proto文件中都加上这个字段,并且每次发送消息之前都要给这个字段赋值,这个字段就代表你的消息类型,比如是文件类型是1,图片类型是2,礼物消息是3,普通消息是4等
![](https://img.haomeiwen.com/i1363418/b965111b5d52c89a.png)
然后服务器解析的时候,首先解析消息类型,用你刚才定义的解析消息类型的proto文件生成的.qb.swift文件里面的类去解析,
经过我的多次测试实验,解析很顺利,能够成功解析,确定消息类型,然后在用对应的类去解析对应的消息
以上就是在socket编程中使用protoc 传输数据确定消息类型方案,希望能帮到大家,少走弯路。