skynet 阅读笔记

原创
2015/08/20 13:07
阅读数 1.2K

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 消息类型 协议类型

展开阅读全文
打赏
0
2 收藏
分享
加载中
更多评论
打赏
0 评论
2 收藏
0
分享
返回顶部
顶部