当前位置: 首页 > news >正文

轻量级游戏服务器框架:skynet的原理讲解

一:skynet的介绍

首先他是一个轻量级的游戏服务器框架,但是他的作用并不只是在游戏中。那么他的轻量级体现在什么地方?

1:实现了 actor 模型,以及相关的脚手架(工具集):

actor 间数据共享机制;

c 服务扩展机制;

2:实现了服务器框架的基础组件:

实现了 reactor 并发网络库,并提供了大量连接的接入方案;

基于自身网络库,实现了常用的数据库驱动(异步连接方案),并融合了 lua 数据结构;

实现了网关服务;

时间轮用于处理定时消息;

二:多核并发编程

多线程:

在一个进程中开启多线程,为了充分利用多核,一般设置工作线程的个数为 cpu 的核心数;

多线程在一个进程当中,所以数据共享来自进程当中的虚拟内存;这里会涉及到很多临界资源的访问,所以需要考虑加锁;

多进程:

在一台机器当中,开启多个进程充分利用多核,一般设置工作进程的个数为 cpu 的核心数;

nginx 就是采用这种方式(master 进程 和多个 worker进程);

nginx 当中的 worker 进程,通过共享内存来进行共享数据;也需要考虑使用锁;

CSP:

以 go 语言为代表,并发实体是协程(用户态线程、轻量级线程);

内部也是采用多少个核心开启多少个线程来充分利用多核;

Actor:

erlang 从语言层面支持 actor 并发模型,并发实体是 actor(在skynet 中称之为服务);

skynet 采用 c + lua 来实现 actor 并发模型;

底层也是通过采用多少个核心开启多少个内核线程来充分利用多核;

        总结:我们要尽量不通过共享内存来通信,而应该通过通信来共享内存。通过通信来共享数据,其实是一种解耦合的过程;并发实体之间可以分别开发并单独优化,而它们唯一的耦合在于消息;这能让我们快速地进行开发;同时也符合我们开发的思路,将一个大的问题拆分成若干个小问题;

三:Actor 并发模型

1:定义

Actor用于并行计算,并且是最基本的计算单元。Actor基于消息计算,并且通过消息进行通信。

2:组成

隔离的环境:主要通过 lua 虚拟机来实现;
消息队列:用来存放有序(先后到达)的消息;
回调函数:用来运行 Actor;从 Actor 的消息队列中取出消息,并作为该回调函数的参数来运行 Actor;在skynet.start 中会设置回调函数,一个消息执行的时候,会获取一个协程执行它;

Actor是skynet在用户层进行抽象的一个进程,为什么要进行抽象进程呢?

        我们知道在 lua 中有虚拟机(拥有隔离的环境),而加载这个虚拟机的代价较小,而在同一个进程中的多个lua虚拟机可以共享很多lua资源。当我们抽象一个进程之后,那么我们就可以提供一个隔离的运行环境,避免多线程的资源竞争(避免多个抽象进程消费同一资源)。

3:Actor 的公平调度

        首先我们一个skynet中含有多个 actor ,而这些actor全都是对等的,并且他们每个actor中都含有消息队列。线程池的并发实体是线程,nginx的并发实体是进程,而skynet的并发实体是actor,所以我们需要公平调度actor。

        对于很多的actor来说,我们需要采用两级队列来进行公平调度:首先我们需要找到其中含有消息的actor(活跃队列),将这些actor连接在一起形成一级队列,然后开始调度队列,调度的时候,我们轮到哪个actor,查看他的消息队列,从中取出一个消息任务,这些消息队列组成的就是二级队列。我们这个公平调度是每一个actor进行pop出这个消息队列中的一个任务后,然后将他pushback到一级队列的末尾,然后继续下一个。

        但是在我们用户定义的actor中,并不是每一个消息队列的消息是一样多的,一般都是不均匀的,因此skynet在工作线程中赋予了权重来解决这个问题

