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

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

一.Skynet底层架构支持

1.Actor

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

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

2.config

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

Lua 复制代码
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是怎么运行的

Lua 复制代码
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.listensocket.start 注册了一个网络监听事件

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

服务 Actor 在处理消息时执行回调函数(打印客户端信息)
skynet.timeout 注册了一个定时器事件

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

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

main

Lua 复制代码
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

Lua 复制代码
local skynet = require "skynet"

local CMD = {}

function CMD.ping()
    skynet.retpack("pong")
end

skynet.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接口
cpp 复制代码
#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);
    //向栈顶压入整数参数1
    lua_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;
}
Lua 复制代码
package.cpath = "luaclib/?.so"
local so = require "tbl.c"



--a=1
--全局可见

--local a =1
--当前文件可见

-- 加了local相当于C/C++中的static

function Init(args)
    print("call [init] function", args)
end

function Loop()
    print("call [loop] function")
    for k, v in ipairs({1,2,3,4,5}) do
    --so.echo(v)  -- 等于 调用 5 次  因为每次调用都是一个新的虚拟栈,所以没有必要维护 --->C调用lua然后调C
   print("value = " .. v)  --->C调用lua
    end
    return 1
end

function Release()
    print("call [release] function")
end

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

3.C调用lua接口-->lua再调用C接口
cpp 复制代码
#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);
    //向栈顶压入整数参数1
    lua_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;
}
Lua 复制代码
package.cpath = "luaclib/?.so"
local so = require "tbl.c"



--a=1
--全局可见

--local a =1
--当前文件可见

-- 加了local相当于C/C++中的static

function Init(args)
    print("call [init] function", args)
end

function Loop()
    print("call [loop] function")
    for k, v in ipairs({1,2,3,4,5}) do
    so.echo(v)  -- 等于 调用 5 次  因为每次调用都是一个新的虚拟栈,所以没有必要维护 --->C调用lua然后调C
    --print("value = " .. v)  --->C调用lua
    end
    return 1
end

function Release()
    print("call [release] function")
end
cpp 复制代码
#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接口

cpp 复制代码
#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;
}
Lua 复制代码
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  每次调用都有一个虚拟栈
]]

cpp 复制代码
#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);// 1
    lua_pushinteger(L, 0);// 2
	luaL_setfuncs(L, l, 1);// 上值
    // luaL_newlib(L, l);
	return 1;
}
Lua 复制代码
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

cpp 复制代码
#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;
}
Lua 复制代码
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不需要维护栈空间 因为每次调用都会生成一个新栈

相关推荐
老猿讲编程41 分钟前
提升ARM Cortex-M系统性能的关键技术:TCM技术解析与实战指南
arm开发·arm·c
科大饭桶18 小时前
数据结构自学Day15 -- 非比较排序--计数排序
数据结构·算法·leetcode·排序算法·c
慢慢沉19 小时前
Lua(数据库访问)
开发语言·数据库·lua
慢慢沉19 小时前
Lua协同程序(coroutine)
lua
慢慢沉2 天前
Lua元表(Metatable)
lua
鑫宇吖2 天前
IAR编辑器如何让左侧的工具栏显示出来?
编辑器·嵌入式·c·iar
慢慢沉2 天前
Lua(字符串)
开发语言·lua
慢慢沉2 天前
Lua(数组)
开发语言·lua
慢慢沉2 天前
Lua(迭代器)
开发语言·lua
慢慢沉2 天前
Lua基本语法
开发语言·lua