Node.js源码初探~我很好奇

摘要:
所以我们经常称之为过程。绑定、处理。cpu Usage、process Dlopen和其他方法实际上正在调用打包为js脚本的C++方法;下一个SetupProcessObject方法是初始化进程对象的方法。除了只读属性进程之外。版本、过程ModuleLoadList等;更重要的是,通过Environment::SetMethod方法,C++方法被打包为js脚本方法;例如:env-˃SetMethod;只需将进程绑定方法绑定到C++中的binding方法;初始化进程对象后,将调用LoadEnvironment方法。在这个方法中,我们可以看到:Local<String>script_ name=FIXED_ONE_BYTE_ String;本地f_Value=执行字符串;ExecuteString方法调用v8::Script::Compile方法来编译传入的js文件;然后我们知道bootstrap_Node。js是一个名为js的文件。里面发生了什么?

前言:

最近在看Node.js,看了一段时间后便想着看看Node.js源码,自己本地调试调试;现在便说说这个过程中的坑,以及一些需要注意的地方;

      Node.js需要一定C++基础,建议看完C++Primer再看,否则V8的好多表达方式,指针,引用,模板之类的会看不懂;

      代码已上传GitHub地址:   https://github.com/sven36/cNode

      完整编译的文件太大,上传GitHub不成功,下载请用百度网盘https://pan.baidu.com/s/1jIC4xCy

先说一下node.js启动过程:

       node.js的src目录下的源代码大部分都是node.js的模块文件;其实初始化node.js用到的文件只有:node.h , node.cc , env.h , env_inl.h , node_internals.h , node_javascript.h , node_javascript.cc , util.h , util.cc ,以及用js2c.py工具将内置JavaScript代码转成C++里面的数组,生成的node_natives.h文件;

我实现的过程是按照node.js的启动过程,需要哪个方法就实现哪个方法,能合并的方法都尽量合并,能忽略的细节都尽量忽略,下面简单说说node.js启动主要的方法和过程;

入口是在node_main.cc中,根据平台的不同会进入不同的Start方法,我的是windows平台,运行的wmain方法,然后调用了node::Start方法;

node::Start方法的具体实现是在node.cc中,node.cc也是node的核心代码;在启动过程中需要注意的有四个方法:StartNodeInstance,在StartNodeInstance里面调用的CreateEnvironment方法,

在CreateEnvironment方法里面调用的SetupProcessObject方法,以及CreateEnvironment结束之后调用的LoadEnvironment方法;

StartNodeInstance在初始化v8虚拟机,绑定作用域之后就会调用CreateEnvironment方法;CreateEnvironment会初始化Environment类,该方法定义在env_inl.h文件中;

CreateEnvironment在初始化Environment类之后,会先初始化v8的的CPU分析器,再初始化handle的回收方法,然后就会初始化全局process对象;

代码如下:

Local<FunctionTemplate> process_template = FunctionTemplate::New(isolate);
process_template->SetClassName(node::OneByteString(isolate, "process", sizeof("process") - 1));

Local<Object> process_object = process_template->GetFunction()->NewInstance(context).ToLocalChecked();
env->set_process_object(process_object);

在v8里面一个template是javascript函数的蓝图。你可以使用一个template来将c++函数和结构体包装到javascript对象中,让javascirpt脚本来使用它。所以我们经常调用的process.binding,process.cpuUsage,process.dlopen等方法,其实是在调用包装成js脚本的C++方法;

接下来的SetupProcessObject方法就是具体初始化process对象的方法了,除了只读属性process.versions,process.moduleLoadList等;更重要的是通过Environment::SetMethod方法,把C++方法包装成js脚本方法;比如:

  env->SetMethod(process, "binding", Binding);

就把process.binding方法绑定成C++里面的Binding方法;

初始化process对象之后就会调用LoadEnvironment方法,在该方法中我们可以看的:

Local<String> script_name = FIXED_ONE_BYTE_STRING(env->isolate(),
"bootstrap_node.js");
Local<Value> f_value = ExecuteString(env, MainSource(env), script_name);

其中ExecuteString方法会调用v8::Script::Compile方法来编译传入的js文件;那我们知道了bootstrap_node.js是一个被调用的js文件,在这个里面又发生了什么呢?

大概可以分为:初始化全局 process 对象上的部分属性 / 行为,初始化全局的一些 timer 方法,初始化全局 console 对象等一些方法;这里我们不展开了,我们看一看node的js模块是如何引入的;


