skynet源码分析2:模块

2024-05-07 05:18
文章标签 分析 模块 源码 skynet

本文主要是介绍skynet源码分析2:模块,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

actor在skynet中称为模块,每个模块由皮囊和骨骼组成。皮囊承载用户逻辑,骨骼承载内部框架逻辑。

皮囊(skynet_module)

皮囊在框架中用skynet_module对象表示,实现在skynet-src/skynet_module.c中,代表一个动态库.下文用sm来称呼.

先来看看sm的定义,在skynet-src/skynet.h中

复制代码
 1 typedef void * (*skynet_dl_create)(void);
 2 typedef int (*skynet_dl_init)(void * inst, struct skynet_context *, const char * parm);
 3 typedef void (*skynet_dl_release)(void * inst);
 4 typedef void (*skynet_dl_signal)(void * inst, int signal);
 5 
 6 struct skynet_module {
 7     const char * name;
 8     void * module;
 9     skynet_dl_create create;
10     skynet_dl_init init;
11     skynet_dl_release release;
12     skynet_dl_signal signal;
13 };
复制代码

 name:动态库名

module:动态库的句柄

create:契约函数之一,用来创建用户对象。

init:契约函数之一,用来初始化用户对象。inst为用户对象,skynet_context为模块骨骼对象(在皮囊层,不需要知道它是什么,只是调用框架方法时需要用到它,后续或分析它),parm为创建actor时带的参数,等同于main函数中的args参数。

release:契约函数之一,用来释放用户对象。

signal:契约函数之一,用来实现信号功能。signal为信号类型.

 

skynet_module.c这个部分实现了一个动态库加载器,有两个作用:

  1. 加载动态库,获取契约函数指针。
  2. 缓存已加载的动态库。

我们来看看它的实现吧,它头文件的接口定义如下:

复制代码
1 void skynet_module_insert(struct skynet_module *mod);
2 struct skynet_module * skynet_module_query(const char * name);
3 void * skynet_module_instance_create(struct skynet_module *);
4 int skynet_module_instance_init(struct skynet_module *, void * inst, struct skynet_context *ctx, const char * parm);
5 void skynet_module_instance_release(struct skynet_module *, void *inst);
6 void skynet_module_instance_signal(struct skynet_module *, void *inst, int signal);
7 
8 void skynet_module_init(const char *path);
复制代码

在linux下,动态库相关接口定义在<dlfcn.h>中,主要有dlopen,dlsym,dlclose,dlerror函数,dlopen用来加载so库,得到句柄;dlsym用来获取符号的地址;dlclose用来卸载so库;dlerror获取前面几个函数调用失败的错误信息。这几个接口用起来都非常简单。

那么它的具体实现必然是使用dlfcn的几个接口。

下面主要来看看加载逻辑,也就是skynet_module_query函数,3-6的函数是对契约函数的代理调用。

在skynet_module.c中,可以看到如下内部定义:

复制代码
 1 #define MAX_MODULE_TYPE 32
 2 
 3 struct modules {
 4     int count;
 5     struct spinlock lock;
 6     const char * path;
 7     struct skynet_module m[MAX_MODULE_TYPE];
 8 };
 9 
10 static struct modules * M = NULL;
复制代码

m是用于缓存sm,最大32个,path表示动态库的搜索路径,与lua的package的语义一致,后面再说。

来看skynet_module_query,在skynet-src/skynet_module.c的93行:

复制代码
 1 struct skynet_module * 
 2 skynet_module_query(const char * name) {
 3     struct skynet_module * result = _query(name);
 4     if (result)
 5         return result;
 6 
 7     SPIN_LOCK(M)
 8 
 9     result = _query(name); // double check
10 
11     if (result == NULL && M->count < MAX_MODULE_TYPE) {
12         int index = M->count;
13         void * dl = _try_open(M,name);
14         if (dl) {
15             M->m[index].name = name;
16             M->m[index].module = dl;
17 
18             if (_open_sym(&M->m[index]) == 0) {
19                 M->m[index].name = skynet_strdup(name);
20                 M->count ++;
21                 result = &M->m[index];
22             }
23         }
24     }
25 
26     SPIN_UNLOCK(M)
27 
28     return result;
29 }
复制代码

逻辑比较简单,先从缓存查找,未找到就加载,然后添加至缓存。

3-5行是查找缓存,_query为一个数组搜索函数。用自旋锁保证线程安全,9又查找了一次缓存,因为可能在锁竞争的时候,其它线程加载了一个同样的so。

后面这个判断我不觉历,只能加载35个不同的so,也就是说框架不能有35个不同逻辑的actor。

