初探 Skynet:轻量级分布式游戏服务器框架实战

在游戏服务器开发领域,高效、稳定且易于扩展的框架一直是开发者追求的目标。Skynet 作为一款轻量级、高性能的分布式游戏服务器框架,凭借其独特的设计理念和强大的功能,赢得了众多开发者的青睐

一.Skynet底层架构支持

1.Actor
erlang 从语言层面支持 actor 并发模型,并发实体是 actor (在 skynet 中称之为 服务 ); skynet 采用 c + lua 来实现 actor 并发模型;底层也是通过采用多少个核心开启多少个内核线程来充分利用多核;

 

有消息的 actor 为活跃的 actor,没有消息为非活跃的 actor 

 2.config

我们要配置config文件来支持skynet的运行

thread=8    --8个工作线程
logger=nil  --不打日志
harbor=0 
start="main" --main文件作为入口函数
lua_path="./skynet/lualib/?.lua;./skynet/lualib/?/init.lua;"
-- lua 抽象进程
luaservice="./skynet/service/?.lua;./app/?.lua;"
lualoader="./skynet/lualib/loader.lua"
cpath="./skynet/cservice/?.so"
lua_cpath="./skynet/luaclib/?.so"
 3.core-eg

我们通过一个网络消息的代码案例来看actor是怎么运行的

