文档章节

skynet 阅读笔记

liyong2
 liyong2
发布于 2015/08/20 13:07
字数 1917
阅读 439
收藏 2

https://github.com/cloudwu/skynet

skynet 整体代码分为3个部分:

1: skynet-src 核心网络层

2: service-src 服务层

3: service 使用lua实现的服务

skynet 实现是一套Actor模式的网络架构,Actor 中核心的概念包括:

Worker 工作执行器

Actor  封装了上下文的相关服务实现

master 调度分配器

在skynet的核心网络层中,主要包含:

worker的实现

module的加载, 用于支持actor的实现

网络分发和监听 以及相关worker的调度

module加载采用dlsym 的字符串加载方式,优点便于配置,缺点阅读代码查找相关实现比较麻烦。

module加载的service_snlua 服务将用于 加载执行lua代码,并将上下文环境保存到lua虚拟机中,从而构成一个Actor

在skynet_start.c 中的 thread_worker 是工作执行器的实现,

通过从消息队列中获取消息,并将消息分发出去交给service来执行。

当没有消息的时候 worker线程将会等在一个条件变量上。

分配器的实现

Skynet 存在一个全局消息队列的队列 global_queue,每个worker 从 global_queue 中获取一个消息队列,接着执行当前消息队列的上下文中的回调函数。

skynet_context_new 函数用于分配一个新的上下文,并且生成一个新的消息队列,而每个上下文相当于一个Actor, 而上下文对应的消息队列 相当于一个 mailbox, 这也就解释了 在 thread_worker 线程中为什么依次去 取不同的消息队列的消息,防止某个Actor被饿死。

在skynet 存在一个 command_func cmd_funcs 里面包含了所有的服务器命令,总共18条,分别为

    { "TIMEOUT", cmd_timeout }
    { "REG", cmd_reg },
    { "QUERY", cmd_query },
    { "NAME", cmd_name },
    { "NOW", cmd_now },
{ "EXIT", cmd_exit },
{ "KILL", cmd_kill },
{ "LAUNCH", cmd_launch },
{ "GETENV", cmd_getenv },
{ "SETENV", cmd_setenv },
{ "STARTTIME", cmd_starttime },
{ "ENDLESS", cmd_endless },
{ "ABORT", cmd_abort },
{ "MONITOR", cmd_monitor },
{ "MQLEN", cmd_mqlen },
{ "LOGON", cmd_logon },
{ "LOGOFF", cmd_logoff },
{ "SIGNAL", cmd_signal },

当某处代码调用LAUNCH 命令的时候就会新建一个上下文,同时分配一个新的消息队列给该上下文,这样Worker就可以执行消息的分发了。

而skynet_send 以及 skynet_sendname 函数实现了向消息队列投递消息;

这样整个调度基本流程就清晰了:

  1. 首先通过LAUNCH 命令启动一个上下文,创建一个消息队列
  2. 接着通过skynet_send 向消息队列中投递消息
  3. Worker线程在合适的时机将消息分发给对应上下文的 处理函数

启动流程

在skynet_start 启动函数中, 通过执行 snlua bootstrap 命令,执行系统初始化

snlua bootstrap表示执行 bootstrap.lua 脚本来具体初始化。

bootstrap 中将会注册需要的一些 lua服务。

而调用 skynet_socket_init 将会启动socket服务器

消息的分类

skynet中消息分为两类:

  1. 服务器内部各个Actor之间互相投递消息
  2. 通过socket 消息投递

thread_socket 为socket线程主循环,主要函数是skynet_socket_poll ,

存在两个方向数据:即从socket接受到的网络数据,和从内部向外发送的网络数据。

  1. 从网络接受的数据通过 forward_message 推送给合适的消息队列,通过 skynet_context_push 函数。
  2. 发送给网络的数据 skynet_socket_send 接口来发送

skynet_socket是一个基础,在之上构建的服务实例也是一个Actor, 例如service_gated 服务, 以及lua的 gateserver 服务。

这样当内部服务需要通过socket向外部发送消息的时候,首先将message 投递给gate,接着由gate来调用实际的socket接口向外发送消息。

一个例子如下:

  1. 首先启动gate
  2. 客户端连接gate gate 根据消息创建对应的 Actor Agent 来处理
  3. Agent 可以通过gate 来向对应客户端投递消息, 客户端消息也可以通过gate 传递给agent

Actor 的实现

lua层中通过实例化一个服务来构建一个新的lua状态机,从而构建一个独立的Actor。 lua中的接口为 skynet.newservice 最终调用skynet_context_new 来构建新的Actor。

每当socket 需要接受或者发送数据,或者timer定时器时间到了,都会调用wakeup, 这样worker线程就会去获取消息队列数据. 系统存在一个Monitor线程, 用于监控所有其它线程,包括socket线程,timer线程,worker线程。

Timer 实现

skynet_timer 有一个 skynet_timeout API, 在skynet_server 中存在一个 TIMEOUT命令,当接受到TIME_OUT命令的时候,就会增加一个新的timer。

lua层接口为 skynet.timeout。

Timer主要应用: 某些Actor需要定期执行,通过skynet.timeout 创建一个协程定期调用函数。

lua协程

skynet lua接口中有一个 co_create 用于创建协程。在以下几种情况下创建:

  1. 增加一个timeout 定时器, 定时后执行协程
  2. fork 一个函数,用于创建一个新的 协程,将协程加入到fork_queue中,在下次分发消息的时候执行该协程
  3. raw_dispatch_message 当分发消息的时候将会将对应的消息处理函数放入协程中

协程可以等在特定的事件上,当事件发生的时候,唤醒对应协程的执行,这个通过一个suspend函数实现的,将session, 事件以及对应的协程保存起来,当事件发生,找到对应的协程,接着执行协程即可。

