Skynet学习笔记

 

一 HelloWorld

先来运行一个最简单的例子,需要一些准备工作:

  • 编译skynet框架

由于我是在window下做的测试,使用的VS的工程,该工程做了windows平台的适配,对skynet源码没有改动,工程目录如下:

Skynet学习笔记_第1张图片Skynet学习笔记_第2张图片Skynet学习笔记_第3张图片

  • 准备一份配置文件

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学习笔记_第4张图片

二 启动流程分析

下面分析以下具体的启动流程。

回顾一下上面的项目目录,梳理一下

Skynet学习笔记_第5张图片

从上面目录结构图来看,越是往下的层次,应用领域越广泛。越是往上的层级,针对性越强,应用领域越小,和业务越相关。

  • 3rd目录:提供lua语言支持、 jemalloc(内存管理模块)、md5加密等,这些模块在开发领域有着广泛的应用。
  • skynet-src目录:包含skynet最核心机制的模块,包括逻辑入口、加载C服务代码的skynet_module模块、运行和管理服务实例的skynet_context模块、skynet消息队列、定时器和socket模块等。
  • service-src目录:这是依附于skynet核心模块的c服务,如用于日志输出的logger服务,用于运行lua脚本snlua的c服务等。
  • lualib-src目录:提供C层级的api调用,如调用socket模块的api,调用skynet消息发送,注册回调函数的api,甚至是对C服务的调用等,并导出lua接口,供lua层使用。可以视为lua调C的媒介
  • service目录:lua层服务,依附于snlua这个c服务,这个目录包含skynet lua层级的一些基本服务,比如启动lua层级服务的bootstrap服务,gate服务,供lua层创建新服务的launcher服务等。
  • lualib目录:包含调用lua服务的辅助函数,方便应用层调用skynet的一些基本服务;包含对一些c模块或lua模块调用的辅助函数,总之,这些lualib方便应用层调用skynet提供的基本服务,和其他库。

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/

四 优缺点

优点:

  • 项目不大,可扩展性强;
  • 可以作为学习研究的对象,有些设计上的思想可以借鉴;
  • 充分利用多核新能;单进程也可以实现多服务;
  • 服务相互隔离,一个服务异常不会影响其他服务;
  • Actor模型的使用(个人逻辑,本质就是用消息传递取代了方法的直接调用),可以解耦业务逻辑,写代码思路跟清晰;避免数据冲突,提高并发;
  • 跨服务调用skynet.call和调用本地方法一样方便;
  • 集群模式水平扩容;

缺点:

  • 作为某一类型的服务器框架(游戏或者微服务的框架)来说,缺少必要的基础功能,这也和skynet的定位有关;
  • 资料不足,使用的人不多,经验不足的团队不建议使用,不然处理问题可能没人解决;
  • 请求挂起时,当前服务处理其他请求回应消息顺序错乱,数据同步问题;
  • 内存隐患,不停创建协程会导致内存达到峰值,但是不释放;
  • 如果一个服务发起大量消息,会占用消息循环,导致发到其他服务的消息一直无法处理;

你可能感兴趣的:(网络编程)