// 工作线程权重图   32个核心
static int weight[] = {-1, -1, -1, -1, 0, 0, 0, 0,1, 1, 1, 1, 1, 1, 1, 1,  // 1/22, 2, 2, 2, 2, 2, 2, 2,  // 1/43, 3, 3, 3, 3, 3, 3, 3, }; // 1/8

比如一次pop出一半的任务,四分之一的任务,这样对于消息很多的队列来说就比较友好。

四:skynet的具体使用

1:skynet的简单程序

首先我们需要在skynet存在的目录中创建一个config文件,在这个文件中我们要指定一些参数:

thread=4                --线程的数量
logger=nil              --日志产生的位置
harbor=0                --设置集群,这里不设置
start="main"            --启动的应用程序
lua_path="./skynet/lualib/?.lua;./skynet/lualib/?/init.lua;"    --lua的路径
luaservice="./skynet/service/?.lua;./5.1-skynet/?.lua"                 --lua服务的路径
lualoader="./skynet/lualib/loader.lua"                          --lua加载器
cpath="./skynet/cservice/?.so"                                  --c服务的路径
lua_cpath="./skynet/luaclib/?.so"                               --lua的c服务的路径
--其中留意一下分号,分号后也是一个路径

然后我们开始写main.lua的代码:

local skynet = require "skynet"skynet.start(function()print("hello skynet")
end)

然后创建一个Makefile文件:

SKYNET_PATH?=./skynetall:cd $(SKYNET_PATH) && $(MAKE) PLAT='linux'clean:cd $(SKYNET_PATH) && $(MAKE) clean

2:skynet网络消息

        在skynet中含有一个socket的线程,专门接收消息,并且采用了reactor模型,对于众多的actor中,我们怎么知道这个网络消息是发送给谁的呢?我们在这个接收的时候,会进行绑定,这样就不会丢失了。

local skynet = require "skynet"
local socket=require "skynet.socket"skynet.start(function()print("hello skynet1")local listenfd=socket.listen("0.0.0.0",8081);socket.start(listenfd,function(clientfd,addr)print("receive a client: ",clientfd,addr);end)print("hello skynet2")
end)

3:定时消息

创建的定时任务被推送给定时线程,定时线程检测完时间后往消息队列推送一个消息,然后actor开始执行callback函数。

local skynet = require "skynet"skynet.start(function()print("hello skynet")skynet.timeout(100,function()print("已经过了 1s");end)end)

4:actor之间的消息

在actor之间的消息,是通过发送消息进行数据交换的,发送的消息将存放到对方的消息队列中。

local skynet = require("skynet")skynet.start(function()print("hello skynet")local slave=skynet.newservice("slave")local response=skynet.call(slave,"lua","ping")print("main: ",response)
end)
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)

本篇主要讲解了skynet的基础使用和原理,感谢大家的观看!0voice · GitHub 


http://www.mrgr.cn/news/65880.html

相关文章:

  • 【论文阅读】Associative Alignment for Few-shot Image Classification
  • 【WebRTC】WebRTC的简单使用
  • 模块功能的描述方法
  • HTB:Shocker[WriteUP]
  • 应用层代理技术
  • ubuntu20.04 加固方案-设置用户缺省UMASK
  • Hadoop简介及单点伪分布式安装
  • C++:模拟实现STL的vector
  • 【含文档】基于ssm+jsp的宠物猫狗商业系统 (含源码+数据库+lw)
  • HashMap 源码分析
  • git上传大文件的解决方案
  • 文件系统上云的挑战
  • 详解:枚举类
  • 关于Linux系统调试和性能优化技巧有哪些?
  • 计算机启动过程中各个步骤
  • 全面解析:物联网技术及其应用
  • ACTF新生赛2020:NTFS数据流
  • C++——用指向指针的指针的方法对n个整数排序并输出。要求将排序单独写成一个团数,整数和n在主函数中输人,最后在主函数中输出。
  • 十四届蓝桥杯STEMA考试Python真题试卷第二套第四题
  • 一文了解Android SELinux
  • Golang | Leetcode Golang题解之第538题把二叉搜索树转换为累加树
  • Python | Leetcode Python题解之第538题把二叉搜索树转换为累加树
  • DDD学习笔记
  • 麻省理工学院的研究人员最近开发了一种新的机器人训练方法
  • 阿里云服务器 篇十:自动定时备份CSDN博客内容
  • 十四届蓝桥杯STEMA考试Python真题试卷第二套第五题