Skynet(3)-Sproto协议的使用 

                                                   Skynet(3)-Sproto协议的使用   

文章整理和来源:https://github.com/cloudwu/skynet/wiki/Sproto

1.首先看下Sproto的协议的格式: 

.Person {
    name 0 : string
    id 1 : integer
    email 2 : string

    .PhoneNumber {
        number 0 : string
        type 1 : integer
    }

    phone 3 : *PhoneNumber
}

.AddressBook {
    person 0 : *Person
}

我们可以看到,凡以“.”开头定义的皆为一种自定义的类型,例如这里的.Person,.AddressBook都是一种类型,而且类型之中可以包含其他的类型。而 “ * ” 加变量名代表一个数组。Sproto中支持以下5种基本类型。

  • string : string
  • binary : binary string (it's a sub type of string)
  • integer : integer, the max length of an integer is signed 64bit. It can be a fixed-point number with specified precision.
  • double : double, floating-point number.
  • boolean : true or false

我们再看一个符合这个协议的sproto数据:

local ab = {
    person = {
        {
            name = "Alice",
            id = 10000,
            phone = {
                { number = "123456789" , type = 1 },
                { number = "87654321" , type = 2 },
            }
        },
        {
            name = "Bob",
            id = 20000,
            phone = {
                { number = "01234567890" , type = 3 },
            }
        }
    }
}

2.Parser(解析器):
local parser = require "sprotoparser"

parser .parse将sproto架构解析为二进制字符串。
解析sproto架构需要解析器。你可以利用它离线将spoto数据生成二进制字符串,
程序运行时就不再需要sproto数据和解析器。

3.Lua API(这部分没啥好讲的,直接从云风的wiki里面copy过来了)

local sproto = require "sproto"
local sprotocore = require "sproto.core" -- optional
  • sproto.new(spbin) creates a sproto object by a schema binary string (generates by parser).
  • sprotocore.newproto(spbin) creates a sproto c object by a schema binary string (generates by parser).
  • sproto.sharenew(spbin) share a sproto object from a sproto c object (generates by sprotocore.newproto).
  • sproto.parse(schema) creares a sproto object by a schema text string (by calling parser.parse)
  • sproto:exist_type(typename) detect whether a type exist in sproto object.
  • sproto:encode(typename, luatable) encodes a lua table with typename into a binary string.
  • sproto:decode(typename, blob [,sz]) decodes a binary string generated by sproto.encode with typename. If blob is a lightuserdata (C ptr), sz (integer) is needed.
  • sproto:pencode(typename, luatable) The same with sproto:encode, but pack (compress) the results.
  • sproto:pdecode(typename, blob [,sz]) The same with sproto.decode, but unpack the blob (generated by sproto:pencode) first.
  • sproto:default(typename, type) Create a table with default values of typename. Type can be nil , "REQUEST", or "RESPONSE".  

4.RPC API:
所谓RPC,就是远程过程调用,我们通常客户端与服务端的数据传输都会用到这个,Sproto提供了RPC的接口,这部分比较实用。
为了帮助我们理解,先来听听Sproto作者云风在博客中对Sproto RPC的描述:
https://blog.codingnow.com/2015/04/sproto_rpc.html

首先我们需要定义一个消息包的主体格式。它必须有一个叫 type 的字段,描述 RPC 到底是哪一条消息。还需要有一个 session 字段来表示回应消息的对应关系。通常这两个字段都被定义成 integer 。

.package {
	type 0 : integer
	session 1 : integer
}

使用 sproto 的 rpc 框架,每条消息都会以这条消息开头,接上真正的消息内容;连接在一起后用 sproto 的 0-pack 方式打包。注意,这个数据包并不包含长度信息,所以真正在网络上传输,还需要添加长度信息,方便分包,也就是说,实际我们传送的包都是(数据长度+消息包)。当然,如果你使用 skynet 的 gate 模块的话,约定了以两字节大端表示的长度加内容的方式分包。

(1)构造一个 sproto rpc 的消息处理器,应使用:

-- packagename 默认值为 "package" 即对应前面的 .package 类型。你也可以起别的名字。
local host = sproto:host(packagename)  

这条调用会返回一个 host 对象,用于处理接收的消息。host对象通过调用dispatch处理消息包,返回请求类型(注意不是消息类型,而是“REQUEST ”或“RESPONSE ”)以及具体的内容,然后我们根据请求类型来自行处理。

host:dispatch(msgcontent)

这里的 msgcontent 也是一个字符串,或是一个 userdata(指针)加一个长度。它应符合上述的以 sproto 的 0-pack 方式打包的包格式。

dispatch 调用有两种可能的返回类别,由第一个返回值决定:

  • REQUEST : 第一个返回值为 "REQUEST" 时,表示这是一个远程请求。如果请求包中没有 session 字段,表示该请求不需要回应。这时,第 2 和第 3 个返回值分别为消息类型名(即在 sproto 定义中提到的某个以 . 开头的类型名),以及消息内容(通常是一个 table );如果请求包中有 session 字段,那么还会有第 4 个返回值:一个用于生成回应包的函数。

  • RESPONSE :第一个返回值为 "RESPONSE" 时,第 2 和 第 3 个返回值分别为 session 和消息内容。消息内容通常是一个 table ,但也可能不存在内容(仅仅是一个回应确认)。

(2)对外发送请求(一般指客户端向服务端发送请求或服务端向客户端发起请求),应使用attach:

local sender = host:attach(sp)  -- 这里的 sp 是向外发出的消息协议定义。

attach 可以构造一个打包函数,用来将对外请求打包编码成可以被 dispatch 正确解码的数据包。这个生成的函数可以将 type session content 三者打包成一个串,这个串可以被对方的 host:dispatch 正确处理。

这个 sender 函数接受三个参数(name, args, session)。name 是消息的字符串名(其实就是消息的类型,如登录消息,授权消息等)、args 是一张保存用消息内容的 table ,而 session 是你提供的唯一识别号,用于让对方正确的回应。 当你的协议不规定需要回应时,session 可以不给出。同样,args 也可以为空。

类似Protocol Buffers(但不同于json),sproto消息是强类型的,并且本身不是自描述的。必须用特殊语言定义消息结构。

可以使用sprotoparser库将模式文本解析为二进制字符串,这样sproto库就可以使用它。可以脱机(离线)解析它们并保存字符串,也可以在程序运行期间解析它们。

架构文本如下:

.Person {	# . means a user defined type 
    name 0 : string	# string is a build-in type.
    id 1 : integer
    email 2 : string

    .PhoneNumber {	# user defined type can be nest.
        number 0 : string
        type 1 : integer
    }

    phone 3 : *PhoneNumber	# *PhoneNumber means an array of PhoneNumber.
    height 4 : integer(2)	# (2) means a 1/100 fixed-point number.
    data 5 : binary		# Some binary data
    weight 6 : double   # floating number
}

.AddressBook {
    person 0 : *Person(id)	# (id) is optional, means Person.id is main index.
}

foobar 1 {	# define a new protocol (for RPC used) with tag 1
    request Person	# Associate the type Person with foobar.request
    response {	# define the foobar.response type
        ok 0 : boolean
    }
}

当然如果要使用自描述的sproto协议也是可以的:

.type {
    .field {
        name 0 : string
        buildin	1 : integer
        type 2 : integer	# type is fixed-point number precision when buildin is SPROTO_TINTEGER; When buildin is SPROTO_TSTRING, it means binary string when type is 1.
        tag 3 : integer
        array 4	: boolean
        key 5 : integer # If key exists, array must be true, and it's a map.
    }
    name 0 : string
    fields 1 : *field
}

.protocol {
    name 0 : string
    tag 1 : integer
    request 2 : integer # index
    response 3 : integer # index
    confirm 4 : boolean # response nil where confirm == true
}

.group {
    type 0 : *type
    protocol 1 : *protocol
}


5.Sproto Loader

由于 skynet 采用的是多 lua 虚拟机。如果在每个 VM 里都加载相同的 sproto 协议定义就略显浪费。所以 skynet 还提供了一个叫 sprotoloader 的模块来共享它们。

其实现原理是在 C 模块中提供了 16 个全局的 slot ,可以通过 sprotoloader.register 或 sprotoloader.save 在初始化时,加载需要的协议,并保存在这些 slot 里。通常我们只需要两个 slot ,一个用于保存客户端到服务器的协议组,另一个用于保存服务器到客户端的协议组。分别位于 slot 1 和 2 。

这样,在每个 vm 内,都可以通过 sprotoloader.load 把协议加载到 vm 中。

注意:这套 api 并非线程安全。所以必须自行保证在初始化完毕后再做 load 操作。(load 本身是线程安全的)。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值