skynet框架 源码分析 三

原创 2013年12月08日 16:45:28


       今天我们来读service_lua.c文件。

       这个文件很重要,它是模块snlua的源文件,也是各个lua服务节点的制造者。比如:agent服务节点,watchdog服务节点,launch服务节点等。

       让我们来看看这个制造者是如何运作的。

       拿agent举例说:

              gate节点在服务端与新到的客户端连接建立成功之后,会向watchdog服务节点发送一条文本消息,消息内容含有open指令,意在生成两个新的服务节点,分别是client和agent服务节点。

watchdog收到消息后会运行command:open接口:

function command:open(parm)
	local fd,addr = string.match(parm,"(%d+) ([^%s]+)")
	fd = tonumber(fd)
	print("agent open", self, string.format("%d %d %s", self, fd, addr))
	local client = skynet.launch("client", fd)
	local agent = skynet.launch("snlua","agent",skynet.address(client))
	if agent then
		agent_all[self] = { agent , client }
		skynet.send(gate, "text", "forward" , self, skynet.address(agent) , skynet.address(client))
	end
end

       执行该接口时,watchdog会执行两条指令skynet.launch(client, ...)和skynet.launch(snalu, agent, ....)分别载入client节点,和agent节点。agent节点是由模块snlua制造的。让我们看看接下来会发生什么。
       watchdog通过脚本调用执行skynet.so库文件中的skynet_command接口载入节点agent。而后,初始化节点agent的上下文,并调用模块snlua的snlua_create接口,生成一个节点agent基础的基础数据,调用snlua_init初始化agent节点的数据并发送了一条消息(执行脚本agent.lua,当然现在还未执行)。此时,最基础的一个agent服务节点就已经产生了,当然,它收到的第一条消息就是读取agent.lua文件,并执行。
       新的worker线程,执行到agent节点时,首先,执行agent.lua脚本文件。里面包括,引入skynet.lua文件,设置client的handle值。skynet.lua比较复杂。skynet.lua中会引入skynet.so
库文件并调用luaopen_skynet_c接口,向当前lua状态机上的添加一张表,而后将表的地址赋给本地变量c。本地变量c表中记录了很多函数指针。当然经常导C++对象或者函数指针到lua中的人对这一块肯定不陌生。不过,我要说的是接下来要发生的逻辑。

local skynet = require "skynet"
local client = ...

skynet.register_protocol {
	name = "client",
	id = 3,
	pack = function(...) return ... end,
	unpack = skynet.tostring,
	dispatch = function (session, address, text)
		-- It's client, there is no session
		skynet.send("LOG", "text", "client message :" .. text)
		local result = skynet.call("SIMPLEDB", "text", text)
		skynet.ret(result)
	end
}

skynet.start(function()
	skynet.send(client,"text","Welcome to skynet")
end)

       首先通过skynet.register_protocol注册一个客户端消息处理函数,里面包含发送内容到log服务,调用simpledb执行指令,最后返回结果到客户端。

       接下来是skynet.start接口,这个接口很重要。我们看看里面写了什么。

function skynet.start(start_func)	
	c.callback(dispatch_message)
	trace_handle = assert(c.stat "trace")
	skynet.timeout(0, function()
		init_service(start_func)
	end)
end

       首先,执行c.callback,调用skynet.so库文件的接口_callback,设置agent节点的回调函数,这样每次有消息发送到agent节点,都会用函数dispatch_message来处理。而后,才会执行传进来的闭包start_func。

       接下来dispatch_message通过pcall调用了接口raw_dispatch_message,而dispatch_message本事并没有太多逻辑。我们看看raw_dispatch_message接口是在干什么。

local function raw_dispatch_message(prototype, msg, sz, session, source, ...)
	-- 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
			c.trace_switch(trace_handle, session)
			session_id_coroutine[session] = nil
			suspend(co, coroutine.resume(co, msg, sz))
		end
	else
		local p = assert(proto[prototype], prototype)
		local f = p.dispatch
		if f then
			local co = co_create(f)
			session_coroutine_id[co] = session
			session_coroutine_address[co] = source
			suspend(co, coroutine.resume(co, session, source, p.unpack(msg,sz, ...)))
		else
			print("Unknown request :" , p.unpack(msg,sz))
			error(string.format("Can't dispatch type %s : ", p.name))
		end
	end
end

       若消息类型值为1,则是返回消息,比方说向simpledb发送了一条文本消息,而后收到其返回结果。当然,不管raw_dispatch_message中走哪条分支,最终基本上都会调用suspend接口,并调用coroutine.resume接口,也就是用协程的方式运行某个lua闭包。解释一下lua协程。按我的理解lua协程其实只开辟了一个线程栈,但没有像操作系统的线程那样,成为一个调度单位。只是在lua主线程执行到某一处时,通过coroutine.resume切换到协程中执行,当执行完之后,通过coroutine.yield返回到主线程栈上继续执行。也就是说协程只是新建了一个空间来执行逻辑,执行完将执行结果返回给主线程,或者执行到一半,切换到主线程,等待下次被唤起继续执行。而之前注册的消息处理函数,就是在协程中执行的。如果已经理解了协程的概念,继续看其中的函数就不会太麻烦。比方说看skynet.call接口时,看到接口skynet.call时:

return p.unpack(coroutine_yield("CALL", assert(session, "call to invalid address")))
  有一个coroutine_yield,这时候,就是将"CALL", session返回给lua状态机的主线程,主线程是在suspend接口中调用该协程的,在收到返回数据后,继续执行suspend接口。
-- suspend is local function
function suspend(co, result, command, param, size)
	if not result then
		trace_count()
		error(debug.traceback(co,command))
	end
	if command == "CALL" then
		c.trace_register(trace_handle, param)
		session_id_coroutine[param] = co
	elseif command == "SLEEP" then
		c.trace_register(trace_handle, param)
		session_id_coroutine[param] = co
		sleep_session[co] = param
	elseif command == "RETURN" then
		local co_session = session_coroutine_id[co]
		local co_address = session_coroutine_address[co]
		-- PTYPE_RESPONSE = 1 , see skynet.h
		if param == nil then
			trace_count()
			error(debug.traceback(co))
		end
		-- c.send maybe throw a error, so call trace_count first.
		-- The coroutine execute time after skynet.ret() will not be trace.
		trace_count()
		c.send(co_address, 1, co_session, param, size)
		return suspend(co, coroutine.resume(co))
	elseif command == "EXIT" then
		-- coroutine exit
		session_coroutine_id[co] = nil
		session_coroutine_address[co] = nil
	else
		trace_count()
		error("Unknown command : " .. command .. "\n" .. debug.traceback(co))
	end
	trace_count()
	dispatch_wakeup()
end

        第一个co就不解释了,result是执行协程的结果,若成功返回true,否则返回false。command就是“CALL”,后面的是返回的参数。比方说现在param 就等于 session。此时,执行command == “call” 将 key = session,value = co保存到表session_id_coroutine表中。例如:当simpledb节点返回数据给agent节点时,首先,在simpledb中执行dispatch接口,执行skynet.ret接口,此时,协程会被挂起,且切换到simpledb主线程,而后返回一条“RETURN”command给suspend接口,suspend接口中从session_coroutine_id中取出session_id,从session_coroutine_address取出handle值。最后发送消息给该handle,也就是agent节点。如果要问为什么消息类型是返回类型,请看上段代码的:

		-- c.send maybe throw a error, so call trace_count first.
		-- The coroutine execute time after skynet.ret() will not be trace.
		trace_count()
		c.send(co_address, 1, co_session, param, size)
              c.send(xxx, 1 --- 此处1对应PTYPE_RESPONSE就是标明该消息是返回数据。









从零开始学习Skynet_examples研究

一、编译Skynet: 1、用ubuntu15.10直接make linux编译Skynet会有报错。安装autoconf、libreadline6、libreadline6-dev后就能编译成功了...
  • Mr_virus
  • Mr_virus
  • 2016年08月26日 17:10
  • 3098

skynet的一些整理

##skynet 相关 *. 网络:gate.lua、gateserver.lua、socketdriver .lua、netpack.lua是一套东西,完整的实现了封包的处理,即两个字节的数据大小...
  • antsmall
  • antsmall
  • 2016年09月09日 20:54
  • 900

skynet学习笔记二

skynet已经做好了服务间消息的推送与接收,使用都只需要按照约定的协议进行handle处理就可以了。RPC调用 skynet.call(address, typename,…) 此接口会在内部生...
  • nynyvkhhiiii
  • nynyvkhhiiii
  • 2016年04月14日 11:31
  • 2012

Skynet基础入门例子详解(7)

GateServer的使用skynet 提供了一个通用模板 lualib/snax/gateserver.lua 来启动一个网关服务器,通过 TCP 连接和客户端交换数据。TCP 基于数据流,但一般我...
  • uisoul
  • uisoul
  • 2017年04月07日 15:50
  • 2100

Skynet服务器框架(四) Lua服务创建和启动剖析

前言:之前从Skynet启动过程,解读了skynet的启动部分C语言编写的底层源码,最后成功启动了引导的lua服务bootstrap.lua,接下来我们要尝试自定义一个lua服务,并让它启动起来。bo...
  • linshuhe1
  • linshuhe1
  • 2017年04月17日 14:13
  • 2827

skynet启动流程及调用服务

3.基本原理 3.1启动流程 1.skynet-src/skynet_main.c 这个是main()函数所在,主要就是设置一下lua的环境、默认的配置、打开config配置文件,并修改默认...
  • lzb991435344
  • lzb991435344
  • 2017年08月16日 09:28
  • 126

skynet框架 源码分析 一

本人所看的skynet框架,是云风所写的开源分布式服务器系统。        说说广泛流传的分布式系统,我觉得最简单的理解就是,一个服务器系统可以开很多进程来完成任务,并且这些进程可以不受地域的限制,...
  • a240581469
  • a240581469
  • 2013年12月06日 18:13
  • 14499

云风skynet服务端框架研究

http://forthxu.com/blog/skynet.html skynet是云风编写的服务端底层管理框架,底层由C编写,配套lua作为脚本使用,可换python等其他脚本语言。...
  • oMingZi12345678
  • oMingZi12345678
  • 2015年12月10日 13:44
  • 7360

Skynet服务器框架(六) Socket服务源码剖析和应用

引言: 如何在skynet框架中使用socket+protobuf。上篇 Skynet服务器框架(五) 使用pbc(protobuf) 我们已经大致了解了如何在Skynet中通过pcb来使用Pr...
  • linshuhe1
  • linshuhe1
  • 2017年05月22日 19:43
  • 2227

skynet教程(1)--服务的编写

作者:shihuaping0918@163.com,转载请注明作者自从skynet源码分析系列文章发布以后,陆续收到不少的反馈,说需要一个step by step的教程,身边的朋友也要求出个教程。于...
  • 119365374
  • 119365374
  • 2017年09月02日 08:20
  • 1542
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:skynet框架 源码分析 三
举报原因:
原因补充:

(最多只允许输入30个字)