Skynet.lua 阅读(1)
主要内容:
- skynet.dispatch_message skynet服务器消息处理的注册,及消息处理。
- proto 注册消息协议结构proto
- select 功能代码分析,及使用。并发同时调用多个skynet.call
proto 结构
proto在skynet存储对应协议的处理方式。
proto table结构内容填充如下:
proto = { -- 通过name 和 id 都能查找到对应协议的处理方式 lua = { -- name: 协议名称 name = "client", id = skynet.PTYPE_CLIENT, unpack = skynet.tostring, dispatch = function(fd, _, sMsg) assert(fd == client_fd) -- You can use fd to reply message skynet.ignoreret() -- session is fd, don't call skynet.ret recv_msg(sMsg) end, trace = true -- true or false }, skynet.PTYPE_LUA = { -- id: 协议的ID ... -- lua 相同内容 }, }
上述的proto结构的复制在
skynet.register_protocol
函数中赋值function skynet.register_protocol(class) local name = class.name local id = class.id assert(proto[name] == nil and proto[id] == nil) assert(type(name) == "string" and type(id) == "number" and id >=0 and id <=255) proto[name] = class proto[id] = class end
基于skynet服务器启动流程-阅读skynet.lua文件
skynet的服务器,基本模板如下:
local skynet = require "skynet" require "skynet.manager" skynet.init(function() skynet.register("test_service") end) skynet.start(function() skynet.dispatch("lua", function(session, source, cmd, ...) local f = assert(CMD[cmd], cmd) if 0==session then f(source, ...) else skynet.retpack(f(source, ...)) end end) end)
我个人使用日常喜欢,会调用skynet.init 注册服务名称。这段代码,重点阅读一下skynet.start接口执行内容,服务如何对外提供服务。
skynet.start函数分析
在skynet.start函数,分别执行如下步骤:
function skynet.start(start_func) c.callback(skynet.dispatch_message) init_thread = skynet.timeout(0, function() skynet.init_service(start_func) init_thread = nil end) end
- 调用c.callback注册消息回调处理函数
- 通过skynet.timeout执行一次skynet.init_service(start_func)
skynet.dispatch_message
函数分析
skynet服务在隧道消息后,将调用该函数处理消息;函数实现分为2部分:raw_dispatch_message 和 while循环。
function skynet.dispatch_message(...) -- 1. 第一部分 local succ, err = pcall(raw_dispatch_message,...) -- 2. 第二部分: 执行skynet.fork的任务 while true do if fork_queue.h > fork_queue.t then -- queue is empty fork_queue.h = 1 fork_queue.t = 0 break end -- pop queue local h = fork_queue.h local co = fork_queue[h] fork_queue[h] = nil fork_queue.h = h + 1 local fork_succ, fork_err = pcall(suspend,co,coroutine_resume(co)) if not fork_succ then if succ then succ = false err = tostring(fork_err) else err = tostring(err) .. "\n" .. tostring(fork_err) end end end assert(succ, tostring(err)) end
第一部分:raw_dispatch_message函数的使用():
local function raw_dispatch_message(prototype, msg, sz, session, source) -- skynet.PTYPE_RESPONSE = 1, read skynet.h -- 反馈处理:调用其他服务的返回值 if prototype == 1 then local co = session_id_coroutine[session] if co == "BREAK" then session_id_coroutine[session] = nil elseif co == nil then unknown_response(session, source, msg, sz) else local tag = session_coroutine_tracetag[co] if tag then c.trace(tag, "resume") end session_id_coroutine[session] = nil suspend(co, coroutine_resume(co, true, msg, sz, session)) end else -- 请求:收到其他服务器的调用请求 local p = proto[prototype] if p == nil then if prototype == skynet.PTYPE_TRACE then -- trace next request trace_source[source] = c.tostring(msg,sz) elseif session ~= 0 then c.send(source, skynet.PTYPE_ERROR, session, "") else unknown_request(session, source, msg, sz, prototype) end return end local f = p.dispatch if f then local co = co_create(f) session_coroutine_id[co] = session session_coroutine_address[co] = source -- 删除了trace处理代码 参看:https://blog.codingnow.com/2018/05/skynet_trace.html -- 唤醒携程处理请求 suspend(co, coroutine_resume(co, session,source, p.unpack(msg,sz))) else -- 没有对应的处理函数,返回失败。 trace_source[source] = nil if session ~= 0 then c.send(source, skynet.PTYPE_ERROR, session, "") else unknown_request(session, source, msg, sz, proto[prototype].name) end end end end
第二部分:while循环
- 代码中在执行
fork_queue
携程。fork_queue
来源于 skynet.fork,所以在执行其他服务器请求后,会去执行一遍fork的任务。
启动一个skynet lua服务,处理外部消息请求,是在
c.callback(skynet.dispatch_message)
注册了回调函数。并且在 lua服务中fork的任务触发是在执行一次消息调用后,才会被触发。并且lua服务,处理每一次请求的时候,都会创建一个新co携程处理。并设置成当前执行的携程。
lua服务,同一时间也只会运行一个任务携程,如果携程中有阻塞调用:例如 skynet.call 携程会让出。纯粹的逻辑处理,是有一定的原子性的。
request/select
的使用
需要连续执行多个不相干的call,可以使用改方法。关于单个服务内的异步操作 在skynet github 上有人提出了该需求。具体代码实现如下:
do ---- request/select local function send_requests(self) local sessions = {} self._sessions = sessions local request_n = 0 local err for i = 1, #self do local req = self[i] local addr = req[1] local p = proto[req[2]] assert(p.unpack) local tag = session_coroutine_tracetag[running_thread] if tag then c.trace(tag, "call", 4) c.send(addr, skynet.PTYPE_TRACE, 0, tag) end local session = c.send(addr, p.id , nil , p.pack(tunpack(req, 3, req.n))) if session == nil then err = err or {} err[#err+1] = req else sessions[session] = req watching_session[session] = addr session_id_coroutine[session] = self._thread request_n = request_n + 1 end end self._request = request_n return err end local function request_thread(self) while true do local succ, msg, sz, session = coroutine_yield "SUSPEND" if session == self._timeout then self._timeout = nil self.timeout = true else watching_session[session] = nil local req = self._sessions[session] local p = proto[req[2]] if succ then self._resp[session] = tpack( p.unpack(msg, sz) ) else self._resp[session] = false end end skynet.wakeup(self) end end local function request_iter(self) return function() if self._error then -- invalid address local e = tremove(self._error) if e then return e end self._error = nil end local session, resp = next(self._resp) if session == nil then if self._request == 0 then return end if self.timeout then return end skynet.wait(self) if self.timeout then return end session, resp = next(self._resp) end self._request = self._request - 1 local req = self._sessions[session] self._resp[session] = nil self._sessions[session] = nil return req, resp end end local request_meta = {} ; request_meta.__index = request_meta function request_meta:add(obj) assert(type(obj) == "table" and not self._thread) self[#self+1] = obj return self end request_meta.__call = request_meta.add function request_meta:close() if self._request > 0 then local resp = self._resp for session, req in pairs(self._sessions) do if not resp[session] then session_id_coroutine[session] = "BREAK" watching_session[session] = nil end end self._request = 0 end if self._timeout then session_id_coroutine[self._timeout] = "BREAK" self._timeout = nil end end request_meta.__close = request_meta.close function request_meta:select(timeout) assert(self._thread == nil) self._thread = coroutine_create(request_thread) self._error = send_requests(self) self._resp = {} if timeout then self._timeout = c.intcommand("TIMEOUT",timeout) session_id_coroutine[self._timeout] = self._thread end local running = running_thread coroutine_resume(self._thread, self) running_thread = running return request_iter(self), nil, nil, self end function skynet.request(obj) local ret = setmetatable({}, request_meta) if obj then return ret(obj) end return ret end end
创建requests的方式:
-- 方式一: local reqs = skynet.request { slave, "lua", "ping", 6, "SLEEP 6" } { slave, "lua", "ping", 5, "SLEEP 5" } { slave, "lua", "ping", 4, "SLEEP 4" } -- 方式二: local reqs = skynet.request() reqs:add { slave, "lua", "ping", i*10, "SLEEP " .. i, token = i }
方式1,通过原表的调用了add方法。在 request函数中,设置了 request_meta 为 reqs 原表。
request_meta.__call = request_meta.add 这样就可以不用明确调用add函数,简化代码。
请求执行
for req, resp in reqs:select(50) do info("RESP %s token<%s>", resp[1], req.token) end
-
代码分析数据结构
{ -- 数组存放请求参数 [1] = { slave, "lua", "ping", 6, "SLEEP 6" } metatable = { _thread = coroutine_create(request_thread) -- 请求等待携程 _error = send_requests(self) -- 请求发起, 保存请求执行发生的错误 _resp = {} -- 请求结果存放 _timeout = c.intcommand("TIMEOUT",timeout) -- 定时器会话ID(阅读skynet.timeout实现) } }
-
select 函数的执行
- 115 - 122行 初始化数据结构中相关字段的数据。
- 125行 唤醒
request_thread
携程,第一次唤醒,所以传入参数self,相当于函数调用传入的参数。
-
send_requests
函数执行-
17行 循环调用 c.send 发起请求。
-
请求发起成功后,在 22 - 25行保存相关信息,配合其他调用的判断。
sessions
存储了请求参数信息。self._sessions = sessions
。用与请求结果返回,request_iter
返回的第一个参数就是该值。调用这,能区分出请求结果的参数来源。watching_session
session_id_coroutine
skynet.lua 局部变量,单处说明使用。request_n
记录发起的请求数量,在request_iter
判断迭代结束。
-
-
request_thread
request_iter
函数配合使用request_thread
在收到返回后 48行调用skynet.wakeup(self)
唤醒request_iter
70行skynet.wait(self)
,这样迭代器就能返回数据了。request_thread
主要作用是等待请求结果,并将结果放入 self._resp 表中,然后唤醒request_iter
。