skynet框架 skynet.lua 阅读(1) skynet.dispatch_message函数 proto结构 require select批量call功能

Skynet.lua 阅读(1)

主要内容:

  1. skynet.dispatch_message skynet服务器消息处理的注册,及消息处理。
  2. proto 注册消息协议结构proto
  3. 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
  1. 调用c.callback注册消息回调处理函数
  2. 通过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
  1. 第一部分: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
    
  2. 第二部分: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
  1. 创建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函数,简化代码。

  2. 请求执行

    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 函数的执行

    1. 115 - 122行 初始化数据结构中相关字段的数据。
    2. 125行 唤醒 request_thread 携程,第一次唤醒,所以传入参数self,相当于函数调用传入的参数。
  • send_requests 函数执行

    1. 17行 循环调用 c.send 发起请求。

    2. 请求发起成功后,在 22 - 25行保存相关信息,配合其他调用的判断。

      1. sessions 存储了请求参数信息。 self._sessions = sessions。用与请求结果返回, request_iter 返回的第一个参数就是该值。调用这,能区分出请求结果的参数来源。
      2. watching_session session_id_coroutine skynet.lua 局部变量,单处说明使用。
      3. request_n 记录发起的请求数量,在 request_iter 判断迭代结束。
  • request_thread request_iter 函数配合使用

    1. request_thread 在收到返回后 48行调用 skynet.wakeup(self) 唤醒 request_iter 70行skynet.wait(self),这样迭代器就能返回数据了。
    2. request_thread 主要作用是等待请求结果,并将结果放入 self._resp 表中,然后唤醒 request_iter
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值