您的位置:首页 > Web前端 > Node.js

Nodejs源码的阅读-事件循环的过程

2014-02-06 23:05 585 查看
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相对应,因为读文件完成了,不需要它来保证循环队列非空了,第三行就是调用回调函数。

到这里所有代码都执行完成了。然后由于事件循环已经空了,所以循环也退出了,于是整个程序都退出了。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: