skynet热更新之inject

news/2024/10/18 16:38:00/文章来源:https://www.cnblogs.com/lcc9527/p/18326776

游戏服务器的热更新是一种常见的需求,skynet可以通过inject的方式,来修改一个服务的消息处理函数,达到热更新的效果。

skynet内置服务debug_console

skynet自带了一个调试控制台服务。inject注入代码需要先启动这个服务。

skynet.newservice("debug_console", "127.0.0.1", "9666")

启动之后,我们可以用telnet或者nc等指令来登录调试控制台。

> nc 127.0.0.1 9666

输入list指令,可以得到当前系统中所有服务的地址:

list
:00000004       snlua cdummy
:00000006       snlua datacenterd
:00000007       snlua service_mgr
:00000008       snlua main
:00000009       snlua debug_console 127.0.0.1 9666
:0000000a       snlua serviceA
<CMD OK>

输入inject指令,我们可以将某个代码文件,注入到指定的服务中:

inject :0000000a service/hotfix.lua<CMD OK>

更多的debug_console指令可以参考这里

inject实例

我们在系统启动时,打开debug_console,然后启动服务serviceA,接着设置每隔5秒给serviceA发送两个lua消息,一个参数bar,一个参数foo,代码如下:

--main.lua
local skynet = require "skynet"
skynet.start(function()skynet.newservice("debug_console", "127.0.0.1", "9666")local addr = skynet.newservice("serviceA")local function tick()skynet.send(addr, "lua", "foo")skynet.send(addr, "lua", "bar")skynet.timeout(500, tick)endskynet.timeout(500, tick)
end)

在服务serverA中,我们根据参数,调用不同的处理函数:

--serviceA.lua
local skynet = require "skynet"
local handles = {}handles.foo = function()print("foo")
endskynet.start(function()skynet.dispatch("lua", function(session, source, cmd, ...)local handle = handles[cmd]if handle thenhandle()elseprint("cmd not found", cmd)endend)
end)

现在我们启动skynet,可以看到每隔5秒输出:

foo
cmd not found   bar

现在我们新建一个文件hotfix.lua

--hotfix.lua
local handles = _P.lua.handles
local print = _G.print
handles.foo = function()print("foo after hotfix")
endhandles.bar = function()print("bar after hotfix")
end

接下来连接到控制台,并输入inject指令:

echo 'inject :0000000a services/hotfix.lua' | nc 127.0.0.1 9666

等到下次输出的时候,我们看到的就是:

foo after hotfix
bar after hotfix

更新完成,修改了foo函数,新增了bar函数。

使用inject调用hotfix.lua时,print函数是被修改过成debug_console的返回输出函数,所以如果要用到print的话,需要使用全局变量_G.print

对upValue的处理

如果我们的serviceA是这样的:

--serviceA.lua
local skynet = require "skynet"
local handles = {}local N = 1
local T = {count = 0,
}handles.foo = function()N = N + 2T.count = T.count + 1print("foo", N, T.count)
endhandles.bar = function()N = N - 1print("bar", N)
endskynet.start(function()skynet.dispatch("lua", function(session, source, cmd, ...)local handle = handles[cmd]if handle thenhandle()elseprint("cmd not found", cmd)endend)
end)

foo函数带有两个upValue: NTbar函数带有一个upValue: N 如果hotfix.lua文件没有做特殊处理,直接覆盖函数的话,那么就会丢失这些upValue。那么,要怎么处理这些upValue呢?这里需要用到luadebug库,主要是两个函数:

  • debug.getupvalue(f, i): 获取函数f中的第iupValue的变量名和值。
  • debug.upvaluejoin(f1, i, f2, j):让函数f1的第iupValue引用f2中的第jupValue

热更新带有upValue的函数,我们的hotfix.lua分三步走:

  1. 定义一个函数get_up,来获取原有的函数的upValue列表。
  2. 定义新的处理函数。
  3. 定义一个函数uv_join,将新函数的upValue和旧函数的upValue绑定起来。

完整代码如下:

local handles = _P.lua.handles
local print = _G.printlocal function get_up(f)local u = {}if not f thenreturn uendlocal i = 1while true dolocal name = debug.getupvalue(f, i)if name == nil thenreturn uendu[name] = ii = i + 1endreturn u
endlocal function uv_join(f, old_f, old_uv)local i = 1while true dolocal name = debug.getupvalue(f, i)if not name thenbreakendif old_uv[name] thendebug.upvaluejoin(f, i, old_f, old_uv[name])endi = i + 1end
endlocal foo = handles.foo
local up = get_up(foo)local N, T      --定义两个upValue,否则函数里会变成全局变量
handles.foo = function()N = N + 200T.count = T.count + 100print("foo", N, T.count)
end
uv_join(handles.foo, foo, up)

这里的get_up函数只取了传入函数的upValue,如果要嵌套处理函数中的函数,可以参考lualib/skynet/inject.lua中的getupvaluetable函数。

inject实现原理

debug_console服务的代码位于service/debug_console.lua文件中,其对inject指令的处理,其实就是发送一条debug类型的消息到目标服务:

--debug_console.lua
function COMMAND.inject(address, filename, ...)address = adjust_address(address)local f = io.open(filename, "rb")if not f thenreturn "Can't open " .. filenameendlocal source = f:read "*a"f:close()local ok, output = skynet.call(address, "debug", "RUN", source, filename, ...)if ok == false thenerror(output)endreturn output
end

在我们的服务中,当我们require 'skynet'的时候,会自动注册debug消息类型的处理:

--lualib/skynet.lua
-- Inject internal debug framework
local debug = require "skynet.debug"
debug.init(skynet, {dispatch = skynet.dispatch_message,suspend = suspend,resume = coroutine_resume,
})
--lualib/skynet/debug.luaskynet.register_protocol {name = "debug",id = assert(skynet.PTYPE_DEBUG),pack = assert(skynet.pack),unpack = assert(skynet.unpack),dispatch = _debug_dispatch,}

其中,参数RUN是这样处理的

--lualib/skynet/debug.lua
function dbgcmd.RUN(source, filename, ...)local inject = require "skynet.inject"local args = table.pack(...)local ok, output = inject(skynet, source, filename, args, export.dispatch, skynet.register_protocol)collectgarbage "collect"skynet.ret(skynet.pack(ok, table.concat(output, "\n")))
end

追溯代码,来到最终的inject函数:

  1. 修改print函数,可以返回输出内容给debug_console服务。
  2. 在上一层调用的时候,传进来的...实际上两个函数skynet.dispatch_messageskynet.register_protocol,这里将这两个函数,以及函数中包含的子函数,所用到的upVluae都收集起来,存入表u中。
  3. protoskynet.register_protocol中用到的一个upValue,存放着当前服务所注册的消息类型。遍历proto,将每个消息的处理函数用到的upValue收集起来,存放到表p中。
  4. 设置环境,调用传入的热更新文件。现在我们知道,在上面的例子中的hotfix.lua,用到的_P,就是存放各种消息类型的处理函数的upValue表。

在控制台调用inject指令时,还可以传入额外的参数,例如:inject :0000000a services/hotfix.lua xxx yyy,最终这两个参数,就是这里的inject函数中的第四个参数args,可以在hotfix.lua中直接使用这两参数。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/772544.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

2024736DP专项练习赛

阿尼亚不想学DP前言 比赛链接榜上那个冒着蓝光的就是我…… 提交记录跟答辩一样…… 吐槽一下,虽然挂着 DP 专题赛的名字,但除了 T1 T3 以外,全是记搜题(虽然好像只有四道题来着)。 T1 签到题,\(n\) 范围很小,先用区间 dp 求出任意区间达到最终状态所需的最小代价,然后…

Sqlserver 处理两条完全一样的记录