_try_open来看一下,在24行:

复制代码
 1 static void *
 2 _try_open(struct modules *m, const char * name) {
 3     const char *l;
 4     const char * path = m->path;
 5     size_t path_size = strlen(path);
 6     size_t name_size = strlen(name);
 7 
 8     int sz = path_size + name_size;
 9     //search path
10     void * dl = NULL;
11     char tmp[sz];
12     do
13     {
14         memset(tmp,0,sz);
15         while (*path == ';') path++;
16         if (*path == '\0') break;
17         l = strchr(path, ';');
18         if (l == NULL) l = path + strlen(path);
19         int len = l - path;
20         int i;
21         for (i=0;path[i]!='?' && i < len ;i++) {
22             tmp[i] = path[i];
23         }
24         memcpy(tmp+i,name,name_size);
25         if (path[i] == '?') {
26             strncpy(tmp+i+name_size,path+i+1,len - i - 1);
27         } else {
28             fprintf(stderr,"Invalid C service path\n");
29             exit(1);
30         }
31         dl = dlopen(tmp, RTLD_NOW | RTLD_GLOBAL);
32         path = l;
33     }while(dl == NULL);
34 
35     if (dl == NULL) {
36         fprintf(stderr, "try open %s failed : %s\n",name,dlerror());
37     }
38 
39     return dl;
40 }
复制代码

主要是对path这个搜索路径的解析,path可以定义一组路径模板,每个路径用';'隔开,如:“./service/?.so;./http/?.so”,假如模块名为foo,那么就会被解析成"./service/foo.so;./http/foo.so",然后用这两个路径依次调用dlopen,直到有成功加载的。

_open_sym是调用dlsym获取四个契约函数的指针,从实现可以得知,契约函数的命名规则为:模块名_函数名.

最后将sm存入m.


 

骨骼(skynet_context) 


下文用sc代指。

sc主要承载框架的调度逻辑。定义在skynet-src/skynet_server.c中:

复制代码
struct skynet_context {void * instance;struct skynet_module * mod;void * cb_ud;skynet_cb cb;struct message_queue *queue;FILE * logfile;char result[32];uint32_t handle;int session_id;int ref;bool init;bool endless;CHECKCALLING_DECL
};
复制代码

mod:皮囊对象.

instance:用契约函数create创建的。

cb:处理消息的回调函数,由皮囊逻辑里注册。

cb_ud:回调函数的用户数据。

queue:actor的信箱,存放收到的消息。

handle:标识自己的句柄,用于生命周期的管理。

logfile:文件句柄,用与录像功能(将所有收到的消息记录与文件).

result:handle的16进制字符,便于传递。

session_id:上一次分配的session,用于分配不重复的session。

ref:引用计数。

init:是否初始化。

endless:是否在处理消息时死循环。

这里只分析sc的创建,先来看创建函数skynet_context_new,定义在skynet-src/skynet_server.c的119行:

复制代码
 1 struct skynet_context * 
 2 skynet_context_new(const char * name, const char *param) {
 3     struct skynet_module * mod = skynet_module_query(name);
 4 
 5     if (mod == NULL)
 6         return NULL;
 7 
 8     void *inst = skynet_module_instance_create(mod);
 9     if (inst == NULL)
10         return NULL;
11     struct skynet_context * ctx = skynet_malloc(sizeof(*ctx));
12     CHECKCALLING_INIT(ctx)
13 
14     ctx->mod = mod;
15     ctx->instance = inst;
16     ctx->ref = 2;
17     ctx->cb = NULL;
18     ctx->cb_ud = NULL;
19     ctx->session_id = 0;
20     ctx->logfile = NULL;
21 
22     ctx->init = false;
23     ctx->endless = false;
24     // Should set to 0 first to avoid skynet_handle_retireall get an uninitialized handle
25     ctx->handle = 0;    
26     ctx->handle = skynet_handle_register(ctx);
27     struct message_queue * queue = ctx->queue = skynet_mq_create(ctx->handle);
28     // init function maybe use ctx->handle, so it must init at last
29     context_inc();
30 
31     CHECKCALLING_BEGIN(ctx)
32     int r = skynet_module_instance_init(mod, inst, ctx, param);
33     CHECKCALLING_END(ctx)
34     if (r == 0) {
35         struct skynet_context * ret = skynet_context_release(ctx);
36         if (ret) {
37             ctx->init = true;
38         }
39         skynet_globalmq_push(queue);
40         if (ret) {
41             skynet_error(ret, "LAUNCH %s %s", name, param ? param : "");
42         }
43         return ret;
44     } else {
45         skynet_error(ctx, "FAILED launch %s", name);
46         uint32_t handle = ctx->handle;
47         skynet_context_release(ctx);
48         skynet_handle_retire(handle);
49         struct drop_t d = { handle };
50         skynet_mq_release(queue, drop_message, &d);
51         return NULL;
52     }
53 }
复制代码

