先来运行一个最简单的例子,需要一些准备工作:
由于我是在window下做的测试,使用的VS的工程,该工程做了windows平台的适配,对skynet源码没有改动,工程目录如下:
skynet自带有example和test,为了简单起见,我新建了一个sfl目录,做自己的测试;首先关注main.lua和config两个文件即可;
在sfl/目录下新建一个脚本,叫做service1.lua作为第一个服务;
--config
root = "./"
thread = 8 --工作线程数量
logger = nil
logpath = "."
harbor = 1 --集群时表示当前集群的id
address = "127.0.0.1:2526" -- slave listen master/slave模式使用,后面再说
master = "127.0.0.1:2013" -- slave connect to
start = "main" -- main script
bootstrap = "snlua bootstrap" -- 启动的第一个服务以及其启动参数 service/bootstrap.lua
standalone = "0.0.0.0:2013" -- master listen
luaservice = root.."service/?.lua;"..root.."sfl/?.lua"
lualoader = root .. "lualib/loader.lua"
lua_path = root.."lualib/?.lua;"..root.."lualib/?/init.lua;"..root.."sfl/?.lua"
lua_cpath = root .. "luaclib/?.so"
-- preload = "./example1/preload.lua" -- run preload.lua before every lua service run
snax = root.."sfl/?.lua;"..root.."test/?.lua"
-- snax_interface_g = "snax_g"
cpath = root.."cservice/?.so"
-- daemon = "./skynet.pid"
--main.lua
local skynet = require "skynet"
local function start_service1()
print("ready to start service1")
skynet.newservice("service1")
skynet.exit()
end
skynet.start(start_service1)
--service1.lua
local skynet = require "skynet"
skynet.start(function()
print("service 1 start--------")
end)
config:目前只需要关注"start"字段,这个字段用来指定lua层的入口(其实lua层的真正的入口是 bootstrap="snlua bootstrap",但是这个过程是skynet封装好的,使用框架的时候,一般需要要知道有这个)
main.lua:config里的"start"字段指定的启动脚本,skynet.newservice 方法启动一个新的服务,需要注意的是新的服务有独立的Lua堆栈,main.lua启动完服务后,没有其他任务,需要调用skynet.exit退出当前服务,防止内存泄漏;
service1.lua:新服务示例
执行命令 “skynet.exe sfl/config” 即可启动服务,可以看到以下输出:
下面分析以下具体的启动流程。
回顾一下上面的项目目录,梳理一下
从上面目录结构图来看,越是往下的层次,应用领域越广泛。越是往上的层级,针对性越强,应用领域越小,和业务越相关。
skynet的底层是C语言实现的,所有我们需要找到C的入口函数main()。
skynet-src/skynet_main.c
//skynet_main.c
int
main(int argc, char *argv[]) {
_set_error_mode(_OUT_TO_MSGBOX);
//assert(0);
const char * config_file = NULL ;
if (argc > 1) {
config_file = argv[1];
} else {
//fprintf(stderr, "Need a config file. Please read skynet wiki : https://github.com/cloudwu/skynet/wiki/Config\n"
// "usage: skynet configfilename\n");
//return 1;
//方便调试,写死一个默认配置地址
config_file = "E:\\test\\skynet-vs2013\\sfl\\config";
}
#ifdef _MSC_VER
cpoll_startup();
#endif
luaS_initshr();//初始化string库,这部分可以不看,新版的skynet已经移除了
skynet_globalinit();//初始化线程标记,先给主线程打上标记
skynet_env_init();//创建一个Lua虚拟机,用来存储全局的环境变量
/*
在linux下写socket的程序的时候,如果尝试send到一个disconnected socket上,就会让底层抛出一个SIGPIPE信号。
这个信号的缺省处理方法是退出进程,大多数时候这都不是我们期望的。因此我们需要重载这个信号的处理方法。
*/
//设置信号处理函数,用于忽略SIGPIPE信号的处理
sigign();
//下面是读取配置文件,lua_State* L只用来读配置,
struct skynet_config config;
struct lua_State *L = lua_newstate(skynet_lalloc, NULL);
luaL_openlibs(L); // link lua lib
int err = luaL_loadstring(L, load_config);
assert(err == LUA_OK);
lua_pushstring(L, config_file);
//加载配置文件
err = lua_pcall(L, 1, 1, 0);
if (err) {
fprintf(stderr,"%s\n",lua_tostring(L,-1));
lua_close(L);
return 1;
}
//把配置文件按照key-value读出来,存到全局的环境变量里去
_init_env(L);
config.thread = optint("thread",8);
config.module_path = optstring("cpath","./cservice/?.so");
config.harbor = optint("harbor", 1);
config.bootstrap = optstring("bootstrap","snlua bootstrap");
config.daemon = optstring("daemon", NULL);
config.logger = optstring("logger", NULL);
config.logservice = optstring("logservice", "logger");
//配置加载完了,释放掉
lua_close(L);
//启动--这一步会阻塞住,一直到程序退出
skynet_start(&config);
skynet_globalexit();
luaS_exitshr();
#ifdef _MSC_VER
cpoll_cleanup();
#endif
return 0;
}
接下来看skynet_start()这个方法,找到skynet-src/skynet_start.c:
//skynet-src/skynet_start.c
void
skynet_start(struct skynet_config * config) {
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();
//网络模块,主要是初始化epoll(linux下)模型,创建socket线程和其它线程通信的接口
skynet_socket_init();
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);
}
//加载引导程序
bootstrap(ctx, config->bootstrap);
//启动工作线程,定时器,socket等线程
start(config->thread);
// harbor_exit may call socket send, so it should exit before socket_free
skynet_harbor_exit();
skynet_socket_free();
if (config->daemon) {
daemon_exit(config->daemon);
}
}
参考https://manistein.github.io/blog/post/server/skynet/skynet%E6%BA%90%E7%A0%81%E8%B5%8F%E6%9E%90/
优点:
缺点: