sproto 是我设计的一个类 google protocol buffers 的东西。
在很多年前,我在我经手的一些项目中使用 google protocol buffers 。用了好几年,经历了几个项目后,我感觉到它其实是为静态编译型语言设计的协议,其实并没有脱离语言的普适性。在动态语言中,大家都不太愿意使用它(json 更为流行)。一个很大的原因是,protobuffers 是基于代码生成工作的,如果你不使用代码生成,那么它自身的 bootstrap 就非常难实现。
因为它的协议本身是用自身描述的,如果你要解析协议,必须先有解析自己的能力。这是个先有鸡还是先有蛋的矛盾。过去很多动态语言的 binding 都逃不掉引入负责的 C++ 库再加上一部分动态代码生成。我对这点很不爽,后来重头实现了 pbc 这个库。虽然它还有一些问题,并且我不再想维护它,这个库加上 lua 的 binding 依然是 lua 中使用 protobuffer 的首选。
protobuffer 自身有很长的历史,历史带来了包袱,实际上在我放弃 protobuffer 后不久,google 也大刀阔斧的更新了 3.0 版,对之前 2.x 版做了很多必要的,不兼容的改进。
我仿照 protobuffer 的基本理念设计了 sproto ,设计上的考虑在之前的 blog 中已经写了很多。
sproto 的设计理念之一:保证基本能用的基础上足够简单,不增加太多特性。如果要增加新特性,都是仔细考虑过,并保持不破坏兼容性。最近一段时间,我给 sproto 加了两个特性,这里简单介绍一下:
其一,sproto 没有内置的浮点数类型。因为我觉得大多数项目中,传输浮点数的必要性都不大。如果必须传输的话,可以用字符串,或二进制串来替代。由传输的双方来共同约定保证一致性。
但是,有时候传输小数的需求依然存在。我认为定点数可以是一种选择,而且不必破坏原有的协议,也不必增加新的类型。只需要在 sproto 的协议描述中加一些备注。
这次我选择在 integer 类型后面加一个备注,比如写 integer(2) 就表示,这个整数类型是一个有两位十进制小数位的定点数。两端在解析同样的协议时,需要在编码的时候 * 100 取整再编码,解码的时候 /100 还原。
如果有一端使用老版本的实现,那么可以在跟上层自行乘除 100 来兼容。
第二,sproto 用 string 类型来传输字符串和二进制串。它可以自然对应到 lua 中的 string 类型。但是,很多其它语言中,可阅读的 string 和 binary 串是两种不同的类型。其 string 类型需要额外指定编码。
sproto 缺乏 binary 串的描述,会在 python c# 等语言中遇到一些麻烦。
考虑之后,我决定给 sproto 增加 binary 类型,但为了兼容,把它作为 string 的一个子类型实现。在新版的 sproto 中,你可以在协议文件中定义 binary 字段。但编码时,还是按 string 编码的,不会破坏兼容性。
但新版本的代码在解码的时候,会根据协议定义,正确的通知 binding 层,这是一个 binary 串。