Nodejs源码的阅读-事件循环的过程
解读基于node V0.2.0
之前我们对所有的watcher都介绍到了,后面就以具体的事件循环的例子来看,中途可能会遇到上面讲的watcher。
我以一个读文件的例子来说:
var fs = require('fs');
fs.readFile('./main.js','utf8',function(err,text){console.log(text);});
我们来看看readfile是怎么被加入到事件队列,以及readfile的回调函数是怎么被执行到的。
首先var fs = require('fs');这里的fs模块的源码就在fs.js。
在fs.js中我们可以看到:
var binding = process.binding('fs');
......
fs.readFile = function (path, encoding_, callback) {
binding.stat...
binding.open...
Binding.read...
}
其中process.binding是之前在main函数中绑定的函数,
NODE_SET_METHOD(process, "binding", binding);
它指向binding函数,这个函数是这么来获取模块的,
else if ((modp = get_builtin_module(*module_v)) != NULL) {
exports = Object::New();
modp->register_func(exports);
binding_cache->Set(module, exports);
}
因为fs是一个内置的模块,所以get_builtin_module就能够获取到值,它获取到的内容是node_file.cc的内容,所以process.binding('fs')将返回node_file.cc的实例。
因此fs.readFile实际调用的是node_file.cc中的stat,open,read函数,关键当然是read函数。
static Handle<Value> Read(const Arguments& args) {
......
//read函数的最后:
if (cb->IsFunction()) {
ASYNC_CALL(read, cb, fd, buf, len, pos);
} else {
// SYNC
//这里是同步调用的时候
}
}
这里是异步调用,所以走的是ASYNC_CALL(read, cb, fd, buf, len, pos);这个调用。
#define ASYNC_CALL(func, callback, ...) \
eio_req *req = eio_##func(__VA_ARGS__, EIO_PRI_DEFAULT, After, \
cb_persist(callback)); \
assert(req); \
ev_ref(EV_DEFAULT_UC); \
return Undefined();
把参数代入宏,之后:
eio_req *req = eio_read(fd, buf, len, pos, EIO_PRI_DEFAULT, After, cb_persist(cb));
assert(req);
ev_ref(EV_DEFAULT_UC);
return Undefined();
在eio_xx系列的函数中,都会组建一个eio_req结构体,
req->finish = cb; // cb这里是After
req->data = data; // data这里是 cb_persist(cb)
然后把这个结构体推入eio的请求队列。
接着ev_ref(EV_DEFAULT_UC);给我们的事件循环增加计数,目的是防止循环因为没有watcher而退出。这里其实并没有watcher加入循环队列,但是增加计数起到同样的作用。
这时用户的js执行完毕,继而执行process.loop();上一篇关于循环建立中我们知道事件循环这时开始启动。由于刚才引用计数增加了,所以循环一直不退出。
然后某一时刻,推入eio队列的请求完成,即文件读取完毕。
这时eio的完成队列从无变成有,由eio_init(node::EIOWantPoll, node::EIODonePoll);可知,node::EIOWantPoll此时被调用。
node::EIOWantPoll只有一句话ev_async_send(EV_DEFAULT_UC_ &eio_want_poll_notifier);作用是唤醒eio_want_poll_notifier这个watcher。
由ev_async_init(&node::eio_want_poll_notifier, node::WantPollNotifier);可知,node::WantPollNotifier将被调用,其核心代码是:
if (eio_poll() == -1) {
//一次没取完
ev_idle_start(EV_DEFAULT_UC_ &eio_poller);
}
取出每一个eio完成队列中的元素并处理,如果一次eio_poll没处理完,就启动eio_poller这个watcher,这个watcher在前面一篇讲过就是用于一次没处理完所有完成队列的元素,于是开启等下次启动以便再次进行处理。
在处理eio的完成队列的元素时,最终调用:
# define EIO_FINISH(req) ((req)->finish) && !EIO_CANCELLED (req) ? (req)->finish (req) : 0
也就是调用了(req)->finish (req),这个finish我们回头看下:
在eio_xx系列的函数中,都会组建一个eio_req结构体,
req->finish = cb; // cb这里是After
req->data = data; // data这里是 cb_persist(cb)
所以(req)->finish (req)将调用After这个函数。
Persistent<Function> *callback = cb_unwrap(req->data);
ev_unref(EV_DEFAULT_UC);
(*callback)->Call(v8::Context::GetCurrent()->Global(), argc, argv);
上面是After的几个关键代码,第一行是取出之前设置的回调函数,也就是我们js代码给readFile设置的回调函数,第二行是事件循环的计数减1,与之前计数加1相对应,因为读文件完成了,不需要它来保证循环队列非空了,第三行就是调用回调函数。
到这里所有代码都执行完成了。然后由于事件循环已经空了,所以循环也退出了,于是整个程序都退出了。