sproto 是我自己设计, 用在我们新项目中取代过去用到的 google protocol buffers 的东西。
为什么不用 protobuf ? 这个问题我有足够的发言权。在 lua 语言为主的项目中,sproto 更合适。google 官方并没有给 protobuf 加入 lua 支持。现在在网上流传的 protobuf lua 方案,被人用的最多的两种,一个是 pbc 的 lua binding ,另一个是 protoc-gen-lua 。前者是我在开发维护,并使用了多年;后者是在我过去的项目中,项目中的同事因为需要而开发的。
另外,在我的项目的副产品中,还有开源的 protobuffer 的 as3 库以及 erlang 库,都有许多用户。所以,我相信我对 protobuf 有足够长时间的使用经验以及对它有足够的了解。这也是放弃 protobuf 而转向自己设计的 sproto 的底气所在。
在这一篇 blog 中,不想讨论 protobuf 的优劣,只谈谈 sproto 中如何使用 rpc 的 api 。这是 sproto 的 api 文档中没有写明,而很多想用它的同学问起的问题。
对于 request/response 的 RPC 方案,除了消息本身打包外,还有两个重要的信息需要传输。它们分别是请求的类型以及请求的 session 。
不要把请求的类型和消息的类型混为一谈。因为不同的请求可以用相同的消息类型,所以在 sproto 中,需要对 rpc 请求额外编码。你也不一定为每个请求额外设计一个消息类型,可以直接在定义 rpc 协议时内联写上请求(以及回应)的消息结构。
通常,我们用数字作为消息类型的标识,当然你也可以使用字符串。在用类 json 的无 schema 的协议中使用字符串多一些,但在 sproto 这种带 schema 的协议中,使用数字会更高效。同样,session 作为一条消息的唯一标识,你也可以用数字或字符串。而生成唯一数字 session 更容易,编码也更高效。
所以,每当我们发送一次远程请求,需要传输的数据就有三项:请求的类型、一个请求方自己保证唯一的 session id 以及请求的数据内容。
服务方收到请求后,应根据请求的类型对请求的数据内容解码,并根据类型分发给相应的处理器。同时应该把 session id 记录下来。等处理器处理完毕后,根据类型去打包回应的消息,并附加上 session id ,发送回客户端。
注意:回应是不需要传输消息类型的。这是因为 session id 就唯一标识了这是对哪一条请求的回应。而 session id 是客户端保证唯一的,它在产生 session id 时,就保存了这个 session 对应的请求的类型,所以也就有能力对回应消息解码。
btw ,如果只是单向推送消息(也就是 publish/subscribe 模式),直接省略 session 就可以了,也不需要回应。
sproto 提供的第一种 rpc 模式封装了上面的流程。
你需要定义一个叫做 package 的消息类型,里面包含 type 和 session 两项。
对于每个包,都以这个 package 开头,后面接上 (padding)消息体。最后连在一起,用 sproto 自带的 0-pack 方式压缩。
你可以用 sproto:host 这个 api 生成一个消息分发器 host ,用来处理上面这种 rpc 消息。
默认每个 rpc 处理端都有处理请求和处理回应的能力。也就是每个 rpc 端都同时可以做服务器和客户端。所以 host:dispatch 这个 api 可以处理消息包,返回它是请求还是回应,以及具体的内容。
如果 host 要对外发送请求,它可以用 host:attach 生成一个打包函数。这个生成的函数可以将 type session content 三者打包成一个串,这个串可以被对方的 host:dispatch 正确处理。
具体的说明,我放在了 skynet 的 wiki 页 上。
sproto 另外还提供了一组没有那么多封装的 rpc api 。它们分别是:
sproto:request_encode
sproto:request_decode
sproto:response_encode
sproto:response_decode
顾名思义,这组 api 不会帮你处理 type session 这些信息,而是留给你处理。它只是在你知道一条消息的内容在已知是请求还是回应包时,可以调用对应的 api 来编解码。
比如,当你的服务器只处理请求的话,就只需要调用 sproto:request_decode
。当然,你得自己先知道请求的 type 是什么(编码在额外的地方),也需要自己保存下 session ;需要回应请求时,调用 sproto:response_decode
即可。不过附带上前面保存的 session 也是你的责任。
设计这组 api 是源于 skynet 的 message server 模板。因为这个模板已经封装好了 session 。在模板上使用 sproto 的前一种 api 就不太合适了。