最新版本号[免费下载]

Node.js源码阅读笔记

作者:本站编辑 发布时间:2015-11-24 来源:本站原创 点击数:
  • C++和JS是如何交互的

  • 异步是如何实现的, event loop在其中充当什么角色

笔记

C++和Javascript交互

通过v8源码的示例process.cccount-hosts.js, 我们可以了解C++和Javascript代码是如何进行交互的.

通过在C++代码中使用v8引擎提供的接口, 可以在Javascript运行上下文中插入使用C++定义的变量(或函数); 同时, 也可以取出Javascript在此上下文中定义的变量(或函数等), 在C++代码中执行.

在Javascript代码中使用通过C++定义的函数

首先创建全局对象, 用于存放build-in函数log source

Handle<ObjectTemplate> global = ObjectTemplate::New(); global->Set(String::New("log"), FunctionTemplate::New(LogCallback));

在Javascript中, 就可以使用log函数输出日志 source

log("Processing " + request.host + request.path + " from " + request.referrer + "@" + request.userAgent);

在C++中获得使用Javascript定义的函数

在count-hosts.js中定义全局函数Process

function Process(request) { ... }

在process.cc中, 先取出该函数 source

Handle<String> process_name = String::New("Process"); Handle<Value> process_val = context->Global()->Get(process_name); Handle<Function> process_fun = Handle<Function>::Cast(process_val);

之后再调用它

const int argc = 1; Handle<Value> argv[argc] = { request_obj }; v8::Local<v8::Function> process = v8::Local<v8::Function>::New(GetIsolate(), process_); Handle<Value> result = process->Call(context->Global(), argc, argv);

Node.js初始化

Node.js的初始化调用链是这样的, main -> Start -> CreateEnvironment -> Load,

在Start过程中启用了event loop

int Start(int argc, char** argv) {     ...     Locker locker(node_isolate);     Environment* env =         CreateEnvironment(node_isolate, argc, argv, exec_argc, exec_argv);     // This Context::Scope is here so EnableDebug() can look up the current     // environment with Environment::GetCurrentChecked().     // TODO(bnoordhuis) Reorder the debugger initialization logic so it can     // be removed.     Context::Scope context_scope(env->context()); *    uv_run(env->event_loop(), UV_RUN_DEFAULT);     EmitExit(env);     RunAtExit(env);     env->Dispose();     env = NULL;     ... }

node:Load加载了node.jsnode.js负责初始化Node.js, 包括初始化全局变量和函数, 如setTimeout, nextTick等.

Node.js模块

Node.js中, 模块是通过require来加载的, 而其背后的实现在src/node.js中.

NativeModule.require首先检测模块是否在缓存中(已经被require的模块就会缓存), 如果没有则读取该模块文件内容, 并在当前上下文中执行.

读取模块文件内容使用NativeModule._sources,

NativeModule.getSource = function(id) {     return NativeModule._source[id]; }

NativeModule._sources是通过process.binding获取的

NativeModule._source = process.binding('natives');

和读取模块内容一样, 在当前上下文执行代码最终也是通过process.binding获取背后的黑盒来实现的.

var fn = runInThisContext(source, { filename: this.filename });
var ContextifyScript = process.binding('contextify').ContextifyScript; function runInThisContext(code, options) {     var script = new ContextifyScript(code, options);     return script.runInThisContext(); }

追查源码, 可以在node_contextify.cc看到contextify最终的C++实现.

这里可以看到, process.binding作为一个桥梁, 使得Node.js可以调用C++中实现的代码.

process.binding

重新review之前提到的Node.js初始化代码, 可以发现process.binding的实现.

node:CreateEnvironment过程中, 会初始化process对象

SetupProcessObject(env, argc, argv, exec_argc, exec_argv);

node:SetupProcessObject设置process.binding方法

Binding方法接受参数, 然后通过调用get_buildin_module返回使用C++编写的模块

node_module_struct* mod = get_builtin_module(*module_v);

get_builtin_module通过事先注册的模块列表node_module_list来加载模块, node_module_list是通过宏实现的.

在src/node_extensions.h中定义宏NODE_EXT_LIST, 其中包含了使用C++编写的模块

在src/node_extensions.cc中, 调用宏, 展开过程中使用到得诸如node_fs_module变量则是在每个C++模块底部定义的

NODE_MODULE_CONTEXT_AWARE(node_contextify, node::InitContextify);

这个宏展开后的结果是

extern "C" {     node::node_module_struct node_contextify_module = {         13 , __null , __FILE__ , __null , ( node::InitContextify ), "node_contextify"}     ; };

get_builtin_module中获取了C++模块后, 通过使用register_context_func模块自己制定的注册函数完成注册的步骤.

mod->register_context_func(exports, unused, env->context());

异步实现

追踪fs.readFile回调

为了追查异步调用的实现, 我们先从一个常用的异步方法fs.readFile开始,

fs.readFile使用fs.read来读取数据, 而fs.read最终调用了

binding.read(fd, buffer, offset, length, position, wrapper);

其中binding是这样定义的

var binding = process.binding('fs');

也就是说, node_file.cc为文件操作提供了最终实现.

fs.readnode_file.cc中实现为Read, 这个实现是对read(2)的一个包装.

Read中, 获取了异步调用的回调函数, 并将其传入ASYNC_CALL

cb = args[5]; if (cb->IsFunction()) {     ASYNC_CALL(read, cb, fd, buf, len, pos); } else {     SYNC_CALL(read, 0, fd, buf, len, pos)     args.GetReturnValue().Set(SYNC_RESULT); }

async进行宏展开,

Environment* env = Environment::GetCurrent(args.GetIsolate()); FSReqWrap* req_wrap = new FSReqWrap(env, "read" ); int err = uv_fs_read (env->event_loop(), &req_wrap->req_, fd , buf , len , pos , After); req_wrap->object()->Set(env->oncomplete_string(), cb ); req_wrap->Dispatched(); if (err < 0) {     uv_fs_t* req = &req_wrap->req_;     req->result = err;     req->path = __null ;     After(req); } args.GetReturnValue().Set(req_wrap->persistent());

在libuv中, uv_file_read的定义是这样的,

UV_EXTERN int uv_fs_read(uv_loop_t* loop, uv_fs_t* req, uv_file file,void* buf, size_t length, int64_t offset, uv_fs_cb cb);

它使用事件循环loop, 当文件读取操作完成后, 将会调用回调函数cb. 后面一节会描述libuv是如何实现完成事件调用函数的功能的.


本文责任编辑: 加入会员收藏夹 点此参与评论>>
复制本网址-发给QQ/微信上的朋友
AI智能听书
选取音色