local skynet = require "skynet"
local socket = require "skynet.socket"skynet.start(function()local listenfd = socket.listen("192.168.217.148",8888)socket.start(listenfd,function (clientfd,addr)print("recive a client:",clientfd,addr)end)skynet.timeout(100,function()print("after 1s ,do here")end)print("hello skynet")-- local slave = skynet.newservice("slave")-- local response = skynet.call(slave,"lua","ping")--[[main ->  ping ->slave  //协程挂起slave -> pong ->main   //协程唤醒c/c++ 异步流程 callbcak 协程来实现通过协程的方式消除异步产生的回调我们就能写同步的代码]]-- print(response)
end)

 

skynet.start 是服务的入口点,Skynet 会为这个服务创建一个独立的 Actor服务启动时执行的代码(如打印 "hello skynet")在 Actor 初始化阶段同步执行

socket.listen 和 socket.start 注册了一个网络监听事件

当有新客户端连接时,Skynet 会将这个事件封装成消息发送给当前服务 Actor

服务 Actor 在处理消息时执行回调函数(打印客户端信息)

skynet.timeout 注册了一个定时器事件

Skynet 的定时器系统会在指定时间后向服务 Actor 发送一个超时消息

服务 Actor 收到超时消息后执行对应的回调函数

 

main

local skynet = require "skynet"
local socket = require "skynet.socket"skynet.start(function()local slave = skynet.newservice("slave")local response = skynet.call(slave,"lua","ping")--[[main ->  ping ->slave  //协程挂起slave -> pong ->main   //协程唤醒c/c++ 异步流程 callbcak 协程来实现通过协程的方式消除异步产生的回调我们就能写同步的代码]]print(response)
end)

slave

local skynet = require "skynet"local CMD = {}function CMD.ping()skynet.retpack("pong")
endskynet.start(function()
skynet.dispatch("lua",function(session,source,cmd,...)local func = assert(CMD[cmd])func(...)
end)
end)

 我们上述代码通过callback回调的方式来驱动actor运行

 4.公平调度

 

actor 是抽象的用户态进程,相对于 linux 内核 ,有进程调度,那么 skynet 也要实现 actor 调度
1. 将活跃的 actor 通过 全局队列 组织起来; actor 当中的 消息队列 有消息就是活跃的 actor
2. 线程池去 全局队列中取出 actor 的消息队列,接着运行 actor

二.lua/c 接口编程

skynet openresty 都是深度使用 lua 语言的典范;学习 lua 不仅仅要学习基本用法,还要学会使用 c lua 交互,这样才学会了 lua 作为胶水语言的精髓;

 

 1.虚拟栈
①栈中只能存放 lua 类型的值,如果想用 c 的类型存储在栈中,需要将 c 类型转换为 lua 类型;
②lua 调用 c 的函数都得到一个 的栈,独立于之前的栈;
③c 调用 lua ,每一个协程都有一个栈;
④c  创建虚拟机时,伴随创建了一个主协程,默认创建一个虚拟栈;
⑤无论何时 Lua 调用 C , 它都只保证至少有 LUA_MINSTACK 这么多的堆栈空间可以使用。
⑥LUA_MINSTACK 一般被定义为 20 , 因此,只要你不是不断的把数据压栈, 通常你不用关心堆栈 大小。

 2.C调用lua接口
#include <stdio.h>
#include <stdlib.h>#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>//lua嵌入在C语言里面的好处--->热更新 不需要重新编译lua代码 重启即可更新or文件变化重新加载/** 调用Lua全局函数,向其传递一个整数值1* L: Lua虚拟机指针* funcname: 要调用的Lua函数名称*/
static void
call_func_0(lua_State *L, const char* funcname)
{// 从全局环境中获取指定名称的函数lua_getglobal(L, funcname);//向栈顶压入整数参数1lua_pushinteger(L, 1);// 调用函数,1个参数,0个返回值lua_call(L, 1, 0);//C调lua必须维护栈的平衡 eg:--->lua_pop(L);
}int main(int argc, char** argv)
{//创建新的lua虚拟机lua_State *L = luaL_newstate();//打开lua标准库luaL_openlibs(L);if (argc > 1) {lua_pushboolean(L, 1);lua_setfield(L, LUA_REGISTRYINDEX, "LUA_NOENV");if ( LUA_OK != luaL_dofile(L, argv[1]) ) {const char* err = lua_tostring(L, -1);fprintf(stderr, "err:\t%s\n", err);return 1;}// 依次调用Lua脚本中的Init、Loop、Release函数call_func_0(L, "Init");call_func_0(L, "Loop");call_func_0(L, "Release");lua_close(L);return 0;}return 0;
}
package.cpath = "luaclib/?.so"
local so = require "tbl.c"--a=1
--全局可见--local a =1
--当前文件可见-- 加了local相当于C/C++中的staticfunction Init(args)print("call [init] function", args)
endfunction Loop()print("call [loop] function")for k, v in ipairs({1,2,3,4,5}) do--so.echo(v)  -- 等于 调用 5 次  因为每次调用都是一个新的虚拟栈,所以没有必要维护 --->C调用lua然后调Cprint("value = " .. v)  --->C调用luaendreturn 1
endfunction Release()print("call [release] function")
end

c->lua每个协程一个栈 我们需要维护栈的空间 弹出不需要的栈空间

 3.C调用lua接口-->lua再调用C接口
#include <stdio.h>
#include <stdlib.h>#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>//lua嵌入在C语言里面的好处--->热更新 不需要重新编译lua代码 重启即可更新or文件变化重新加载/** 调用Lua全局函数,向其传递一个整数值1* L: Lua虚拟机指针* funcname: 要调用的Lua函数名称*/
static void
call_func_0(lua_State *L, const char* funcname)
{// 从全局环境中获取指定名称的函数lua_getglobal(L, funcname);//向栈顶压入整数参数1lua_pushinteger(L, 1);// 调用函数,1个参数,0个返回值lua_call(L, 1, 0);//C调lua必须维护栈的平衡 eg:--->lua_pop(L);
}int main(int argc, char** argv)
{//创建新的lua虚拟机lua_State *L = luaL_newstate();//打开lua标准库luaL_openlibs(L);if (argc > 1) {lua_pushboolean(L, 1);lua_setfield(L, LUA_REGISTRYINDEX, "LUA_NOENV");if ( LUA_OK != luaL_dofile(L, argv[1]) ) {const char* err = lua_tostring(L, -1);fprintf(stderr, "err:\t%s\n", err);return 1;}// 依次调用Lua脚本中的Init、Loop、Release函数call_func_0(L, "Init");call_func_0(L, "Loop");call_func_0(L, "Release");lua_close(L);return 0;}return 0;
}
package.cpath = "luaclib/?.so"
local so = require "tbl.c"--a=1
--全局可见--local a =1
--当前文件可见-- 加了local相当于C/C++中的staticfunction Init(args)print("call [init] function", args)
endfunction Loop()print("call [loop] function")for k, v in ipairs({1,2,3,4,5}) doso.echo(v)  -- 等于 调用 5 次  因为每次调用都是一个新的虚拟栈,所以没有必要维护 --->C调用lua然后调C--print("value = " .. v)  --->C调用luaendreturn 1
endfunction Release()print("call [release] function")
end
#include <lua.h>#include <lauxlib.h>#include <lualib.h>#include <stdio.h>static int
lecho (lua_State *L) {const char* str = lua_tostring(L, -1);fprintf(stdout, "%s\n", str);return 0;
}static const luaL_Reg l[] = {// 导出给lua使用数组{"echo", lecho},{NULL, NULL},
};int
luaopen_tbl_c(lua_State *L) { // local tbl = require "tbl.c"// 创建一张新的表,并预分配足够保存下数组 l 内容的空间// luaL_newlibtable(L, l);// luaL_setfuncs(L, l, 0);luaL_newlib(L, l);return 1;
}

4.运行lua/ src/lua 可执行程序让lua调用C接口

#include <lua.h>#include <lauxlib.h>#include <lualib.h>#include <stdio.h>static int
lecho (lua_State *L) {const char* str = lua_tostring(L, -1);fprintf(stdout, "%s\n", str);return 0;
}static const luaL_Reg l[] = {// 导出给lua使用数组{"echo", lecho},{NULL, NULL},
};int
luaopen_tbl_c(lua_State *L) { // local tbl = require "tbl.c"// 创建一张新的表,并预分配足够保存下数组 l 内容的空间// luaL_newlibtable(L, l);// luaL_setfuncs(L, l, 0);luaL_newlib(L, l);return 1;
}
package.cpath = "luaclib/?.so" --c库的路径local so = require "tbl.c"so.echo("hello world") -- 新的虚拟栈
so.echo("hello world1")-- 新的虚拟栈
so.echo("hello world2")-- 新的虚拟栈--[[1. c调用lua  c有多个协程 每个协程一个虚拟栈2. lua调用c  每次调用都有一个虚拟栈
]]

#include <lua.h>#include <lauxlib.h>#include <lualib.h>#include <stdio.h>// 闭包实现:  函数 + 上值  luaL_setfuncs
// lua_upvalueindex(1)
// lua_upvalueindex(2)
static int
lecho (lua_State *L) {lua_Integer n = lua_tointeger(L, lua_upvalueindex(1));n++;const char* str = lua_tostring(L, -1);fprintf(stdout, "[n=%lld]---%s\n", n, str);lua_pushinteger(L, n);lua_replace(L, lua_upvalueindex(1));return 0;
}static const luaL_Reg l[] = {{"echo", lecho},{NULL, NULL},
};int
luaopen_uv_c(lua_State *L) { // local tbl = require "tbl.c"luaL_newlibtable(L, l);// 1lua_pushinteger(L, 0);// 2luaL_setfuncs(L, l, 1);// 上值// luaL_newlib(L, l);return 1;
}

 

package.cpath = "luaclib/?.so"local so = require "uv.c"so.echo("hello world1")
so.echo("hello world2")
so.echo("hello world3")
so.echo("hello world4")
so.echo("hello world5")
so.echo("hello world6")
so.echo("hello world7")
so.echo("hello world8")
so.echo("hello world9")--./lua/src/lua test-uv.lua

#include <lua.h>#include <lauxlib.h>#include <lualib.h>#include <stdio.h>#include <stdlib.h>#include <string.h>struct log {int count;
};static int
lagain(lua_State *L) {struct log *p = (struct log *)luaL_checkudata(L, 1, "mk.ud.log");lua_getuservalue(L, -1);const char* str = lua_tostring(L, -1);fprintf(stdout, "ud[n=%d]----%s\n", p->count, str);return 0;
}static int
lecho(lua_State *L) {struct log *p = (struct log *)luaL_checkudata(L, 1, "mk.ud.log");const char* str = lua_tostring(L, -1);p->count++;lua_setuservalue(L, -2);fprintf(stdout, "ud[n=%d]----%s\n", p->count, str);return 0;
}static int
lnew (lua_State *L) {struct log *q = (struct log*)lua_newuserdata(L, sizeof(struct log));q->count = 0;lua_pushstring(L, "");lua_setuservalue(L, -2);if (luaL_newmetatable(L, "mk.ud.log")) {luaL_Reg m[] = {{"echo", lecho},{"again", lagain},{NULL, NULL},};luaL_newlib(L, m);lua_setfield(L, -2, "__index");lua_setmetatable(L, -2);}return 1;
}static const luaL_Reg l[] = {{"new", lnew},{NULL, NULL},
};int
luaopen_ud_c(lua_State *L) {luaL_newlib(L, l);return 1;
}

 

package.cpath = "luaclib/?.so"local so = require "ud.c"local ud = so.new()ud:echo("hello world1")
ud:again()
ud:echo("hello world2")
ud:again()
ud:echo("hello world3")
ud:again()
ud:echo("hello world4")
ud:again()
ud:echo("hello world5")
ud:again()
ud:echo("hello world6")
ud:again()
ud:echo("hello world7")
ud:again()
ud:echo("hello world8")
ud:again()
ud:echo("hello world9")
ud:again()--./lua/src/lua test-ud.lua

lua->c不需要维护栈空间 因为每次调用都会生成一个新栈

 

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

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

相关文章

Oracle数据库中,WITH..AS 子句用法解析

前言 在Oracle数据库中&#xff0c;WITH子句&#xff08;也成为“子查询因子化” 或“公共表表达式”&#xff0c;Common Table Expression, CTE)用于定义一个临时命名的结果集&#xff0c;这个结果集可以在后续的查询中引用。 一、基本语法 WITH sjtab AS &#xff08;SELE…

什么是卫星电话?卫星电话与普通电话有什么区别?

在信息时代&#xff0c;通信技术已渗透到人类生活的方方面面&#xff0c;但传统地面通信网络仍存在盲区&#xff0c;无论是海洋、沙漠、高山&#xff0c;还是灾害现场&#xff0c;普通手机往往因基站信号无法覆盖而失效。此时&#xff0c;卫星电话便成为连接世界的“生命线”。…

【C/C++】高阶用法_笔记

1. 模板元编程&#xff08;TMP&#xff09;与编译时计算 (1) 类型萃取与 SFINAE 类型萃取&#xff08;Type Traits&#xff09;&#xff1a;利用模板特化在编译时推断类型属性。 template<typename T> struct is_pointer { static constexpr bool value false; };templ…

鸿蒙OSUniApp 实现一个精致的日历组件#三方框架 #Uniapp

使用 UniApp 实现一个精致的日历组件 前言 最近在开发一个约会小程序时&#xff0c;需要实现一个既美观又实用的日历组件。市面上虽然有不少现成的组件库&#xff0c;但都不太符合我们的设计需求。于是&#xff0c;我决定从零开始&#xff0c;基于 UniApp 自己实现一个功能完…

PyQt5完整指南:从入门到实践

引言 PyQt5是Python编程语言的一个GUI&#xff08;图形用户界面&#xff09;工具包&#xff0c;它是Qt5应用程序框架的Python绑定。Qt是一个跨平台的C应用程序开发框架&#xff0c;被广泛用于开发GUI程序和非GUI程序。PyQt5让Python开发者能够使用Python语言享受到Qt框架的强大…

Excel的详细使用指南

### **一、Excel基础操作** #### **1. 界面与基本概念** - **工作簿&#xff08;Workbook&#xff09;**&#xff1a;一个Excel文件&#xff08;扩展名.xlsx&#xff09;。 - **工作表&#xff08;Worksheet&#xff09;**&#xff1a;工作簿中的单个表格&#xff08;默认名…

Linux grep -r 查找依赖包是否存在依赖类 Class

方法一&#xff1a;通过 Linux &#xff0c;grep -r ClassPath 命令 grep -f org.apache.kafka.connect.source.SourceRecord在 jar 包所在 lib 或者 lib/plugins 目录下执行&#xff0c;grep -r&#xff0c; flink-sql-connector-sqlserver-cdc-3.3.0.jar 中此 kafka Source…

碎片笔记|AI生成图像溯源方法源码复现经验(持续更新中……)

前言&#xff1a;本篇博客分享一些溯源方法的复现经验&#xff0c;希望能帮助到大家&#x1f389;。 目录 1. Close-set AttributionRepmixDe-FakeDNA-Net 2. Open-set AttributionPOSE 3. Single-Model AttributionOCC-CLIPLatentTracer 1. Close-set Attribution Repmix 论…

SAP Fiori Elements Object Page

🏰 SAP Fiori Elements Object Page:魔法积木城堡的建造秘密 想象一下,你曾经去过一个神奇的乐高主题公园,在那里,城堡会根据你的设计图纸自动搭建,而你只需要提供一张设计说明书,不必亲自摆放每一块积木!这就是SAP Fiori Elements Object Page的渲染魔法! 🧙‍♂…

Git 用户名与邮箱配置全解析:精准配置——基于场景的参数选择

目录 一、配置查看&#xff1a;理解多层级配置体系二、精准配置&#xff1a;基于场景的参数选择1. 仓库级配置&#xff08;推荐&#xff09;2. 用户级配置3. 系统级配置 三、历史提交信息修改1. 修改最近一次提交2. 修改多个历史提交&#xff08;危险操作&#xff09; 五、配置…

Fabric系列 - SoftHSM 软件模拟HSM

在 fabric-ca-server 上使用软件模拟的 HSM(密码卡) 功能 安装 SoftHSMv2 教程 SoftHSMv2 默认的配置文件 /etc/softhsm2.conf默认的token目录 /var/lib/softhsm/tokens/ 初始化和启动fabric-ca-server&#xff0c;需要设置一个管理员用户的名称和密码 初始化令牌 # 初始…

医学影像系统的集成与工作流优化

🧑 博主简介:CSDN博客专家、CSDN平台优质创作者,高级开发工程师,数学专业,10年以上C/C++, C#, Java等多种编程语言开发经验,拥有高级工程师证书;擅长C/C++、C#等开发语言,熟悉Java常用开发技术,能熟练应用常用数据库SQL server,Oracle,mysql,postgresql等进行开发应用…

转发多台px4仿真UDP数据到地面站

转发脚本的任务需求 仿真采用UDP通信方式&#xff0c;在 wsl 中仿真三台飞机&#xff0c;项目需要将一台电脑中的三台飞机的数据打包发送到另一台飞机的地面站&#xff0c;但地面站是无法直接访问另一台主机的 wsl 中的端口的&#xff0c;wsl 中的端口需要本机才能访问&#x…

FPGA图像处理(五)------ 图片水平镜像

利用bram形成双缓冲&#xff0c;如下图配置所示&#xff1a; wr_flag 表明 buffer0写 还是 buffer1写 rd_flag 表明 buffer0读 还是 buffer1读 通过写入逻辑控制(结合wr_finish) 写哪个buffer &#xff1b;写地址 进而控制ip的写使能 通过状态缓存来跳转buffer的…

微服务八股(自用)

微服务 SpringCloud 注册中心&#xff1a;Eureka 负载均衡&#xff1a;Ribbon 远程调用&#xff1a;Feign 服务熔断&#xff1a;Hystrix 网关&#xff1a;Gateway/Zuul Alibaba 配置中心&#xff1a;Nacos 负载均衡&#xff1a;Ribbon 服务调用&#xff1a;Feign 服务…

ESP32_IDF_OTA_HTTP升级固件

ESP32_IDF_OTA_HTTP升级固件 前言&#xff1a;一个项目的主控使用的是ESP32&#xff0c;因为封装外壳的原因&#xff0c;所以需要采用OTA的方式进行升级&#xff0c;因为之前有对WIFI的OTA有所了解&#xff0c;所以在此基础上&#xff0c;使用官方提供的native_ota_example例程…

MySQL表结构化:数据类型与表生命周期详解

引言 各位数据库学习者大家好&#xff01;今天我们将深入探讨MySQL中最核心的对象——表&#xff08;Table&#xff09;的各类操作 &#x1f3af;。表是存储数据的基石&#xff0c;就像Excel中的工作表一样&#xff0c;但功能要强大得多&#xff01;无论是电商网站的用户信息&…

React中的状态管理Dva总结

在 React 开发中&#xff0c;随着应用的复杂度增加&#xff0c;如何高效地管理应用状态成为了一个非常重要的问题。为了解决这一问题&#xff0c;很多开发者选择了 Redux&#xff0c;然而 Redux 的学习曲线较陡&#xff0c;且需要配置较多的样板代码。为此&#xff0c;Ant Desi…

数据结构中的高级排序算法

希尔排序 你可以将希尔排序理解成——先通过几次分组的、较小的组间插入排序将原数组变得有序&#xff0c;最后再进行一次序列基本有序的完整插入排序。 #include <stdio.h>#define ARR_LEN(arr) (sizeof(arr) / sizeof(arr[0]))void print_arr(int arr[], int len) {for…

计算机视觉最不卷的方向:三维重建学习路线梳理

提到计算机视觉&#xff08;CV&#xff09;&#xff0c;大多数人脑海中会立马浮现出一个字&#xff1a;“卷”。卷到什么程度呢&#xff1f;2022年秋招CV工程师岗位数下降了16%&#xff0c;但求职人数增加了23%&#xff0c;求职人数与招聘岗位的比例达到了恐怖的15:1&#xff0…