RPC in chuck Lua

Time:2020-10-12

Chuck Lua has built-in RPC support based on coroutine. All remote method calls are as simple as calling local methods. Let’s take a simple example.

rpcserver.lua

local Task   = require("distri.uthread.task")
local Distri = require("distri.distri")
local Redis  = require("distri.redis")
local Socket = require("distri.socket")
local chuck  = require("chuck")
local RPC    = require("distri.rpc")
local Packet = chuck.packet

local config = RPC.Config(
function (data)                            --encoder
    local wpk = Packet.wpacket(512)
    wpk:WriteTab(data)
    return wpk
end,
function (packet)                          --decoder
    return packet:ReadTab()
end)

local rpcServer = RPC.Server(config)
rpcServer:RegService("hello",function ()
    return "world"
end)

local server = Socket.stream.Listen("127.0.0.1",8010,function (s,errno)
    if s then
        s:Ok(4096,Socket.stream.decoder.rpacket(4096),function (_,msg,errno)
            if msg then
                rpcServer:ProcessRPC(s,msg)
            else
                s:Close()
                s = nil
            end
        end)
    end
end)

if server then
    Distri.Signal(chuck.signal.SIGINT,Distri.Stop)
    Distri.Run()
end

rpcclient.lua

local Task   = require("distri.uthread.task")
local Distri = require("distri.distri")
local Redis  = require("distri.redis")
local Socket = require("distri.socket")
local chuck  = require("chuck")
local RPC    = require("distri.rpc")
local Packet = chuck.packet
local Sche  = require("distri.uthread.sche")

local config = RPC.Config(
function (data)                           --encoder                        
    local wpk = Packet.wpacket(512)
    wpk:WriteTab(data)
    return wpk
end,
function (packet)                         --decoder
    return packet:ReadTab()
end)

local rpcClient = RPC.Client(config)

local c = 0
if Socket.stream.Connect("127.0.0.1",8010,function (s,errno)
    if s then
        if not s:Ok(4096,Socket.stream.decoder.rpacket(4096),
                    function (_,msg,errno)
                        if msg then
                            c = c + 1
                            RPC.OnRPCResponse(config,s,msg)
                        else
                            print("close")
                            s:Close()
                            s = nil
                        end
                    end) 
        then
            return
        end
        rpcClient:Connect(s)
        for i = 1,100 do
            Task.New(function ()
                while true do
                    local err,ret = rpcClient:Call("hello")
                    if ret ~= "world" then
                        print("err")
                        break
                    end
                end
            end)
        end
    end
end) then
    Distri.Signal(chuck.signal.SIGINT,Distri.Stop)
    Distri.Run()
end

The first thing to understand isRPC.Config, which is the RPC configuration object:

local rpc_config = {}

function rpc_config:new(encoder,decoder,serializer,unserializer)
    if not encoder or not decoder then
        return nil
    end
    local o        = {}   
    o.__index      = rpc_config
    setmetatable(o, o)
    o. Decoder = decoder -- take the data out of the packet
    o. Encoder = encoder -- encapsulates data into packets using a specific packet format      
    o. Serializer = serializer -- serializes Lua table into transmission format, can be null
    o. Unserializer = unserializer -- deserialize the data in transmission format into Lua table, which can be null
    return o
end

Four important methods are provided, and this object will be passed to theRPC.ClientandRPC.ServerParameters that they will call.

In the example above, I only provideddecoderandencoderThe following is a brief introduction to the functions of these four methods

  • Serializer: the parameters and return values of the RPC call are wrapped in luatable. This function is used to serialize the wrapped table into a public transport format, such as JSON. Its output object will be provided to the encoder

  • Deserializer: in contrast to the serializer, deserializes the public transport format into luatable. Its output object will be provided to the decoder. If these two functions are not provided, luatable will be directly provided to the encoder / decoder

  • Encoder: encapsulates the transmission data into network packets for send

  • Decoder: extract the transmitted data from the network packet

In the above example, I use a built-in packet. Write luatable directly into the packet

Another point to note in the example is that,rpcClient:Call("hello")Must be used in the context of coroutine, otherwise an error will be returnedrpcServer:ProcessRPC(s,msg)If you need to perform some blocking calls in the RPC request processing function, such as requesting other remote methods or requesting redis data, you mustrpcServer:ProcessRPC(s,msg)It is executed under coroutine

If only RPC requests are involved in a link, a simple encapsulation of rpcclient can be done, which saves manual initialization steps every time, for example:

local WrapRPCClient = {}

function WrapRPCClient:new(config)
  local o = {}
  o.__index = actor      
  setmetatable(o,o)
  o.rpc    = RPC.Client(config)
  return o
end

function WrapRPCClient:Connect(host,port,on_success)
    local config = self.rpc.config
    if Socket.stream.Connect("127.0.0.1",8010,function (s,errno)
        if s then
            if not s:Ok(4096,Socket.stream.decoder.rpacket(4096),
                        function (_,msg,errno)
                            if msg then
                                RPC.OnRPCResponse(config,s,msg)
                            else
                                print("close")
                                s:Close()
                                s = nil
                            end
                        end) 
            then
                return
            end
            rpcClient:Connect(s)
            on_success()
        end
    end) then
        return true
    end 
end

function WrapRPCClient:Call(func,...)
    return self.rpc:Call(func,...)
end

With this encapsulation, you just callConnectWhen on_ After the success callback, you can call the remote method directly

https://github.com/sniperHW/chuck