1、加载sm对象,调用create取得用户对象.

2、分配sc,注册handle,分配信箱.

3、调用init初始化用户对象.

之所以到处有一些CALLINGCHECK宏,主要是为了检测调度是否正确,因为skynet调度时,每个actor只会被一个线程持有调度,也就是消息处理是单线程的。


未完待续,不当之处请道友指正。

这篇关于skynet源码分析2:模块的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/966427

相关文章

Java 正则表达式URL 匹配与源码全解析

《Java正则表达式URL匹配与源码全解析》在Web应用开发中,我们经常需要对URL进行格式验证,今天我们结合Java的Pattern和Matcher类,深入理解正则表达式在实际应用中... 目录1.正则表达式分解:2. 添加域名匹配 (2)3. 添加路径和查询参数匹配 (3) 4. 最终优化版本5.设计思

Java字符串操作技巧之语法、示例与应用场景分析

《Java字符串操作技巧之语法、示例与应用场景分析》在Java算法题和日常开发中,字符串处理是必备的核心技能,本文全面梳理Java中字符串的常用操作语法,结合代码示例、应用场景和避坑指南,可快速掌握字... 目录引言1. 基础操作1.1 创建字符串1.2 获取长度1.3 访问字符2. 字符串处理2.1 子字

python logging模块详解及其日志定时清理方式

《pythonlogging模块详解及其日志定时清理方式》:本文主要介绍pythonlogging模块详解及其日志定时清理方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地... 目录python logging模块及日志定时清理1.创建logger对象2.logging.basicCo

Qt spdlog日志模块的使用详解

《Qtspdlog日志模块的使用详解》在Qt应用程序开发中,良好的日志系统至关重要,本文将介绍如何使用spdlog1.5.0创建满足以下要求的日志系统,感兴趣的朋友一起看看吧... 目录版本摘要例子logmanager.cpp文件main.cpp文件版本spdlog版本:1.5.0采用1.5.0版本主要

Python 迭代器和生成器概念及场景分析

《Python迭代器和生成器概念及场景分析》yield是Python中实现惰性计算和协程的核心工具,结合send()、throw()、close()等方法,能够构建高效、灵活的数据流和控制流模型,这... 目录迭代器的介绍自定义迭代器省略的迭代器生产器的介绍yield的普通用法yield的高级用法yidle

C++ Sort函数使用场景分析

《C++Sort函数使用场景分析》sort函数是algorithm库下的一个函数,sort函数是不稳定的,即大小相同的元素在排序后相对顺序可能发生改变,如果某些场景需要保持相同元素间的相对顺序,可使... 目录C++ Sort函数详解一、sort函数调用的两种方式二、sort函数使用场景三、sort函数排序

Java调用C++动态库超详细步骤讲解(附源码)

《Java调用C++动态库超详细步骤讲解(附源码)》C语言因其高效和接近硬件的特性,时常会被用在性能要求较高或者需要直接操作硬件的场合,:本文主要介绍Java调用C++动态库的相关资料,文中通过代... 目录一、直接调用C++库第一步:动态库生成(vs2017+qt5.12.10)第二步:Java调用C++

kotlin中const 和val的区别及使用场景分析

《kotlin中const和val的区别及使用场景分析》在Kotlin中,const和val都是用来声明常量的,但它们的使用场景和功能有所不同,下面给大家介绍kotlin中const和val的区别,... 目录kotlin中const 和val的区别1. val:2. const:二 代码示例1 Java

Go标准库常见错误分析和解决办法

《Go标准库常见错误分析和解决办法》Go语言的标准库为开发者提供了丰富且高效的工具,涵盖了从网络编程到文件操作等各个方面,然而,标准库虽好,使用不当却可能适得其反,正所谓工欲善其事,必先利其器,本文将... 目录1. 使用了错误的time.Duration2. time.After导致的内存泄漏3. jsO

Python实现无痛修改第三方库源码的方法详解

《Python实现无痛修改第三方库源码的方法详解》很多时候,我们下载的第三方库是不会有需求不满足的情况,但也有极少的情况,第三方库没有兼顾到需求,本文将介绍几个修改源码的操作,大家可以根据需求进行选择... 目录需求不符合模拟示例 1. 修改源文件2. 继承修改3. 猴子补丁4. 追踪局部变量需求不符合很