skynet1.0阅读笔记2_skynet的消息投递skynet.call
为了了解 skynet.call 的调用过程,需要先看看 skynet的队列是如何把包分到不同工作线程的。看下图

查看 global_queue 的skynet_globalmq_push和skynet_globamq_pop,很容易可以找到两个关键的函数:
skyent_context_push
和
skynet_context_message_dispatch
先来看出口,skynet_context_message_dispatch。在skynet的启动函数中,我们已经知道skynet_start里面的start(config->thread)启动了 worker等线程:
thread_worker(void *p) {
//初始化
...
struct message_queue * q = NULL;
while (!m->quit) {
//循环调用 skynet_context_message_dispatch
q = skynet_context_message_dispatch(sm, q, weight);
if (q == NULL) {
//没包了就挂其线程
...
}
}
return NULL;
}
很清晰的代码,worker线程不断调用 skynet_context_message_dispatch 来读取q里面的skynet_message 队列,并进行分发。我们来看它是怎么分发的。
struct message_queue *
skynet_context_message_dispatch(struct skynet_monitor *sm, struct message_queue *q, int weight) {
//q为空时,重新从global_queue中取下一个 message_queue
if (q == NULL) {
q = skynet_globalmq_pop();
if (q==NULL)
return NULL;
} //当前 message_queue 所属服务的 context 的handle id
uint32_t handle = skynet_mq_handle(q); struct skynet_context * ctx = skynet_handle_grab(handle);
if (ctx == NULL) {
struct drop_t d = { handle };
skynet_mq_release(q, drop_message, &d);
return skynet_globalmq_pop();
} int i,n=;
struct skynet_message msg; for (i=;i<n;i++) {
//从message_queue 中 pop一个msg出来
if (skynet_mq_pop(q,&msg)) {
//若message_queue为空,返回1表示失败,释放ctx的引用次数
skynet_context_release(ctx);
//把返回global_queue里面的下一个message_queue,以供skynet_context_message_dispatch调用
return skynet_globalmq_pop();
} else if (i== && weight >= ) {
n = skynet_mq_length(q);
n >>= weight;
}
int overload = skynet_mq_overload(q);
if (overload) {
skynet_error(ctx, "May overload, message queue length = %d", overload);
} skynet_monitor_trigger(sm, msg.source , handle); //若 ctx->cb不为空,使用dispatch_message调用 ctx->cb
if (ctx->cb == NULL) {
skynet_free(msg.data);
} else {
dispatch_message(ctx, &msg);
} skynet_monitor_trigger(sm, ,);
} assert(q == ctx->queue);
//若global_queue中还有下一个message_queue,返回下一个message_queue供分发,若为空则继续执行当前message_queue的请求
struct message_queue *nq = skynet_globalmq_pop();
if (nq) {
// If global mq is not empty , push q back, and return next queue (nq)
// Else (global mq is empty or block, don't push q back, and return q again (for next dispatch)
skynet_globalmq_push(q);
q = nq;
}
skynet_context_release(ctx); return q;
}
那么,从全局队列最终拿到的 skynet_message包,最后交由了 dispatch_message和ctx-cb来处理了。dispatch_message把msg里面的东西取出来后,调用ctx->cb来进行处理
static void dispatch_message(struct skynet_context *ctx, struct skynet_message *msg) {
...
if (!ctx->cb(ctx, ctx->cb_ud, type, msg->session, msg->source, msg->data, sz)) {
skynet_free(msg->data);
}
CHECKCALLING_END(ctx)
}
在skynet的启动笔记中,已经知道了,首先是:
snlua_init 用 skynet_callback(ctx,l,_launch) 把 ctx->cb注册为 _launch
然后立马投递第一个消息
消息重新进到 dispatch_message,调用 _launch ,把 ctx->cb注册为 skynet.dispatch_message
启动完成后,以后的所有消息其实都进到了 skynet.dispatch_message,然后调用了 raw_dispatch_message
function skynet.dispatch_message(...)
local succ, err = pcall(raw_dispatch_message,...)
...
end
那接着来看 raw_dispatch_message
local function raw_dispatch_message(prototype, msg, sz, session, source, ...)
if prototype == then
...
else
local p = proto[prototype]
...
local f = p.dispatch if f then
...
local co = co_create(f)
...
suspend(co, coroutine.resume(co, session,source, p.unpack(msg,sz, ...)))
end
end
end
接下来要找的是 proto[prototype].disproto所有位置,找出那里定义 dispatch
的。看到 这个函数:
function skynet.register_protocol(class)
local name = class.name
local id = class.id
...
proto[name] = class
proto[id] = class
end
以及
function skynet.dispatch(typename, func)
local p = proto[typename]
if func then
local ret = p.dispatch
p.dispatch = func
return ret
else
return p and p.dispatch
end
end
没错了,就是class.dispatch 。所有的消息,最终进到的就是 class.dispatch。
///////////////////////////////////////////////////////////////////////
skynet.regiser_protocol 和 skynet.dispatch 你会在lua服务中经常看见。以launcher.lua为例子
launcher.lua在启动时,注册一个 name为"text"的table,它的dispatch也定义在下面
所以你应该能看到 skynet.call(".launcher","text",...)这种调用
skynet.register_protocol {
name = "text",
id = skynet.PTYPE_TEXT,
unpack = skynet.tostring,
dispatch = function(session, address , cmd)
if cmd == "" then
command.LAUNCHOK(address)
elseif cmd == "ERROR" then
command.ERROR(address)
else
error ("Invalid text command " .. cmd)
end
end,
}
//定义 launcher服务的 proto["lua"] 的dispatch
skynet.dispatch("lua", function(session, address, cmd , ...)
cmd = string.upper(cmd)
local f = command[cmd]
if f then
local ret = f(address, ...)
if ret ~= NORET then
skynet.ret(skynet.pack(ret))
end
else
skynet.ret(skynet.pack {"Unknown command"} )
end
end)
但这里还有一个问题,上面的proto["lua"] 是谁注册的呢? 查找skynet.register_protocol,我们能找到这个位置:
--skynet.lua
----- register protocol
do
local REG = skynet.register_protocol REG {
name = "lua",
id = skynet.PTYPE_LUA,
pack = skynet.pack,
unpack = skynet.unpack,
} REG {
name = "response",
id = skynet.PTYPE_RESPONSE,
} REG {
name = "error",
id = skynet.PTYPE_ERROR,
unpack = function(...) return ... end,
dispatch = _error_dispatch,
}
end
在你第一次require "skynet"
的时候,它已经默认帮你注册了"lua","response","error"3种消息,然后你创建新的lua服务时,调用skynet.dispatch 为 proto["lua"] 指定dispatch,之后通过 skynet.call("服务名","lua",...) 调用的消息就能最终投递到你定义的处理函数里面了。
到了这里,从队列取出数据,并分发到指定处理函数dispath的完整流程我们以及看到了。接下来,我们来看 消息是如果放入global_queue的。
来看 skynet.call 函数(skynet.send其实也一样的,只是它不管返回)
function skynet.call(addr, typename, ...)
//如proto["lua"] ,消息类型id放入msg中
local p = proto[typename]
local session = c.send(addr, p.id , nil , p.pack(...))
...
//等待返回
return p.unpack(yield_call(addr, session))
end
这里的 c.send 的调用,我们看一下 c 的定义:
local c = require "skynet.core"
这里的 skynet.core ,实际上调用的是 skynet.so ,而从 skynet 的make log我们可以看到这样一行:
cc -g -O2 -Wall -I3rd/lua -fPIC --shared lualib-src/lua-skynet.c lualib-src/lua-seri.c -o luaclib/skynet.so -Iskynet-src -Iservice-src -Ilualib-src
在 lualib-src/lua-skynet.c 中,我们看到这段代码:
luaL_Reg l[] = {
{ "send" , _send },
{ "genid", _genid },
{ "redirect", _redirect },
{ "command" , _command },
{ "intcommand", _intcommand },
{ "error", _error },
{ "tostring", _tostring },
{ "harbor", _harbor },
{ "pack", _luaseri_pack },
{ "unpack", _luaseri_unpack },
{ "packstring", lpackstring },
{ "trash" , ltrash },
{ "callback", _callback },
{ NULL, NULL },
};
这里的 luaL_Reg 把c函数注册到lua中,从而让lua调用这些函数。
所以 c.send 调用的,就是这里的 _send
_send 调用了 skynet_send ,如果目标在当前进程,将调用 skynet_context_push
然后 skyent_context_push 调用
skynet_mq_push(ctx->queue, message);
把消息放如了全局队列,最后来看看 skynet_mq_push :
void
skynet_mq_push(struct message_queue *q, struct skynet_message *message) {
assert(message);
SPIN_LOCK(q) //把msg放到队列尾,然后 ++ q->taiskynet_globalmq_pushl
q->queue[q->tail] = *message;
if (++ q->tail >= q->cap) {
q->tail = ;
} if (q->head == q->tail) {
expand_queue(q);
} //若ctx->queue未放入global_queue,放进去
if (q->in_global == ) {
q->in_global = MQ_IN_GLOBAL;
skynet_globalmq_push(q);
} SPIN_UNLOCK(q)
}
值得一提的是,取消息从 ctx->queue 的head开始取,push消息则是从 tail push。
所以先投递的消息会先执行,但由于协程的原因,还是不能保证先投递的消息先执行完。
skynet1.0阅读笔记2_skynet的消息投递skynet.call的更多相关文章
- skynet1.0阅读笔记_skynet的启动
首先看skynet的启动,函数入口在 skynet_main.c 的main(),其中最重要的是: skynet_start(&config); 在skynet_start中做了两个启动: / ...
- The Implementation of Lua 5.0 阅读笔记(一)
没想到Lua的作者理论水平这么高,这篇文章读的我顿生高屋建瓴之感.云风分享了一篇中译:http://www.codingnow.com/2000/download/The%20Implementati ...
- Effective objective-c 2.0阅读笔记
这本书非常的好,看完后,感触挺深,总结纪录一下,针对ios开发的备忘: 注:分类和原著有些不同,自己总结学习用的,仅供参考. 系统篇: 了解oc起源:继承c,由Smalltalk演化而来.动态语言 ...
- The implementation of Lua 5.0 阅读笔记(二)
6 线程和协程 读完这篇文章我才意识到python的协程到底缺了什么,这个就是coroutine和semi-coroutine的区别了.区别就是,semi-coroutine只能返回(yield)到调 ...
- Kafka 权威指南阅读笔记(第三章,第四章)
Kafka 第三章,第四章阅读笔记 Kafka 发送消息有三种方式:不关心结果的,同步方式,异步方式. Kafka 的异常主要有两类:一种是可重试异常,一种是无需重试异常. 生产者的配置: acks ...
- 《C# 6.0 本质论》 阅读笔记
<C# 6.0 本质论> 阅读笔记 阅读笔记不是讲述这本书的内容,只是提取了其中一部分我认为比较重要或者还没有掌握的知识,所以如果有错误或者模糊之处,请指正,谢谢! 对于C# 6.0才 ...
- Linux 0.11源码阅读笔记-文件管理
Linux 0.11源码阅读笔记-文件管理 文件系统 生磁盘 未安装文件系统的磁盘称之为生磁盘,生磁盘也可以作为文件读写,linux中一切皆文件. 磁盘分区 生磁盘可以被分区,分区中可以安装文件系统, ...
- Linux 0.11源码阅读笔记-中断过程
Linux 0.11源码阅读笔记-中断过程 是什么中断 中断发生时,计算机会停止当前运行的程序,转而执行中断处理程序,然后再返回原被中断的程序继续运行.中断包括硬件中断和软件中断,硬中断是由外设自动产 ...
- Linux 0.11源码阅读笔记-总览
Linux 0.11源码阅读笔记-总览 阅读源码的目的 加深对Linux操作系统的了解,了解Linux操作系统基本架构,熟悉进程管理.内存管理等主要模块知识. 通过阅读教复杂的代码,锻炼自己复杂项目代 ...
随机推荐
- C# 小叙 Encoding (一)
前言 众所周知计算机只能识别二进制数字,如1010,1001.我们屏幕所看到的文字,字符都是和二进制转换后的结果.将我们的文字按照某种规则转换二进制存储在计算机上,这一个过程叫字符编码,反之就是解码. ...
- 【Linux】目录权限与文件权限
现在我们知道了Linux系统内文件的三种身份(拥有者.群组与其他人),知道每种身份都有三种权限(rwx),已知道能够使用chown, chgrp, chmod去修改这些权限与属性,当然,利用ls -l ...
- Linux内核(9) - 精华版 之 方法论
到目前为之,博客上分享的精华篇都可以归为方法论的范畴,在很多时候,都是方法论要比细节紧要得多.而这些精华篇又可细分为三个专题:Linux大史记:内核学习的方法论:驱动开发的方法论. Linux大史记 ...
- NYOJ239 月老的难题 【二分图最大匹配·匈牙利】
月老的难题 时间限制:1000 ms | 内存限制:65535 KB 难度:4 描写叙述 月老准备给n个女孩与n个男孩牵红线.成就一对对美好的姻缘. 如今,因为一些原因,部分男孩与女孩可能结成幸福 ...
- linux用户管理之创建用户和删除用户
一.常用命令: (1)创建用户命令两条: adduser useradd (2)用户删除命令: userdel 二.两个用户创建命令之间的区别 adduser: 会自动为创建的用户指定主目录.系统sh ...
- 前端 JS,localStorage/sessionStorage、cookie 及 url 等实现前台数据共享、传输
需求是这样的:需要统计用户公司某款产品用户的回馈情况,美工给的设计多个psd,每个页面里面都有一个选择题,让用户选择自己的答案,最后经过几次选择之后在最后一个页面统一提交到后台!所以这里引出的技术需求 ...
- compiled inline cache
http://cr.openjdk.java.net/~jrose/pres/200910-VMIL.pdf https://wiki.openjdk.java.net/display/HotSpot ...
- oracle triggers 实现两个结构相同的表的数据级联更新操作
首先创建两个结构相同的表 -- Create table create table TABLE_TEMP ( userid NUMBER not null, username NVARCHAR2(50 ...
- linux学习(34):except的安装
expect据说是交互性很强的脚本语言,但是expect依赖于tcl,但linux系统里一般不自带安装tcl,需要手动安装 expect版本 5.43 http://download.chinauni ...
- shell实现除法,保留小数点后N位
$more get_wstts_success.sh #!/bin/bash open_gw_1=$( "sh /home/admin/bin/check_wstts_success.sh& ...