Ubuntu安装Skynet
- 系统环境:Ubuntu
- 框架地址:https://github.com/cloudwu/skynet
# 安装git
$ sudo apt-get update
$ sudo apt-get install git
# 安装支持库
$ sudo apt-get install build-essential libssl-dev libcurl4-gnutls-dev libexpat1-dev gettext unzip
# git克隆项目
$ git clone https://github.com/cloudwu/skynet.git
# 安装 autoconf
$ sudo apt-get install autoconf
# 安装 realine-devel
$ sudo apt-get install libreadline-dev
# 编译源码
$ cd skynet
$ sudo make linux
运行Skynet服务器
# 启动skynet服务节点
$ ./skynet example/config
运行Skynet客户端
# 终端下编译Lua虚拟机成功后生成lua可执行文件
$ cd ./3rd/lua/
$ make linux
# 返回skynet根目录下运行客户端脚本
$ cd ../../
$ ./3rd/lua/lua example/client.lua
CentOS配置Skynet
安装Lua5.3
- http://www.lua.org/ftp
$ lua -v
Lua 5.1.4 Copyright (C) 1994-2008 Lua.org, PUC-Rio
$ which lua luac
/usr/bin/lua
/usr/bin/luac
$ wget http://www.lua.org/ftp/lua-5.3.5.tar.gz
$ tar -zxvf lua-5.3.5.tar.gz
$ cd lua-5.3.5
$ make linux test
$ make install
$ yum install libtermcap-devel ncurses-devel libevent-devel readline-devel
$ make linux
$ make linux install
配置Lua环境变量
$ sudo rm -rf /usr/bin/lua*
$ sudo ln -s /home/jc/projects/lua/lua-5.3.5/src/luac /usr/bin/luac
$ sudo ln -s /home/jc/projects/lua/lua-5.3.5/src/lua /usr/bin/lua
$ lua -v
Lua 5.3.5 Copyright (C) 1994-2018 Lua.org, PUC-Rio
安装Skynet
$ sudo yum -y install curl curl-devel readline-devel ncurses-devel autoconf gcc-c++ git
$ git clone [https://github.com/cloudwu/skynet.git](https://github.com/cloudwu/skynet.git)
$ cd skynet
$ make install
$ ./skynet example/config
目录结构
Skynet目录结构
-
3rd
第三方代码,包括lua和jemalloc等。 -
lualib
使用Lua编写的库 -
lualib-src
使用C编写并封装给Lua使用的库 -
service
使用Lua编写的服务模块 -
service-src
使用C编写的服务模块 -
skynet-src
Skynet核心代码
Skynet启动
Skynet由一个或多个进程构成,每个进程又称为Skynet节点。Skynet节点的启动首先需要配置文件config
支持,分析实例example
文件夹下提供的config
配置文件会发现它是Lua代码,并以key-value
形式进行赋值。Skynet启动时会读取必要配置项,其他配置项以字符串的形式存入env
环境表中,所有配置项可通过skynet.getenv
的方式获取。
Skynet启动流程大体如下:
- 加载配置文件
- 配置文件存入Lua的全局变量
env
中 - 创建和启动C服务的日志
logger
- 启动引导模块并开启第一个Lua服务(bootstrap)
第一个启动的Lua服务其实都会由config
配置文件中的bootstrap
配置项所决定,实际项目会根据情况进行修改,保持使用bootstrap
作为第一个Lua服务,会直接或间接地去启动其他的Lua服务。
- 配置文件
$ vim example/config
include "config.path"
-- preload = "./examples/preload.lua" -- run preload.lua before every lua service run
thread = 8
logger = nil
logpath = "."
harbor = 1
address = "127.0.0.1:2526"
master = "127.0.0.1:2013"
start = "main" -- main script
bootstrap = "snlua bootstrap" -- The service for bootstrap
standalone = "0.0.0.0:2013"
-- snax_interface_g = "snax_g"
cpath = root.."cservice/?.so"
-- daemon = "./skynet.pid"
Skynet节点启动的必要配置项包括
-- 启动多少个工作线程,通常不要超过实际拥有的CPU核心数量。
thread = 8
-- 启动的第一服务及参数,默认为snlua bootstrap表示启动名为bootstrap的Lua服务,即service/bootstrap.lua。
bootstrap = "snlua bootstrap"
-- 使用C语言编写的服务模块的位置,通常是cservice文件下.so文件。若系统的动态库不是以.so作为后缀需修改,此路径可配置多项以;分号分隔。
cpath = root.."cservice/?.so"
- 配置信息结构体
skynet启动时必要的数据被定义在skynet-src/skynet_imp.h
文件中的skynet_config
结构体中。
$ vim syket-src/skynet_imp.h
#ifndef SKYNET_IMP_H
#define SKYNET_IMP_H
//skynet配置信息结构体
struct skynet_config {
//启动工作线程数量,建议不要设置超过实际拥有的CPU的核心数。
int thread;
//skynet网络节点的唯一编号,1到255之间的任意整数。一个skynet网络最多支持255个节点。
int harbor;
//是否开启统计功能,统计每个服务使用了多少CPU时间,默认为开启。
int profile;
//后台模式:daemon="./skynet.pid"以后台模式启动skynet,同时需配置logger日志项中输出的log。
const char * daemon;
//设置使用C语言编写的服务模块的位置,通常指的是cservice目录下的.so文件。
const char * module_path;
//设置skynet启动的第一个服务以及启动参数,默认配置为snlua bootstrap,即启动一个名为bootstrap的Lua服务,通常指的是service/bootstrap.lua文件。
const char * bootstrap;
//设置skynet内置skynet_error这个C API将日志信息输出文件的路径,如果配置为nil将输出到标准输出,可配置一个文件名将信息保存在特定的文件中。
const char * logger;
//自定义log服务,默认为logger,可参考service_logger.c来实现。若希望使用LUa来实现log服务则填写snlua,并在logger中配置具体的Lua服务的名字。在example配置中有config.userlog范例可供参考。
const char * logservice;
};
#define THREAD_WORKER 0
#define THREAD_MAIN 1
#define THREAD_SOCKET 2
#define THREAD_TIMER 3
#define THREAD_MONITOR 4
void skynet_start(struct skynet_config * config);
#endif
- 入口函数和初始化
启动Skynet所使用的指令./skynet example/config
实际上是调用 skynet-src/skynet_main.c
脚本的入口函数main
,调用时将config
配置文件地址作为参数传入函数中,在主函数中主要完成设置环境和加载配置文件两项工作。
$ vim skynet-src/skynet_main.c
//配置信息
#include skynet_imp.h
// 入口主函数
int
main(int argc, char *argv[]) {
//定义保存配置文件地址的变量
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;
}
//初始化:todo
luaS_initshr();
//全局初始化,为线程特有数据使用pthread_key_create()函数创建一个key,然后使用pthread_setspecific()函数为这个key设置value值。
skynet_globalinit();
//初始化Lua环境,创建一个全局数据结构struct skynet_env *E,并初始化结构的值。
skynet_env_init();
//设置信号处理函数,用于忽略sigpipe信号的处理
sigign();
//创建启动skynet所必须的配置信息结构数据
struct skynet_config config;
//申请一个Lua虚拟机
struct lua_State *L = luaL_newstate();
//连接到必要的Lua库到上面申请的Lua虚拟机中
luaL_openlibs(L); // link lua lib
//执行配置文件并在Lua中进行读取
int err = luaL_loadbufferx(L, load_config, strlen(load_config), "=[skynet config]", "t");
assert(err == LUA_OK);
//将C读取的配置文件内容串压入栈顶
lua_pushstring(L, config_file);
//执行栈顶的程序块(chunk),实际是加载配置Lua脚本字符串的内容
err = lua_pcall(L, 1, 1, 0);
if (err) {
fprintf(stderr,"%s\n",lua_tostring(L,-1));
lua_close(L);
return 1;
}
//初始化保存配置信息的环境变量env
_init_env(L);
//通过skynet_getenv()接口从env中获取配置文件的信息,其实内部机制是通过lua_setglobal将之前压入栈顶的配置文件config_file转换为Lua中的全局变量
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");
config.profile = optboolean("profile", 1);
//关闭上面创建的Lua虚拟机L
lua_close(L);
//开始执行skynet,正式启动skynet服务程序的操作
skynet_start(&config);
//删除线程存储的key,对应skynet_globalinit()
skynet_globalexit();
//初始化结束,对应luaS_initshr()
luaS_exitshr();
return 0;
}
- 启动skynet服务程序
在skynet-src/skynet_main.c
的main
函数末尾,也就是完成环境设置和配置加载后,会调用skynet_start(&config);
。skynet_start
函数和是在skynet-src/skynet_start.c
中定义的,作用是开始执行skynet。
$ vim skynet-src/skynet_start.c
void
skynet_start(struct skynet_config * config) {
// register SIGHUP for log file reopen
struct sigaction sa;
sa.sa_handler = &handle_hup;
sa.sa_flags = SA_RESTART;
sigfillset(&sa.sa_mask);
sigaction(SIGHUP, &sa, NULL);
//根据配置进行初始化
if (config->daemon) {
//初始化守护进程
if (daemon_init(config->daemon)) {
exit(1);
}
}
//初始化节点模块,用于集群并转发远程节点的消息
skynet_harbor_init(config->harbor);
//初始化句柄模块,用于给每个skynet服务创建一个全局唯一的句柄值。
skynet_handle_init(config->harbor);
//初始化消息队列模块(skynet的主要数据结构)
skynet_mq_init();
//初始化服务动态库加载模块,用于加载符合skynet服务模块接口的动态链结库(.so)。
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);
}
skynet_handle_namehandle(skynet_context_handle(ctx), "logger");
//加载bootstrap引导模块
bootstrap(ctx, config->bootstrap);
//启动Lua服务
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);
}
}
使用Docker安装配置Skynet
关于Docker的安装配置,这里不做过多说明,详细请查看相关文档。
操作步骤
- 使用SSH登录Docker虚拟机
- 在DockerHub中查找Skynet文件
- 使用Docker拉取Skynet镜像
- 运行Skynet镜像
$ docker-machine ls
$ docker-machine ssh default
docker@default $ docker pull skynet/skynet
docker@default $