skynet热更新之inject

游戏服务器的热更新是一种常见的需求,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.mzph.cn/pingmian/50562.shtml

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

相关文章

【python】python大学排名数据抓取+可视化(源码+数据集+可视化+论文)【独一无二】

&#x1f449;博__主&#x1f448;&#xff1a;米码收割机 &#x1f449;技__能&#x1f448;&#xff1a;C/Python语言 &#x1f449;公众号&#x1f448;&#xff1a;测试开发自动化【获取源码商业合作】 &#x1f449;荣__誉&#x1f448;&#xff1a;阿里云博客专家博主、5…

uart开发调试

1. Uart基本框架 1.1概念 通信系统有两种方式&#xff0c;同步通信和异步通信. 同步通信的典型特征&#xff1a;通信双方公用同一个时钟&#xff0c;发送/接受速率完全一致&#xff0c;通信时需要带时钟信号传输. 异步通信的典型特征&#xff1a;通信双方各自具有独立的时钟…

MyBatis操作数据库 -- 动态SQL

T04BF &#x1f44b;专栏: 算法|JAVA|MySQL|Spring &#x1faf5; 与天斗其乐无穷 文章目录 1. 动态SQL<if>标签<trim>标签<where> 标签<set> 标签<foreach> 标签<include>标签注解方式 1. 动态SQL 动态sql能够实现不同条件下的sql拼接 …

jquery+bootstrap实现DOM转图片并下载

&#x1f34a;jquery实现DOM结构转图片并下载 版本介绍&#xff1a; Bootstrap v3.3.7jQuery v3.5.1domToImage.js 根据Bootstrap实现dialog上一步下一步多个弹窗交互进行大肆修改&#xff0c;完善了第二步生成图片的功能与更强的交互 1.、功能说明 重新设置bootstrap主题色 …

DNS应用以及扩展知识

&#xff08;一&#xff09;DNS正向代理 1.首先在DNS服务器上安装bind包&#xff0c;安装环境 此部分参考上一个笔记 2.修改配置文件 vim /etc/named.conf 在配置文件中加上"any;" 3.然后配置/etc/named.rfc1912.zonesw文件 添加选中部分 选中部分有一个file文…

24年第三届钉钉杯大学生大数据挑战赛浅析

需要完整资料&#xff0c;请关注WX&#xff1a;“小何数模”&#xff01; 本次钉钉杯大数据挑战赛的赛题已正式出炉&#xff0c;无论是赛题难度还是认可度&#xff0c;该比赛都是仅次于数模国赛的独一档&#xff0c;可以用于国赛前的练手训练。考虑到大家解题实属不易&#xf…

气膜足球馆:经济高效的室内足球场馆解决方案—轻空间

如果你有一片足球场&#xff0c;想要建一个室内的足球馆&#xff0c;为什么不考虑一下气膜建筑呢&#xff1f;气膜建筑以其独特的优势和高性价比&#xff0c;成为现代体育场馆建设中的一匹黑马。它不仅具有传统建筑无法比拟的经济效益和快速施工优势&#xff0c;还在智能控制、…

vue实现电子签名、图片合成、及预览功能

业务功能&#xff1a;电子签名、图片合成、及预览功能 业务背景&#xff1a;需求说想要实现一个电子签名&#xff0c;然后需要提供一个预览的功能&#xff0c;可以查看签完名之后的完整效果。 需求探讨&#xff1a;后端大佬跟我说&#xff0c;文档我返回给你一个PDF的oss链接…

7.27扣...

知识点补充&#xff1a; 1.StringBuilder StringBuilder 类在 Java 中是一个可变字符序列。与 String 类不同&#xff0c;StringBuilder 可以在创建之后被修改。这意味着你可以向 StringBuilder 对象追加、插入或删除字符&#xff0c;而不需要创建新的对象&#xff08;辅助数…

企业公户验证API如何使用JAVA、Python、PHP语言进行应用

在纷繁复杂的金融与商业领域&#xff0c;确保每笔交易的安全与合规是至关重要的。而企业公户验证API&#xff0c;正是这样一位默默守护的数字卫士&#xff0c;它通过智能化的手段&#xff0c;简化了企业对公账户验证流程&#xff0c;让繁琐的审核变得快捷且可靠。 什么是企业公…