skynet的协议以及投递消息寻址方式

skynet 中发送消息的接口主要两个 skynet_send 和skynet_sendname.

lua层接口 skynet.send

发送消息的时候一般需要提供,当前发送服务的上下文,消息投递来源,消息目标服务接受者,消息类型,消息会话id,消息数据和消息数据大小。

参考样例:watchdog.lua 和agent.lua

在watchdog.lua中,close_agent 调用skynet.send(目标服务对象agent, 协议类型lua, 数据内容disconnect)

这样就通过lua打包协议,将disconnect 字符串发送给了 一个agent服务对象。

在agent.lua中,skynet.start( skynet.dispatch("lua" ))

对通过lua协议分发来的消息,注册相关的处理函数。在skynet.lua 的 raw_dispatch_message 函数中,当收到新的消息,则根据协议类型lua 找到处理函数.

而agent.lua 中调用skynet.ret()含义为,将函数执行的结果按原路yield返回给请求方。

而调用agent.lua 的分发处理函数的 raw_dispatch_message 中以协程方式调用该函数,因此在suspend 函数中将会收到,RETURN 命令,而将函数调用结果返回给watchdog.lua

在watchdog.lua 的socket.OPEN 函数中,调用了skynet.call 这个接口和skynet.send不同在于call 需要等待请求的服务方的返回。

skynet.call实现使用了 yield_call函数,将会将目标服务和session匹配起来,接着yield一个call命令, 当对方服务的RESPONSE 发送回来的时候,会带上相关的会话id,接着在raw_dispatch_message 中检测到协议为 RESPONSE, 根据会话id 去除watchdog.lua 的挂起的协程,继续执行watchdog的协程。

skynet 的网络模型

skynet 底层只有skynet_send 标准接口用于向目标投递消息,不同的通讯模型建立在这个基础之上。

  1. request response lua层skynet.call 将启动一个会话,并将lua协程等在该会话上,当response发送回来之后则重启协程执行

  2. request lua 层skynet.send 直接调用c接口,发送数据包

  3. push 每当skynet raw_dispatch_message 时,都会启动一个协程来处理对应的消息, 也就是每个消息对应一个协程, 在同一个Actor中可以同时存在多个协程

skynet 消息类型 协议类型

© 著作权归作者所有

liyong2

liyong2

粉丝 49
博文 195
码字总数 64266
作品 0
广州
程序员
私信 提问
skynet 入门笔记(1):Hello, skynet!

Hello, Skynet! Skynet这名字让我想起了经典科幻电影《终结者》里毁灭人类世界的终极人工智障,skynet的官方文档是挺给力的,但是没有那么好的引导机制,看了半天的文档还是不知道该怎么用s...

uniqptr
2018/06/27
0
0
skynet 入门笔记(2):service 消息收发

Skynet 入门笔记(2):Service 消息收发 编写第一个 service 成功了,接下来考虑多个 service 之间如何通信的问题。 skynet 是单进程多线程框架,每个 lua service 独立运行在自己的 lua vm...

uniqptr
2018/06/27
0
0
开源并发框架 Skynet 发布第一个正式版 v0.1.0

距离 skynet 开源项目的公布 已经有 20 月+ 了,如果从闭源阶段算起,已经超过了 30 个月。在我们公司内部有五个项目使用 skynet 开发,据有限的了解,在我们公司之外,至少有两个正式项目使...

C_Z
2014/04/23
3.9K
6
游戏服务器架构调研报告

服务器架构调研报告 刘源霖20151119 1. 前言 本文档主要是调研分析新的手游服务端架构,为下一款手游服务端研发提供可参考的方案。主要的参考点是数据持久化,并发效率,分布式,沙盒机制,热...

shezjl
2016/01/22
2K
1
当我设计游戏服务器时,我在想些什么?(2)

半年前我参与了一个手游项目,第一次能够主导整个游戏的设计,这篇文章单说服务器的架构,客户端就不提了。 对于服务端,我想从之前的端游服务器改过来肯定是走不通的(详见:),因为手游的...

rangercyh
2015/07/13
0
0

没有更多内容

加载失败,请刷新页面

加载更多

热点图heatMap.js V2.0 研究笔记 及应用

https://blog.csdn.net/rongchaoliu/article/details/47830799 调用方法 function init() { $.ajax({ url: "${request.contextPath}/refresh?type=1", type: "get",......

yan_liu
7分钟前
0
0
Kubernetes 支持 OpenAPI 的新功能

Open API 让 API 提供者可以定义自己的操作和模型,并让开发者可以自动化的生成喜欢语言的客户端,用以和 API 服务器通信。Kubernetes 已经支持 Swagger 1.2(OpenAPI 规范的前身)有一段时间...

xiangyunyan
11分钟前
0
0
深入分析Zookeeper原理

本章重点: 1.了解zookeeper 及zookeeper 的设计猜想 2. zookeeper集群角色 3. 深入分析ZAB协议 4. 从源码层面分析leader选举的实现过程 5. 关于zookeeper的数据存储  Zookeeper的由...

须臾之余
13分钟前
1
0
Spring Cloud Eureka 常用配置详解,建议收藏!

前几天,栈长分享了 《Spring Cloud Eureka 注册中心集群搭建,Greenwich 最新版!》,今天来分享下 Spring Cloud Eureka 常用的一些参数配置及说明。 Spring Boot 的配置参考Java技术栈微信...

Java技术栈
31分钟前
0
0
分布式项目(七)consul 服务注册与发现

说到分布式自然就离不开分布式和微服务的话题,简单聊一下。 微服务是一种软件架构方式,或者说一个一种结构设计风格,它并不是标准,它的逻辑是把一个整体服务按业务拆分成不同独立的服务,...

lelinked
32分钟前
0
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部