Skip to content
Go back

使用Lua协程处理客户端网络消息

Edit page

问题

客户端和服务器交互时通常采用的是注册回调的方式,因为发送消息接受消息的代码是分离的,客户端并不保存发送消息的上下文。如果客户端要做一个关联请求,就会因为缺少已经发送消息的状态,需要服务器辅助转发,这样即浪费网络流量,代码编写起来也不简洁。借助lua协程可以把异步回调转换成同步的书写方式,可以解决这一问题。下面是代码实现。

local co_create = coroutine.create
local co_running = coroutine.running
local co_yield = coroutine.yield
local co_resume = coroutine.resume

local function coresume(co, ...)
    local ok, err = co_resume(co, ...)
    if not ok then
        error(debug.traceback(co, err))
    end
    return ok, err
end

local expect_queue = {}

local expect = {}

function expect.expect(cmd, if_fn)
    table.insert(expect_queue, {cmd = cmd,fn = if_fn, co = co_running()})
    return co_yield()
end

function expect.dispatch(cmd, msg)
    ---如果发生错误操作,终止所有等待
    if cmd == "S2CErrorCode" then
        for _, v in ipairs(expect_queue) do
            local ok, err = xpcall(coresume, debug.traceback, v.co, false, msg)
            if not ok then
                print(err)
            end
        end
        expect_queue = {}
        return false
    end

    for _, v in ipairs(expect_queue) do
        if v.cmd == cmd then
            if v.fn then
                msg = v.fn(msg)
            end

            if msg then
                table.remove(expect_queue, _)
                coresume(v.co, msg)
                return true
            end
        end
    end
    return false
end

local co_num = 0

---for coroutine reused
local co_pool = setmetatable({}, {__mode = "kv"})

local function routine(fn)
    local co = co_running()
    while true do
        co_num = co_num + 1
        fn()
        co_num = co_num - 1
        co_pool[#co_pool + 1] = co
        fn = co_yield()
    end
end

function expect.async(fn)
    local co = table.remove(co_pool)
    if not co then
        co = co_create(routine)
    end
    local _, res = coresume(co, fn)
    if res then
        return res
    end
    return co
end

return expect

使用,以一个客户端请求玩家初始化数据为例:

local expect = require("expect")

---需要在你的消息处理的地方调用先这个函数
---expect.dispatch
---返回false,继续执行旧的消息处理,返回true直接return

do
    ---点击某个按钮 或者 初始化时获取玩家数据
    expect.async(function()
        local sendmsg1 = {a=1,b=2}
        --send(sendmsg1) --发送消息
        ---等待收第1条消息
        print("receive", expect.expect("S2CPlayerData1"))
        --这里可以使用发送消息状态数据
        print(sendmsg1.a)
        ---等待收第2条消息
        print("receive", expect.expect("S2CPlayerData3"))
        ---等待收第3条消息
        print("receive", expect.expect("S2CPlayerData4"))
        ---等待收第5条消息
        print("receive", expect.expect("S2CPlayerData5"))
        ---上下文状态一直存在 这里依然可以访问 sendmsg1
    end)
end

Edit page

Previous Post
Lua 5.4 GC中的问题