JavenLaw

热爱可抵岁月漫长,坚持可解道阻且长

Skynet使用之:c-lua接口和so动态库

Skynet中的例子 c-lua的接口是比较重要的一部分 因为skynet底层使用的c语言,上层使用lua来实现,必然关涉到c-lua的交互 ​ 我们重点关注:skynet/lualib-src目录下的文件 ​ 在平时分析Skynet源码的时候,我们经常可以看到类似的调用 local c = require "skynet.core" -- 这里的skynet就是在 skynet/lualib-src/lua-skynet.c 文件下实现的 -- 只不过是 skynet/lualib-src 文件下的c文件,最终都被编译到 skynet/luaclib 下 -- 详细见《Skynet源码准备》 在skynet/lualib/skynet.lua中 local c = require "skynet.core" -- 除了一些普通的API调用 c.send() c.error() -- skynet还封装了一些常用的命令 -- 但是通过统一的接口来使用 c.intcommand("STAT", "mqlen") c.command("EXIT") -- 以上命令就能直接在lua层调用c层的函数 -- 当然这种形式最后得需要c文件编译为so库才行 而在skynet/lualib-src/lua-skynet.c中 LUAMOD_API int luaopen_skynet_core(lua_State *L) { luaL_checkversion(L); luaL_Reg l[] = { { "send" , lsend }, { "genid", lgenid }, { "redirect", lredirect }, { "command" , lcommand }, { "intcommand", lintcommand }, { "addresscommand", laddresscommand }, { "error", lerror }, { "harbor", lharbor }, { "callback", lcallback }, { "trace", ltrace }, { NULL, NULL }, }; // functions without skynet_context luaL_Reg l2[] = { { "tostring", ltostring }, { "pack", luaseri_pack }, { "unpack", luaseri_unpack }, { "packstring", lpackstring }, { "trash" , ltrash }, { "now", lnow }, { "hpc", lhpc }, // getHPCounter { NULL, NULL }, }; // 部分代码省略 } // 可以看到,之前在lua层的函数调用,在c层都有一一对应的c函数实现 最终完成了c-lua的交互
2024-03-09 阅 读 全 文

Skynet第三方之:zlog日志

什么是zlog 先了解什么是zlog zlog是一个高可靠性、高性能、线程安全、灵活、概念清晰的纯C日志函数库 官网:一个可靠的纯C日志库 zlog library homepage (hardysimpson.github.io) GitHub:HardySimpson/zlog: A reliable, high-performance, thread safe, flexsible, clear-model, pure C logging library. (github.com) 中文文档:zlog使用手册 (hardysimpson.github.io) ​ Skynet日志存在的问题 为什么需要使用zlog是我们首选需要回答的问题 回顾Skynet的日志,主要有《Skynet源码之:service_logger》和《Skynet源码之:日志打印》,但是以上2者都存在问题 ​ 《Skynet源码之:service_logger》:同个进程内,所有的服务需要打印日志的话,就不能再使用printf(c层面),print(lua层) 甚至标准输入/输出都不再使用(详细见《Skynet源码之:守护进程》中的 redirect_fds() 输出重定向) 因此每个服务都需要调用 skynet.error() 函数来打印日志,这又会造成2个问题: ​ 假设某个节点内,开启了1000个服务,每个服务可能都需要一定数量的打印 ​ 因此所有服务都会给这个节点内的logger服务发送大量的消息 ​ 这必然会造成logger服务占用工作线程来打印日志,造成性能损失 ​ ​ 因为Skynet并没有对日志的划分做一些设计 ​ 因此在实际的工作中,例如日志划分、转存、格式几乎没有好的支持 ​ 即在实际工作项目中,这个日志是很难用的 ​ 所以我们需要引入一个第三方的日志库zlog来帮助我们实现日志的划分、存档、格式打印等功能 ​ 如何在Skynet中引入zlog 很显然,zlog是一个纯c的日志库,我们需要封装一下给lua层的服务调用 做法就是c-lua的动态库的制作,一般为 ​ 通过luaopen_zlog(lua_State *L)函数在lua层调用了zlog的c函数 ​ (详细的c-lua的交互还是直接看代码比较清晰) ​ 最后我们得到一个zlog.so动态库,在lua层直接获取 ​ local zlog = require “zlog” 然后根据skynet中 local c = skynet.
2024-02-15 阅 读 全 文

Skynet第三方之:lz4压缩

什么是lz4 先了解什么是lz4 LZ4是一种无损快速压缩算法,旨在提供非常高的压缩和解压缩速度。 官网:LZ4 - Extremely fast compression GitHub:[lz4/lz4: Extremely Fast Compression algorithm (github.com)](https://github.com/HardySimpson/zlog) ​ 为什么要使用压缩 为什么需要使用lz4是我们首选需要回答的问题 答案非常简单 ​ 请求量过大:当某个时间点(例如开服)配置的请求量过大的时候,造成带宽流量的占用 ​ 配置太大:当某个配置数据量太大,造成单个包超过64kb时,可能会出现效率或者其他问题 ​ (这个问题不是很确定,参看《Skynet第三方之:sproto协议》) ​ 云风的 BLOG: 为什么 skynet 提供的包协议只用 2 个字节表示包长度 (codingnow.com) ​ 如何使用lz4压缩 使用lz4压缩和使用zlog的方式差不多,都是需要编译成so库的形式,例如 local lz4 = require "lz4" -- 但是使用的时候,我们一般用来压缩配置 -- 大体积配置生成 lz4 压缩数据 -- 不走 proto 协议 -- 走 json 格式 -- 压缩后发送给客户端解压 -- -- 即我们需要先把配置转换为json格式 local str = json.encode(conf) -- 然后我们在压缩配置 local e = lz4.compress(str) 最后客户段收到数据后,在lz4解压,解压完成之后,再用json把str转为lua的table,即最终的conf配置
2024-02-14 阅 读 全 文

Skynet第三方之:sproto协议

什么是sproto 我们先看看云风设计sproto的初衷以及sproto的改进历史 1,云风的 BLOG: 设计一种简化的 protocol buffer 协议 (codingnow.com) 2,云风的 BLOG: sproto 的实现与评测 (codingnow.com) 3,云风的 BLOG: 给 sproto 增加 unordered map 的支持 (codingnow.com) 4,云风的 BLOG: skynet 近期更新及 sproto 若干 bug 的修复 (codingnow.com) 5,云风的 BLOG: sproto rpc 的用法 (codingnow.com) 6,云风的 BLOG: sproto 的缺省值处理 (codingnow.com) 7,云风的 BLOG: sproto 的一些更新 (codingnow.com) 8,云风的 BLOG: 为什么 skynet 提供的包协议只用 2 个字节表示包长度 (codingnow.com) ​ 官方项目 cloudwu/sproto: Yet another protocol library like google protocol buffers , but simple and fast.
2024-02-13 阅 读 全 文

Go基本命令

最近准备学习 Go 语言,总结了一些常见的 Go 命令,用于 Go 环境设置,项目构建等 主要有: ​ GOROOT,GOPATH,GOBIN,GOPROXY,GO111MODULE 等环境设置 ​ go env,go run,go build,go install,go mod,go get,go clean 等命令 ​ 最后记录了一下 go.mod 文件常见的格式 GOROOT 安装目录:/usr/local/go GOPATH 工作目录:/root/go ​ GOPATH/bin ​ GOPATH/pkg ​ GOPATH/src GOBIN 生成目录:GOPATH/bin GOPROXY 设置代理 ​ go env -w GOPROXY=“https://goproxy.cn,direct” GO111MODULE 开启包管理 ​ go env -w GO111MODULE=“on” go env 查看 go 环境设置 ​ go env go run 只执行,不会产生任何文件,一定要有 main 包,没有 main 包会报错 ​ go run file_name
2024-01-30 阅 读 全 文

Skynet源码之:进程启动(15)

回顾 我们可以回过头来看看skynet之前的准备 ​ 1,环境准备 ​ 2,守护进程 ​ 3,节点建立 ​ 4,服务管理 ​ 5,消息队列 ​ 6,模块加载 ​ 7,定时器 ​ 8,监视器 ​ 9,网络模块 ​ 10,日志打印 在经过一系列步骤后,我们最终还是要把这些内容交由线程去执行 后面就会触发下面的内容 ​ 11,service_logger ​ 12,service_sblua ​ 13,service_harbor ​ 14,服务的实现 现在就让我们带着以上的内容,开启我们的进程 让skynet正式跑起来 ​ 创建线程 让我们看看在哪里调用的线程创建 // skynet_server.c文件,289行 start(config->thread); // 具体实现如下 // skynet_server.c文件,182行 static void start(int thread) { // 可以看到,我们创建的线程数是:配置的线程字段 + 3 // 是因为 config->thread 的含义是:worker线程的数量 // 多的3个线程分别是:timer,socket,monitor pthread_t pid[thread+3]; // 对monitor线程进程初始化 struct monitor *m = skynet_malloc(sizeof(*m)); memset(m, 0, sizeof(*m)); m->count = thread; m->sleep = 0; // 为每个工作线程都创建一个monitor监视器 m->m = skynet_malloc(thread * sizeof(struct skynet_monitor *)); int i; for (i=0;i<thread;i++) { m->m[i] = skynet_monitor_new(); } // pthread_mutex_init函数的知识,详细见《Skynet专题之:线程》 if (pthread_mutex_init(&m->mutex, NULL)) { fprintf(stderr, "Init mutex error"); exit(1); } // pthread_cond_init函数的知识,详细见《Skynet专题之:线程》 if (pthread_cond_init(&m->cond, NULL)) { fprintf(stderr, "Init cond error"); exit(1); } // 直接先创建3个线程,给timer,socket,monitor create_thread(&pid[0], thread_monitor, m); // thread_monitor看代码段1 create_thread(&pid[1], thread_timer, m); // thread_timer看代码段2 create_thread(&pid[2], thread_socket, m); // thread_timer看代码段3 // skynet为了最大化消息队列的执行效率 // 即避免频繁地对全局队列加锁以及线程频繁切换服务 // 对worker线程获取每个服务的处理消息的数量做了优化 static int weight[] = { -1, -1, -1, -1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, }; struct worker_parm wp[thread]; // 循环建立worker线程 for (i=0;i<thread;i++) { wp[i].
2023-11-28 阅 读 全 文

Skynet源码之:服务实现(14)

服务实现的复杂性 服务实现是最为复杂的一个部分,其复杂不是在于本身的代码,而是服务几乎把所有的模块都联系到了一起 从消息队列message_queue,服务管理handle_storage,模块加载modules,监视器monitor,Skynet中API的实现 以及c-lua的调用,lualib的调用等,各种命令的传递,都深度关涉到服务实现 可以说服务实现是使用这些部分的一个综合协调体 ​ 服务会使用模块加载的创建,初始化,释放,通知 ​ 服务会使用服务管理的创建,释放,查找,命名 ​ 服务会使用消息队列的发消息,收消息,处理消息 ​ 服务还会被worker线程获取执行,被监视器monitor监控 ​ 服务更需要实现对Skynet中各类API接口的封装 ​ 同时服务也被网络通信绑定 ​ 也关系到lua协程、以及任务的执行 对此,我会尽最大的努力来描述,并尽可能地把上面的模块串联起来 ​ 第一个启动的服务 随着以下步骤的实现,我们迎来了第一个服务的启动 if (config->daemon) { // 守护进程 if (daemon_init(config->daemon)) { exit(1); } } skynet_harbor_init(config->harbor); // 节点建立 skynet_handle_init(config->harbor); // 服务管理 skynet_mq_init(); // 消息队列 skynet_module_init(config->module_path);// 模块加载 skynet_timer_init(); // 定时器 skynet_socket_init(); // 网络 skynet_profile_enable(config->profile); // 性能监控 ​ 第一个启动的就是logger服务 struct skynet_context *ctx = skynet_context_new(config->logservice, config->logger); if (ctx == NULL) { fprintf(stderr, "Can't launch %s service\n", config->logservice); exit(1); } ​
2023-11-13 阅 读 全 文

Skynet源码之:日志打印(13)

在几乎所有部分都准备好了之后(除了skynet_socket.c),只剩下一个部分还没有分析,就是日志打印部分 原先计划是写在《Skynet源码之:service_logger》的,但是后来发现写在一起太勉强了 并且Skynet有独立的skynet_log.c文件,另外skynet的debug_console控制台的也用到了日志打印部分 因此有了这篇独立的日志打印部分 ​ 日志打印从哪里开始 日志打印不像之前的部分,有个明显的入口函数,它更像是一堆函数,等待着别的部分来调用 因此我先从Skynet的一个配置字段 logpath = “.” 讲起 ​ 原文:cloudwu/skynet · Discussions · GitHub 问题:请问开启daemon后,日志写到哪里了 第一:配置为 “./log” ,你需要在 skynet 项目下先建立一个名为 log 的文件夹 第二:执行 nc 127.0.0.1 8000 进入 skynet 后台控制 debug console,执行list,显示所有服务的地址 第三:输入:logon 服务地址,例如:logon 8 第四:在 skynet/log/00000008.log 文件,就是这个服务的打印日志(main服务的地址是00000008) 第五:建议 example/main.lua 后面增加: local function my_timeout() print("timer=======", skynet.now()) skynet.timeout(200, my_timeout) end my_timeout() -- skynet.exit() -- main服务不要退出 最后执行 tail -f 00000008.log 就可以查看到 main 服务的日志输出 第六:输入:logoff 服务地址,例如:logoff 8,关闭日志输出 ​ logon命令 skynet在收到debug_console中logon命令的时候,会调用
2023-10-10 阅 读 全 文

Skynet源码之:性能监控(12)

让我们看看首先在哪里调用的 在skynet_start.c文件中,第277行 // 函数调用 skynet_profile_enable(config->profile); // 函数实现 void skynet_profile_enable(int enable) { G_NODE.profile = (bool)enable; } // 实质就是把全局环境中的profile置为true // G_NODE的相关信息查看《Skynet源码之:环境准备》 ​ 为何设置profile? 为什么我们要设置profile呢?原因就是为了性能监控 // 在skynet_server.c 文件的 273行中 if (ctx->profile) { ctx->cpu_start = skynet_thread_time(); reserve_msg = ctx->cb(ctx, ctx->cb_ud, type, msg->session, msg->source, msg->data, sz); uint64_t cost_time = skynet_thread_time() - ctx->cpu_start; ctx->cpu_cost += cost_time; } else { reserve_msg = ctx->cb(ctx, ctx->cb_ud, type, msg->session, msg->source, msg->data, sz); } // 对每个服务的消息处理时间进行统计 // 以及整个服务占用cpu的总时间 主要是为了监控服务的性能和状态 在发生问题时,能查看到那个服务发生了问题 可以通过控制台程序:debug_console,发出指令 time 或 cpu
2023-10-01 阅 读 全 文

Skynet源码之:网络模块(11)

socket网络模块是非常非常重要的部分,可以说是除服务实现外最主要的部分了 但在此之前,需要了解比较重要的网络知识,详细请看《Skynet专题之:网络》 因为Skynet框架的网络部分比较复杂,我暂时没有搞得非常透彻 只了解一些皮毛,文章还没写完,不敢乱放出来 后面在彻底理解了基础网络编程、skynet网络设计之后,再来完善这个部分 并且跟《Skynet源码之:service_gate一起更新》 ​ 参考: ​ 在找资料的时候,看到一个非常棒的关于skynet网络模块的分析:skynet网络机制 (manistein.club)
2023-09-26 阅 读 全 文