Skip to content

服务端并发实现示例

lua-yar 是纯协议库,server:handle(spec) 无 I/O 依赖(callback 模式)、server.dispatcher:handle_message(data) 无 I/O、无 yield,可被任意协程直接调用。并发能力依赖宿主环境注入。本文档列出不同宿主环境下的并发 server 实现示例。

目录


原生 Lua(顺序)

标准 Lua + luasocket,单线程顺序 accept。仅供开发/测试。

HTTP

local Yar = require("yar")
local server = Yar.server.new({
    add = function(a, b) return a + b end,
})
server:listen("http://0.0.0.0:8888")
server:loop()

→ 源码:example/server_http.lua

TCP

local Server = require("yar.server")
local server = Server.new({
    add = function(a, b) return a + b end,
})
server:listen("tcp://0.0.0.0:9999")
server:loop()

→ 源码:example/server_tcp.lua

loop() 为单线程顺序 accept,一个慢请求会阻塞所有连接。


原生协程

coroutine + socket.select,不依赖 copas。每个连接在独立协程内调度,socket 设为非阻塞,遇 timeoutyield 让出控制,主循环用 socket.select 检测就绪后 resume

local socket = require("socket")
local Server = require("yar.server")

local server = Server.new({ add = function(a, b) return a + b end })
server.protocol = "http"  -- 宿主模式下默认 "tcp",HTTP 需显式设置

-- 协程友好的 socket 包装器:非阻塞 I/O 遇 "timeout" 时 yield
local function wrap_socket(sock)
    sock:settimeout(0)
    return {
        receive = function(_, pattern)
            while true do
                local data, err = sock:receive(pattern)
                if data then return data end
                if err == "timeout" then coroutine.yield("read")
                else return nil, err end
            end
        end,
        send = function(_, data)
            -- 同理,遇 timeout 时 yield("write")
        end,
        -- ...
    }
end

-- 主循环:socket.select 检测读/写就绪,resume 对应协程
-- handle 内部按 YAR 帧读取、派发、回写
server:handle({ socket = wrap_socket(client) })

约 70 行实现 copas 的等价核心,展示 Lua 原生协程调度原理。

→ 源码:example/server_coroutine.lua


copas

copas 是基于 luasocket 的协程调度器,每个连接在独立协程内被调度,实现单线程多连接并发。

local copas  = require("copas")
local socket = require("socket")
local Server = require("yar.server")

local server = Server.new({ add = function(a, b) return a + b end })

local srv = socket.bind("0.0.0.0", 9999)
copas.addserver(srv, function(client)
    server:handle({ socket = client, keepalive = true })
    client:close()
end)
copas.loop()

copas 已将 client 包装为协程安全 socket,receive/send/close 兼容,直接传入 handle({ socket = ... }) 即可。

→ 安装:luarocks install copas → 源码:example/server_copas.lua


lua-eco

lua-eco 是基于 Lua 5.4 + epoll 的协程优先网络运行时。每个连接在独立协程内被调度。

local eco    = require("eco")
local socket = require("eco.socket")
local Server = require("yar.server")

local server = Server.new({ add = function(a, b) return a + b end })

-- 将 lua-eco socket 适配为 luasocket 兼容接口(receive/send/close)
local function adapt(eco_sock)
    return {
        receive = function(_, n)
            local data, err = eco_sock:recv(n)
            if not data then return nil, err end
            return data
        end,
        send = function(_, data)
            local ok, err = eco_sock:send(data)
            if not ok then return nil, err end
            return #data
        end,
        close = function() eco_sock:close() end,
    }
end

local srv = socket.listen_tcp("0.0.0.0", 9999)
while true do
    local client = srv:accept()
    if client then
        eco.run(function()
            local sock = adapt(client)
            server:handle({ socket = sock })
            sock:close()
        end)
    end
end

lua-eco 的 socket API(recv/send)与 luasocket 略有不同,需用 adapter 适配为 receive/send/close 接口。

→ 安装:luarocks install lua-eco → 源码:example/server_luaeco.lua


Skynet

Skynet 是基于 Actor 模型的服务端框架,socket API 为 fd-based(socket.listen/socket.start(fd, cb)/socket.read/socket.write)。

local skynet = require("skynet")
local socket = require("skynet.socket")
local Server = require("yar.server")

local server = Server.new({ add = function(a, b) return a + b end })

-- 将 Skynet fd-based socket 适配为 luasocket 兼容接口
local function adapt(fd)
    return {
        receive = function(_, n)
            local data = socket.read(fd, n)
            if not data or data == "" then return nil, "closed" end
            return data
        end,
        send = function(_, data)
            socket.write(fd, data)
            return #data
        end,
        close = function() socket.close(fd) end,
    }
end

skynet.start(function()
    local listen_fd = socket.listen("0.0.0.0", 9999)
    socket.start(listen_fd, function(fd, _addr)
        local sock = adapt(fd)
        server:handle({ socket = sock })
        sock:close()
    end)
end)

Skynet 的 fd-based API(socket.read(fd, n) / socket.write(fd, data) / socket.close(fd))需用 adapter 包装为 receive(n)/send(data)/close() 对象接口。

→ 安装:https://github.com/cloudwu/skynet → 源码:example/server_skynet.lua


OpenResty

OpenResty 通过 content_by_lua_block 处理请求,ngx.socket(cosocket)提供非阻塞 I/O + 连接池。

HTTP(content_by_lua)

location /api {
    content_by_lua_block {
        local Yar = require "yar"
        local server = Yar.server.new({
            add = function(a, b) return a + b end,
        })
        ngx.req.read_body()
        server:handle({
            method = ngx.req.get_method(),
            data = ngx.req.get_body_data() or "",
            writer = function(status, headers, body)
                ngx.status = status
                for k, v in pairs(headers) do ngx.header[k] = v end
                ngx.print(body)
            end,
        })
    }
}

TCP(stream 模块)

stream {
    lua_package_path "/path/to/lua-yar/src/?.lua;/path/to/lua-yar/src/?/init.lua;;";
    server {
        listen 9999;
        content_by_lua_block {
            require("example.server_openresty").serve()
        }
    }
}

serve() 内部通过 ngx.req.sock() 获取下游 cosocket,直接传入 handle({ socket = sock }),无需 adapter:

-- example/server_openresty.lua
function _M.serve()
    local sock, err = ngx.req.sock()
    if not sock then
        ngx.log(ngx.ERR, "failed to get downstream socket: " .. tostring(err))
        return
    end
    server:handle({ socket = sock })
    sock:close()
end

cosocket 的 receive/send/close 与 luasocket 接口一致,直接传入即可。

→ 源码:example/server_openresty.lua

生产级 OPM 部署

生产环境推荐使用 lua-resty-yar OPM 包,提供完整的 init/handler 分层:

模块 用途
example/resty_yar_init.lua init_by_lua 初始化:注入 cosocket + 创建进程级 Server 实例 + 透传 options
example/resty_yar_http_server.lua HTTP content_by_lua handler:读 body → handle(callback) → writer 输出
example/resty_yar_tcp_server.lua TCP stream content_by_lua handler:cosocket → handle(socket) + 连接保活
example/resty_yar_gateway.lua RPC 网关示例:服务端内部作为客户端调用下游服务(ngx.thread.spawn 并行)

生产环境

生产环境推荐直接使用 lua-resty-yar(OpenResty 绑定,开箱即用)。