function NativeModule(id) {
this.filename = `${id}.js`;
this.id = id;
this.exports = {};
this.loaded = false;
this.loading = false;
}

NativeModule._source = process.binding('natives');
NativeModule._cache = {};

我们看的原生模块会调用process.binding('natives')方法,我们找到node.cc里面的Binding方法看看会进行哪些操作;

else if (!strcmp(*module_v, "natives")) {
exports = Object::New(env->isolate());
DefineJavaScript(env, exports);
cache->Set(module, exports);

我们看到当传入的参数是natives的时候会调用DefineJavaScript方法,我们看看这个方法做了什么;

void DefineJavaScript(Environment* env, Local<Object> target) {
auto context = env->context();
#define V(id)
do {
auto key =
String::NewFromOneByte(
env->isolate(), id##_name, NewStringType::kNormal,
sizeof(id##_name)).ToLocalChecked();
auto value =
String::NewExternalOneByte(
env->isolate(), &id##_external_data).ToLocalChecked();
CHECK(target->Set(context, key, value).FromJust());
} while (0);
NODE_NATIVES_MAP(V)
#undef V
}

是一个复杂的宏定义,看着不太好理解那我们自己抽离一个实现看看;

void DefineJavaScript(Environment* env, Local<Object> target){
auto context = env->context();
do {
auto key = String::NewFromOneByte(env->isolate(), buffer_name, NewStringType::kNormal, sizeof(buffer_name)).ToLocalChecked();
auto value = String::NewExternalOneByte(env->isolate(), &buffer_external_data).ToLocalChecked();
} while (0);

}

buffer_name是什么呢?在js2c.py把内置js文件转成C++数组的node_natives.h文件里面我们可以找到:

static const uint8_t buffer_name[] = {
98,117,102,102,101,114};

所以process.binding('natives')其实是使用v8引擎,编译我们内置的js文件;

到这里node.js的启动过程和文件模块机制基本上就说了个大概了,其它诸如非核心模块的引入,和buffer,stream等应C++完成核心部分,其它部分用js包装或导出的模块就需要大家自己去了解了;

 

我在过程中碰到一些问题和需要注意的地方:


说明:https://github.com/sven36/cNode这个项目是不能编译的,因为这个是我最开始的版本(不过代码我都同步成最新的了),v8等编译命令都没设置,我本来想上传一个新的GitHub的可是项目太大了,老上传失败;
      所以我就上传了个百度网盘https://pan.baidu.com/s/1jIC4xCy
      这个是可以直接编译的,有需要的可以去下载;

 

编译:我用的win10的环境,具体编译请参考Node.js的编译说明:https://github.com/nodejs/node/blob/master/BUILDING.md

        其中的坑:Python应该是2.6或2.7,不要装3.0或以上的,因为node.js有的py文件3.0编译出错Visual C++ Build Tools必须是2015,安装官方的链接就是,因为Node.js在Windows平台的编译用的是vs2015的v140平台工具集,用低版本的或者vs2017的v141平台工具集都会报错;

 

        编译流程:安装完python和Visual C++ Build Tools2015之后,下载node.js的源码  node-v6.10.0.tar.gz  然后用Visual C++ Build Tools2015的命令行运行解压目录下的vcbuild.bat处理文件就会开始编译了;编译成功后解压目录下就会出现.sln文件就可以使用vs打开了(vs也需要是2015或2017,并且项目的平台工具集也需要是v140否则编译报错);如图:

 Node.js源码初探~我很好奇第1张

 

V8引擎:

          因为node.js其实就是嵌入V8的一个C++程序;首先要对v8的Isolate,LocalHandle,Scope等概念有一个了解,此处不展开了,请参考这个文档:

           https://github.com/Chunlin-Li/Chunlin-Li.github.io/blob/master/blogs/javascript/V8_Embedder's_Guide_CHS.md

          在我的代码里面我也加了一些注释,在src目录下的node.cpp文件内,可以参考;

 

C++编译与平常的面向对象编译方式的不同

          在.NET或Java之类的语言中,可以不必关注方法的声明顺序,比如:

          private void a(){

               b();

         }

         private void b(){

               console.log(1);

         }

         不过在C++中这样是不行的,调用b之前,必须完全声明b;也就是把b放在方法a之前(这也是node.cpp文件中为什么把开始方法Start放在最下端);

     

为什么Node.js的头文件要用namespace node包起来:

                 是为了更好的解耦node.js的各个模块;在C++中命名空间相同而内部成员名字不同,它们会自动合并为同一个命名空间,可以理解为追加;

 

Node.js里面比较复杂的宏定义:

    Node.js和V8用了很多复杂的宏定义,如果不理解它们看起来会很费力;在C++中,宏定义里面的##符号是连接字符串的意思;

    比如:env-inl.h文件下的宏定义:

#define VP(PropertyName, StringValue) V(v8::Private, PropertyName, StringValue)
#define VS(PropertyName, StringValue) V(v8::String, PropertyName, StringValue)
#define V(TypeName, PropertyName, StringValue)                               
  inline                                                                     
  v8::Local<TypeName> Environment::IsolateData::PropertyName() const {       
    /* Strings are immutable so casting away const-ness here is okay. */     
    return const_cast<IsolateData*>(this)->PropertyName ## _.Get(isolate()); 
  }
    PER_ISOLATE_PRIVATE_SYMBOL_PROPERTIES(VP)
        PER_ISOLATE_STRING_PROPERTIES(VS)
#undef V
#undef VS
#undef VP

它最后的编译形式是:

    inline v8::Local<v8::String> Environment::IsolateData::async_queue_string() const {
        return const_cast<IsolateData*>(this)->async_queue_string_.Get(isolate());
    }

这种地方多一些耐心仔细分析一下就会懂了;我写的代码里面基本上这种宏定义第一个字符串我都是这种手写的,其余的是按照原先的宏定义的方式声明,可以参考;

 

使用VS编译需要注意的地方

      node.js项目文件其实是用google的GYP工具生产的,所以VS项目编译的过程也在各个项目下的.gyp文件内。

      了解gyp工具请参考:http://www.cnblogs.com/nanvann/p/3913880.html#conditions

 

当然还有很多具体的C++问题就需要靠自己多思考,勤搜索了;

 最后附上编译成功的图片:

Node.js源码初探~我很好奇第2张

 

 

        

 

 

 

 

免责声明:文章转载自《Node.js源码初探~我很好奇》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇强制将IE8设置为IE7兼容模式来解析网页(转)dapper 多对多查询对象和对象列表下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

asp.net core系列 31 EF管理数据库架构--必备知识 反向工程

一.   反向工程   反向工程是基于数据库架构,生成的实体类和DbContext类代码的过程,对于Visual Studio开发,建议使用PMC。对于其他开发环境,请选择.NET Core CLI工具(跨平台)。     (1) 在程序包管理器控制台(PMC)工具中使用命令Scaffold-DbContext 来进行反向工程。     (2) 在.NET...

在Java代码中使用iTextPDF生成PDF

1. 生成PDF 载入字体 static { FontFactory.register("/fonts/msyh.ttf"); FontFactory.register("/fonts/msyhbd.ttf"); FontFactory.register("/fonts/simsun.ttc")...

微服务-使用Redis实现分布式缓存

在单体中对于key信息和用户信息是放在内存中放的,通过session进行管理。 微服务是要放在分布式缓存中,以实现服务的无状态化。 @Autowired privateStringRedisTemplate redisTemplate; @Value("${file.prefix}") privateString imgPrefix;...

谁动了我的主机? 之活用History命令

点击上方“开源Linux”,选择“设为星标” 回复“学习”获取独家整理的学习资料! Linux系统下可通过history命令查看用户所有的历史操作记录,在安全应急响应中起着非常重要的作用,但在未进行附加配置情况下,history命令只能查看用户历史操作记录,并不能区分用户以及操作时间,不便于审计分析。 当然,一些不好的操作习惯也可能通过命令历史泄露敏感...

HBase BulkLoad批量写入数据实战

1.概述 在进行数据传输中,批量加载数据到HBase集群有多种方式,比如通过HBase API进行批量写入数据、使用Sqoop工具批量导数到HBase集群、使用MapReduce批量导入等。这些方式,在导入数据的过程中,如果数据量过大,可能耗时会比较严重或者占用HBase集群资源较多(如磁盘IO、HBase Handler数等)。今天这篇博客笔者将为大家分...

二、实践与视频解决方案

一、视频解决方案 说明: 公司下户拍摄视频,上传存储一直用的优酷云(视频压缩、解码、播放)当然还支持水印。 现在场景,我们公司内部买服务器,下户拍摄视频上传到我们自己服务内,需要解决的问题,下户拍摄视频很大,需要解决的问题: 1、(下户视频过大)需要压缩处理、 2、(视频格式、播放帧处理)解码格式 3、(提供url)提供接口让内部人员可以播放  解决方案1...