c接口转Lua接口:
下面这个代码之后就可以直接通过client.socket进行导入,c接口转Lua接口:
LUAMOD_API int
luaopen_client_socket(lua_State *L) {
luaL_checkversion(L);
luaL_Reg l[] = {
{ "connect", lconnect },
{ "recv", lrecv },
{ "send", lsend },
{ "close", lclose },
{ "usleep", lusleep },
{ NULL, NULL },
};
luaL_newlib(L, l);
struct queue * q = lua_newuserdata(L, sizeof(*q));
memset(q, 0, sizeof(*q));
pthread_mutex_init(&q->lock, NULL);
lua_pushcclosure(L, lreadstdin, 1);
lua_setfield(L, -2, "readstdin");
pthread_t pid ;
pthread_create(&pid, NULL, readline_stdin, q);
return 1;
}
对应的,所有的c中的函数,例如lsend等,都需要把参数改为lua_State
加载skynet_module结构体(通过.so)
通过字符串获取skynet_module结构体:
struct skynet_module * skynet_module_query(const char * name)
struct skynet_module {
const char * name;
void * module;
skynet_dl_create create;
skynet_dl_init init;
skynet_dl_release release;
skynet_dl_signal signal;
};
以snlua为例,获得了module之后,会继续调用skynet_module_instance_create, skynet_module_instance_init函数,然后就会分别调用模块的create, init函数
int
snlua_init(struct snlua *l, struct skynet_context *ctx, const char * args) {
int sz = strlen(args);
char * tmp = skynet_malloc(sz);
memcpy(tmp, args, sz);
skynet_callback(ctx, l , launch_cb); // 注册消息处理函数
const char * self = skynet_command(ctx, "REG", NULL); // 得到自身的服务的地址,是一个带冒号的字符串
uint32_t handle_id = strtoul(self+1, NULL, 16); // 得到数字地址
// it must be first message
//直接发送消息,以便snlua能第一个加载 相应的服务
skynet_send(ctx, 0, handle_id, PTYPE_TAG_DONTCOPY,0, tmp, sz); // 发消息给自己以便加载相应的服务模块
return 0;
}
以bootstrap="snlua bootstrap"
为例,那么上面函数中的args将为”bootstrap”,将会作为msg通过skynet_send
传个自己,然后再调用对应的lauch_cb函数
static int
launch_cb(struct skynet_context * context, void *ud, int type, int session, uint32_t source , const void * msg, size_t sz) {
assert(type == 0 && session == 0);
struct snlua *l = ud;
skynet_callback(context, NULL, NULL); // 将消息处理函数置空,以免别的消息发过来
int err = init_cb(l, context, msg, sz); // 消息处理:通过 loader.lua 加载 lua 代码块
if (err) {
skynet_command(context, "EXIT", NULL);
}
return 0;
}
其中snlua结构体如下(似乎是代表着一个lua服务?):
struct snlua {
lua_State * L;
struct skynet_context * ctx;
size_t mem;
size_t mem_report;
size_t mem_limit;
};
Bootstrap
bootstrap 这个配置项关系着 skynet 运行的第二个服务。通常通过这个服务把整个系统启动起来。默认的 bootstrap 配置项为 “snlua bootstrap
” ,这意味着,skynet 会启动 snlua 这个服务,并将 bootstrap 作为参数传给它。snlua 是 lua 沙盒服务,bootstrap 会根据配置的 luaservice 匹配到最终的 lua 脚本。如果按默认配置,这个脚本应该是 service/bootstrap.lua 。
root = "./"
thread = 8 -- 启动多少个线程
logger = nil -- 决定内建的skynet_error会输出到什么文件,nil将输出到标准输出
harbor = 1 -- 1-255的任意数,每个节点必须有一个编号
address = "127.0.0.1:2526" -- 当前skynet节点的地址和端口
master = "127.0.0.1:2013" -- skynet控制中心的地址和端口.如果配置了standalone,则通常与standalone相同
start = "main" -- bootstrap最后一个环节将启动的lua服务,也就是你定制的skynet节点的主程序.默认为main,即启动main.lua脚本
bootstrap = "snlua bootstrap" -- skynet启动的第一个服务及其启动参数
standalone = "0.0.0.0:2013" -- 如果把这个 skynet 进程作为主进程启动,那么需要配置standalone 这一项,表示这个进程是主节点,它需要开启一个控制中心,监听一个端口,让其它节点接入。
luaservice = root.."service/?.lua;"..root.."test/?.lua;"..root.."examples/?.lua"
lualoader = "lualib/loader.lua"
snax = root.."examples/?.lua;"..root.."test/?.lua"
cpath = root.."cservice/?.so" -- 用 C 编写的服务模块的位置,通常指 cservice 下那些 .so 文件
main.lua
通过skynet.newservice启动新的lua服务:
skynet.newservice("debug_console",8000)
skynet.newservice("simpledb")
local watchdog = skynet.newservice("watchdog")
skynet.newservice函数调用skynet.call(),然后call再调用laucher.lua来启动lua服务.
function skynet.newservice(name, ...)
return skynet.call(".launcher", "lua" , "LAUNCH", "snlua", name, ...) -- launcher 就是 launcher.lua
end
skynet.cal会调用skynet.send可以向服务发送消息.skynet.send会调用c接口中的lsend(lua_State *L)
(在lua-skynet.c中),lsend又会调用skynet_send(context, 0, dest, type, session , msg, len)
,然后通过skynet_send调用skynet_context_push(destination, &smsg)
将消息压入目的地址服务的消息队列中供work线程取出.
-- 调用此接口发送消息(需要返回值)
function skynet.call(addr, typename, ...)
local p = proto[typename]
local session = c.send(addr, p.id , nil , p.pack(...)) -- 发送消息
-- 由于skynet.call是需要返回值的,所以c.send的第三个参数表示由框架自动分配一个session,以便返回时根据相应的session找到对应的协程进行处理
if session == nil then
error("call to invalid address " .. skynet.address(addr))
end
return p.unpack(yield_call(addr, session)) -- 阻塞等待返回值
end
.laucher
.launcher服务是在service/bootstrap.lua中被注册的:
local launcher = skynet.launch("snlua", "launcher")
skynet.name(".launcher", launcher)
它载入了Lua服务,并将注册后的服务的handle返回。launcher通过launch_service来启动服务,而launch_service又通过skynet.launch(在manager.lua中)来启动服务
local function launch_service(service, ...)
local param = table.concat({...}, " ")
local inst = skynet.launch(service, param) -- service 为要启动的服务名 param 为以空格分开的参数
local response = skynet.response()
if inst then
services[inst] = service .. " " .. param
instance[inst] = response
else
response(false)
return
end
return inst
end
local c = require "skynet.core"
function skynet.launch(...) --为要启动的服务名加上参数组成的字符串 参数以空格分隔
local addr = c.command("LAUNCH", table.concat({...}," "))
if addr then
return tonumber("0x" .. string.sub(addr , 2))
end
end
skynet.launch调用c.command()来启动服务,这个时候,就转到了c的接口了.调用lcommand(lua_State *L),然后再是skynet_command
(与skynet_send类似,定义在skynet.h中,由skynet_sever.c实现.
再之后,则会调用cmd_launch,通过skynet_context_new来创建新服务.
static const char *
cmd_launch(struct skynet_context * context, const char * param) {
size_t sz = strlen(param);
char tmp[sz+1];
strcpy(tmp,param);
char * args = tmp;
char * mod = strsep(&args, " \t\r\n");
args = strsep(&args, "\r\n");
struct skynet_context * inst = skynet_context_new(mod,args);
if (inst == NULL) {
return NULL;
} else {
id_to_hex(context->result, inst->handle);
return context->result;
}
}