想要删除重复记录(所有字段值相同),怎么处理? with cte AS (select row_number() over (partition by wo_woid, wo_lx order by(select null)) as rn,*from jserp.Wo_Modified_Record_Backupwhere wo_woid like MO24% and wo_woid>=MO240601 and len(wo_woid)=14 and wo…

apifox日常使用

一、前后置操作 1.1 提取变量 登录接口提取返回数据里的token,保存为全局变量1.2 接口间相互传递数据 详情接口使用登录接口返回提取的token[Haima的博客] http://www.cnblogs.com/haima/

Prompt Engneering

Prompt-Engineerning Prompt-Engineerning(提示词工程) 目录Prompt-Engineerning零、文档中参数说明1、OpenAI API接口参数一、什么是提示词工程1、学习AI在提示词工程上有哪些优势2、Prompt调优二、Prompt典型构成1、定义角色为什有效?案例:推荐流量包的智能客服1.对话系统…

“智能体风”吹进体育圈 粉丝手搓上百个智能体为中国健儿应援 太有AI了!粉丝手搓上百个智能体为中国健儿打CALL

智能体的风吹进了体育竞技圈。近日,在百度文心智能体平台,出现了上百个充满“AI”的运动明星粉丝应援智能体,比如支持中国女子乒乓球运动员孙颖莎的“孙颖莎的小迷妹”、支持中国女子跳水队员全红婵的“婵婵的小书包”,应援中国女子乒乓球运动员王曼昱的“曼昱的小芋圆”等…

centos7 最小化安装yum不能安装软件解决方案

慕课网神思者老师课常资料带的布署工具中,自带的liunx 系统centos7 yum发现不能安装软件,比如docker 解决方案 首先我们安装好虚拟机启动系统centos7 尝试安装任何软件都会报仓库错误 第一反应就是更新yum yum update 由于仓库不对更新肯定不行了 第二反是 更新仓库…

节约时间与金钱:顶级简单项目管理软件推荐

国内外主流的10款项目进度管理软件对比:PingCode、Worktile、蓝凌OA、用友、泛微OA、飞书、Asana、Trello、Smartsheet、Jira。在快节奏的商业环境中,有效地管理项目进度常常是团队成功与否的关键。许多团队面临着项目管理过于复杂,难以迅速适应变化的挑战。一个直观且功能全…

使用Excel画出各类统计图(2)

承接上一章,本章继续介绍如何用Excel画图 目录一、折线图1.画折线图常见的错误2.双坐标轴折线图(1)设置主次坐标轴(2)设置坐标轴的最大最小值(3)设置折线的颜色3.柱形图顶端的折线图(1)绘制折线图(2)添加数据(3)绘制面积图(4)更改坐标轴(5)数据处理 一、折线图…

本田裂行 传动保养、换普利珠

9颗螺丝,8mm套筒,最好接延长管。 我买了气动扳手,后来还是放弃了,汽修师傅10分钟搞定的事情,你可能要搞很久,而且是蹲着,算了,千万别买工具自己搞,直接花钱请人搞好了。 普利珠还是不要换, 我原厂14g 的,换了18g 以后,啃盘严重, 2、3千公里以后里面全是啃下来的粉…

QT mainwindow UI界面添加工具栏

1.在mainwindow UI设计器界面右上角 右键mainwindow 弹出如下菜单图1 可以看到 添加工具栏,移除状态栏 等相关操作都在菜单中 2.新建action相关菜单项图2 在红框中的Action Edit 中,第一行菜单栏按钮(分别是新建,复制,粘贴,删除,修改)点击以进行创建 鼠标右键Action E…

在Pandas中 SQL操作:SQLAlchemy和PyMySQL的区别

SQLAlchemy和PyMySQL的区别 1. SQLAlchemy和PyMySQL简介SQLAlchemy 是Python编程语言下的一款开源软件。它提供了SQL工具包和对象关系映射器(ORM)来进行数据库操作。SQLAlchemy可以与多种数据库系统进行交互,包括MySQL、PostgreSQL、SQLite等。PyMySQL 是Python编程语言下的…

正新轮胎真的挺好

sc12 龙王胎, 雨天根本不滑7月份买的,2424 ,新的很,就一百多,换好160左右。还是很值的,别用什么顾满德,人家推这个产品是因为利润,不是因为他觉得轮胎好, 之前老板前后轮卖给我400块钱,包安装,含泪赚了至少200 。。太坑了,轮胎本身也没什么问题,就是硬,耐磨,有点…