chrome浏览器驱动(所有版本)

chrome浏览器驱动 114之前版本 https://chromedriver.storage.googleapis.com/index.html 125以后 125以后版本下载链接在此&#xff0c;只有后面status是绿色对勾的才可以下载&#xff0c;驱动大版本一致就可以使用&#xff0c;不需版本号一模一样&#xff1b;下载所需版本只…

语言转文字

因为工作原因需要将语音转化为文字&#xff0c;经常搜索终于找到一个免费的好用工具&#xff0c;记录下使用方法 安装Whisper 搜索Colaboratory 右上方链接服务 执行 !pip install githttps://github.com/openai/whisper.git !sudo apt update && sudo apt install f…

高性能 Java 本地缓存 Caffeine 框架介绍及在 SpringBoot 中的使用

在现代应用程序中&#xff0c;缓存是一种重要的性能优化技术&#xff0c;它可以显著减少数据访问延迟&#xff0c;降低服务器负载&#xff0c;提高系统的响应速度。特别是在高并发的场景下&#xff0c;合理地使用缓存能够有效提升系统的稳定性和效率。 Caffeine 是一个高性能的…

《程序猿入职必会(4) · Vue 完成 CURD 案例 》

&#x1f4e2; 大家好&#xff0c;我是 【战神刘玉栋】&#xff0c;有10多年的研发经验&#xff0c;致力于前后端技术栈的知识沉淀和传播。 &#x1f497; &#x1f33b; CSDN入驻不久&#xff0c;希望大家多多支持&#xff0c;后续会继续提升文章质量&#xff0c;绝不滥竽充数…

【C++刷题】优选算法——队列+宽搜

N 叉树的层序遍历 vector<vector<int>> levelOrder(Node* root) {vector<vector<int>> ret;if (root nullptr) return ret;queue<Node*> q;q.push(root);ret.push_back({root->val});int size 1;while (!q.empty()) {vector<int> v…

【机器学习】Jupyter Notebook如何使用之基本步骤和进阶操作

引言 Jupyter Notebook 是一个交互式计算环境&#xff0c;它允许创建包含代码、文本和可视化内容的文档 文章目录 引言一、基本步骤1.1 启动 Jupyter Notebook1.2 使用 Jupyter Notebook 仪表板1.3 在笔记本中工作1.4 常用快捷键1.5 导出和分享笔记本 二、进阶用法2.1 组织笔…

从零开始学习网络安全渗透测试之基础入门篇——(二)Web架构前后端分离站Docker容器站OSS存储负载均衡CDN加速反向代理WAF防护

Web架构 Web架构是指构建和管理Web应用程序的方法和模式。随着技术的发展&#xff0c;Web架构也在不断演进。当前&#xff0c;最常用的Web架构包括以下几种&#xff1a; 单页面应用&#xff08;SPA&#xff09;&#xff1a; 特点&#xff1a;所有用户界面逻辑和数据处理都包含…

劝你不要上自动化立体库,非要上,砸锅了吧

导语 大家好&#xff0c;我是社长&#xff0c;老K。专注分享智能制造和智能仓储物流等内容。 新书《智能物流系统构成与技术实践》 在当今这个科技日新月异的时代&#xff0c;自动化立体库作为仓储物流领域的佼佼者&#xff0c;以其高效、精准、节省人力的优势&#xff0c;吸引…

Windows下帆软BI(finebi)单机部署移植(Tomcat)攻略

一、基础环境 操作系统&#xff1a;Windows 10 64bit 帆软BI 版本&#xff1a;V9.0/V10.0 HTTP工具&#xff1a;Tomcat 外置数据库&#xff1a;Oracle 11g 实验内容&#xff1a;将已经部署好的帆软BI从一台电脑移植到另一台电脑 二、前期准备 1、做好外置数据库移植&…

【Three.js基础学习】17.imported-models

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 前言 课程回顾&#xff1a; 如何在three.js 中引入不同的模型&#xff1f; 1. 格式 &#xff08;不同的格式&#xff09; https://en.wikipedia.org/wiki/List_of_file_form…