如何设计一个通讯协议

最近给 TKeyboard 增加了不少新功能,其中最有意义的部分是,通过蓝牙和 Wifi,配合 protobuf,在 Mac 和 iOS 之间建立了两条便捷的数据交换通道,算是打好了未来产品开发的地基,网络通道建立好了,后续的想象空间也大。

比如,现在可以将 iPhone 上 safari 正在浏览的网页,一键分享到 Mac 的 safari 上,比 handoff 更方便可靠。还可以在 Mac 的 safari 浏览网页时,如果发现的有趣的图片,一键保存到 iPhone 上。总之,两个端之间现在已经畅通无阻,文字,url,图片,等等各类数据可以随意分享,关键在于怎么去分析场景,因地制宜了。

在实现整个应用层通讯协议的过程中,觉得有些知识点可以分享,特写一篇文章和大家交流下。

首先大家可以尝试自己思考下,如果让你来设计两个端之间的通讯协议,你打算如何动手?这并不是个罕见的应用场景,现在不光是手游,不少移动端 App 都有需要 rpc 调用的场景。

应该会有一些同学会想到基于 http 的通讯框架,这也是现在不少 App 所采用的方案,App 端只需要定义好 url 中的 query string 或者是 body 体中的 json 格式,就可以实现一套应用层可用的通讯协议。

这确实是一个选择,这种选择我们可以归纳为 text based protocol,其好处是,http 已经是个偏向于应用层的非常成熟的通讯协议,一来一往,一个 request 对应一个 response,简单易用,http 本身已经做好了包的切割,包内部结构的定义等,应用层只需要从中取数据即可。但其缺点也明显,http 出于使用简单的考虑,将协议设计成一来一回,response 发完之后即断开请求,节省服务器资源,是一种无状态的协议,http 协议的使用者如果要做进一步的定制,需要采取一些奇技淫巧。而且基于 text 的协议,在协议解析的时候对于包里面的 text 内容都有严格限制。

大家都知道 http url 里面传入参数时,需要经过 url encode,其原因就是由于 http 是基于文本的协议,在解析 url 中的 query string 时,是以一些特殊的文本符号来做切割的,比如 ?,=,& 等。如果不把内容做 encode,就有可能导致协议解析出错。当然 http 协议的设计者替我们解决了这些问题,我们做为使用者可以高枕无忧。

http 方案另一个弊端在于流量上面,短连接每次重复握手,每个 request 里面有重复的 header 设置等,都会造成流量的浪费,这也是不少游戏客户端会建立自己的 socket 长连接通道的原因,自己设计基于流的通讯协议,这就是我们的另一个选择方案,stream based protocol。

TKeyboard 就是选择了基于 stream 的协议方案,CoreBlueTooth 上并没有现成的基于 http 的通讯方案,所以只能自己动手做个简单的设计,基于流的协议设计复杂度可大可小,端看应用场景。

工作量主要在于两方面,一是数据的序列化,即将我们平时所用的 model 转化为二进制流,其二是定义好包的格式,在通讯框架里做好包的切割,解析,和传递。最后简化调用流程,提供一套简单的类似 http 的双向数据调用 API 即可。

TKeyboard 的应用场景比较简单,所以我在设计上也做了不少简化,大致想大家介绍下思路。

第一个序列化的问题好解决,已有 google 的成熟方案 protobuf 可以使用,而且还有基于 Objective C 的版本,model 的序列化和反序列化,一个 API 调用即可完成。

第二个问题是包的格式定义。学习 TCP/IP 的意义在这里就能体现了,无论是 TCP 包还是 IP 包,都有自己的包格式定义,而且往往是一个 header 配合一个 payload(类似于 http 的 body)。之所以要有包,是因为二进制流只完成 stream 的传输,并不知道一次数据请求与相应的起始和结束,我们要预先定义好包结构才能做解析。这一步其实就是一个实现 RPC 调用的过程,关于 RPC 现在虽然还没有什么官方的协议通过,但是我们完全可以自己设计一个简单可用的协议版本。

要能实现包的准确切割,我们需要明确包的长度。所以必须在 header 中留一个字段,表达整个包(header + payload)由多少 bytes 构成,两个字节的长度就可以描述 0~65535 个字节数,具体使用多少个字节就看协议的使用场景了。

因为是 RPC 调用协议,所以包体里必须有调用的名称,即 API name 字段,这个 name 是可变长度,所以也需要将其长度信息加入包体中,原则上,所有可变长度的内容都需记录其精确的长度信息,否则无法做信息的切割。另外,调用方还需要知道包是请求的回应(response)还是另一端的通知(notify),所以我们还需要定义 call type 信息,这种信息一个 8 比特位的枚举量就绰绰有余了,这种固定长度的信息就不需要记录其长度信息了。

一般固定长度的信息我们放在 header 中,可变长度的信息我们则放入 payload 中,当然,我们 RPC 调用的具体参数(经由 protobuf 序列化之后的 stream)也是放入 payload 中,接收方接收以后,只需读取固定长度的字节,即可通过反序列化,再在接收方还原成具体的应用层数据。

做完这些,就差不多有一个简单的通讯协议雏形了。

当然这其中还有不少细节需要处理,比如 iOS 上基于 LEBT 的蓝牙方案,基于能耗考虑,发送方每次能发送的字节数是有限制的,据我测试,一个 Central 每次向 Pheripheral 发送数据的时候,最多只能写 512 个字节,所以 Central 端要做一个 send buffer,发送一次就去 buffer 中取 512 个字节,多了就会发送失败。Pheripheral 端接收数据也是一样,每次只能收到 20 个 bytes,需要设计一个 receive buffer,将数据全部缓存起来,每次收到数据就做一次解析,看数据量是否够一个最小包的大小,够就取出,不够就继续等待。

因为我们是在设计应用层协议,所以还需要考虑传输层是可靠还是不可靠,CoreBlueTooth 实际上既提供了类似于 TCP 的可靠传输(CBCharacteristicPropertyIndicate),也有类似于 UDP 的不可靠传输(CBCharacteristicPropertyNotify),不明白这一点,必然会踩坑。

还有不少其他问题要处理,比如数据的加解密,增加 CRC 做包的完整性校验,协议的版本控制,远程调用的超时机制等,这里就不一一赘述了,大家可以自己去尝试实现下,光说不练假把式,动过手,个中滋味自会明了。

最近所写的 TCP/IP 系列文章多是理论方面的知识,大家在学习理论的时候,最好能辅以代码上的实践,纸上得来终觉浅,抓住一切可能的机会多实践,才能加深理解。

全文完。

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

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,599评论 18 139
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,510评论 25 707
  • 转自:http://blog.csdn.net/kesonyk/article/details/50924489 ...
    晴天哥_王志阅读 24,771评论 2 38
  • 这些天“台湾女作家林奕含被性侵自杀”事件在网上引起热。她是无数成长在乌托邦的产物之一,而这次血淋淋的例子却在打着这...
    独白社阅读 344评论 0 0
  • 沉睡同泥土,一样芳香 向阳处,生命向外蔓延 暗地下,梦却与时间缠绵 紧拥,融于血髓,刻骨铭心 透明橱窗,缄默的骨架...
    寞霏阅读 181评论 0 2