技术标签: nodejs
本文是根据之前在公司内部做的分享整理而成。是早期对nodejs的一个认识。源码版本10.x。
Nodejs本质上是对js功能的拓展。提供了网络、文件、dns解析、进程线程等功能。
1.1 Nodejs是如何拓展js功能的?
利用v8提供的接口。
1.2 如何在v8新建一个自定义的功能?
// c++里定义
Handle<FunctionTemplate> Test = FunctionTemplate::New(cb);
global->Set(String::New(“Test"), Test);
// js里使用
var test = new Test();
1.3 nodejs是如何实现拓展的
但nodejs不是给每个功能拓展一个对象,而是拓展一个process对象,再通过process.binding拓展js功能。Nodejs定义了一个js对象process,映射到一个c++对象process,底层维护了一个c++模块的链表,js通过调用js层的process.binding,访问到c++的process对象,从而访问c++模块(类似访问js的Object、Date等)。
2.1 Libuv是什么?为什么nodejs需要他?
libuv是一个跨平台异步IO库。因为Nodejs是单线程的,作为服务器,他涉及到IO,而IO是会阻塞的,从而影响性能。所以Nodejs把IO操作交给libuv,保证主线程可以继续处理其他事情。Libuv做了什么?Libuv主要是,利用系统提供的事件驱动模块解决网络异步IO,利用线程池解决文件IO。另外还实现了定时器,对进程,线程等使用进行了封装。
1 新建一个uv_loop_t* loop。loop中保存了各个阶段对应的数据结构。
2 执行uv_run函数进入“死”循环。
3 用户(nodejs)操作loop里的结构,注册事件和回调。
4 libuv在每一轮循环里处理各个阶段。
2.2 libuv的各个阶段(phase)
1 定时器(setTimeout)
2 pending callback
3 idle(自定义)
4 prepare(自定义)
5 poll i/o(网络和文件IO)
6 check(setImmediate)
7 close callback(关闭一个handle)
2.3 libuv的实现
最小堆(定时器)
链表(check、idle等)
线程池(文件io)
操作系统提供的事件驱动模块(网络io)
1 注册内置c++模块(通过process.binding函数使用内置c++模块)。
2 新建process的c++对象,设置一系列属性(binding),然后挂载到全局。
3 执行bootstrap_node.js,初始化和挂载nextTick,setTimeout等函数,然后加载用户js,编译执行。
4 调用libuv开始事件循环。
3.1 注册内置c++模块
1 每个c++模块由一个node_module结构体管理。
2 用链表的方式把各个模块的node_module连接起来。
3 运行时,js通过process.binding函数从链表中找到对应的模块,从而使用c++模块功能。
3.2 process对象的生成和作用
1 新建一个c++的process对象
// 利用v8新建一个函数
auto process_template = FunctionTemplate::New(isolate());
// 设置函数名
process_template->SetClassName(FIXED_ONE_BYTE_STRING(isolate(), "process"));
// 利用函数new一个对象 auto process_object = process_template->GetFunction()->NewInstance(context()).ToLocalChecked();
// 设置env的一个属性,类型是Object,val是process_object
set_process_object(process_object);
2 nodejs如何访问global?
通过给global增加一个global属性
Local<Object> global = env->context()->Global();
global->Set("global", global);
3 nodejs如果设置process对象?
编译node_bootstrap.js成c++代码,执行时传入c++的process对象,执行global.process = process; 从js层面来看,是多了一个全局变量process
4 process的Binding函数
process.Binding加载c++模块。实现js使用c++模块功能。const { TCP } = process.binding(‘tcp_wrap’); const tcp = new TCP();tcp.listen();js里通过process.binding加载一个c++模块的时候,这段js在编译后执行,首先访问js层的process对象,v8知道js的process对象对应是c++的process对象,再通过底层的Binding,就可以使用c++模块的功能了。
3.3 执行bootstrap_node.js
1 挂载全局变量setTimeout,Buffer等,给process对象挂载nexTick函数等初始化工作。
2 执行用户js
3.4 调用libuv开始事件循环。
如何生成任务给事件循环系统消费?
1 setTimeout
2 setImmediate
3 文件io
4 网络io
4.1 setTimeout的实现
1 用户调用setTimeout,设置时间是s秒
2 找到s对应的链表L。
3 如果L不存在则新建一个,并在libuv最小堆里新增一个超时节点。
4 往链表L头部插入一个Timeout节点。返回。(最早超时在链表末尾)
5 uv_run执行uv__run_timers判断是否有超时节点。
6 从后往前遍历链表L,如果当前节点没有超时则全部没有超时,设置新的超时时间,否则执行超时回调。
4.2 setImmediate实现
1 nodejs启动的时候注册了check阶段的一个c++层回调是CheckImmediate,该函数再执行js回调processImmediate
2 用户调用setImmediate,生成一个节点插到双向链表。返回。
3 uv_run在check阶段。执行回调。setImmediate和setTimeout的关系这两个其实没什么关系,对应的阶段也不一样。
4.3 文件io
为啥用线程池实现文件操作的异步?
因为文件的异步操作在各操作系统中兼容性不好。libuv线程池默认打开4个,最多打开128个线程。所有线程共享一个任务队列,当有任务的时候,添加到任务队列,线程的工作函数在死循环里不断处理队列里的任务。Libuv初始化的时候,注册了一个异步的io观察者A,用于子线程和主线程间通信的。io观察者A设置了一个管道文件描述符和回调。子线程完成任务后设置该任务的标记位,然后通过管道通知主线程,主线程在uv_run的poll io阶段会执行观察者A的回调,观察者的回调会判断每个异步任务的状态。然后执行用户的回调。
文件操作的过程
1 打开一个文件,新建一个c++ FSReqWrap对象。设置用户回调。调用FSReqWrap对象的Open,接着调用libuv层uv_fs_open。uv_fs_open。Libuv生成一个任务放到线程池的任务队列,返回nodejs。Nodejs可以继续做其他事情。
2 线程池处理该任务,线程会阻塞直到任务完成。比如读写文件,dns查询,然后设置任务的完成标记,可以通过管道写端通知主线程。主线程执行c++层回调,再执行js层回调。
4.4 网络io
网络io的实现方案。利用操作系统提供的事件驱动模块。
var http = require('http');
http.createServer(function (request, response) { response.end('Hello World\n'); }).listen(9297);
原文链接: —Android IPC之Binder机制分析—更多精彩请点击: AIDL实现IPC详解——AIDL实现IPC详解一、 Android IPC方式选择1.1 IPC释意IPC:Inter-Process Communication,进程间的通信或跨进程通信。因为不同的进程是不能共享内存块的,所以进程之间的通信需要使用特别的方式。进程中的线程是可以共享内存的,就是多个线程可以操作或者可以
注:wm_concat(str1) 11g 后不支持使用LISTAGG函数用法select LISTAGG(name, ',') WITHIN GROUP (ORDER BY id) from tableName记录每天发现的问题,同行如有宝贵意见请不吝赐教。...
所有的php程序员都知道在php脚本里面执行 echo “1”;访客的浏览器里面就会显示“1”。但是我们执行下面的代码的时候,并不是显示“1”之后5秒再显示“2”,而是等待5秒后直接显示“12”echo '1';sleep(5);echo '2';这就涉及到几个缓存机制,为了更高的薪水,同学们非常有必要把这个缓存机制学习好。通常情况下,我们的web应用由以下几个
在linux下,用minicom作为终端连接ARM开发板。由于电脑没有串口,需要用usb转串口 首先接上usb转串口,在终端输入:lsusb查看信息:[email protected]:/dev$ lsusb Bus...
这是我学习linux的过程,每天都会更新所学习的知识总结,每个例子都是我自己的亲手实践的,作为新人的我希望各位大佬提出宝贵的意见!!工作管理如何产生一个程序呢?就是执行一个程式或指令,系统只认识二进制文件,当我们启动一个二进制文件的时候会生成程序,而二进制文件里的东西就是程式,程式被某个条件触发生成程序。程式一般是放置在实体磁碟中,然后透过使用者的执行来触发。触发后会载入到记忆体中成为一个个体,那就是程序。程序有给予执行者的权限/属性等参数,并包括程式所需要的指令码与资料或档案资料等, 最后再给予
云原生时代,Ingress 作为Kubernetes集群中服务的入口,已经被大家所接受。除了Kubernetes默认的NGINX Ingress Controller(基于原生 NGIN...
Go语言的内置函数 copy() 可以将一个数组切片复制到另一个数组切片中,如果加入的两个数组切片不一样大,就会按照其中较小的那个数组切片的元素个数进行复制。copy() 函数的使用格式如下:copy( destSlice, srcSlice []T) int其中 srcSlice 为数据来源切片,destSlice 为复制的目标(也就是将 srcSlice 复制到 destSlice),目标切片必须分配过空间且足够承载复制的元素个数,并且来源和目标的类型必须一致,copy() 函数的返回值表示实
我们在开发 Java Web 服务时,如果使用外部 Tomcat Web容器,那么启停服务可以直接使用 Tomcat 自带的脚本。不过现在大多数服务使用 Spring Boot 框架来开发,使用内嵌的 Tomcat / Jetty Web 容器,所以这时我们一般使用特定的命令或者编写脚本来启停 Web 服务。
Android ListView数据刷新http://yajin167.info/2011/06/21/572.html
方法1:eclipse->preferences->Maven->Archetypes->Add Remote Catalog在Catalog File中填写:http://repo1.maven.org/maven2/archetype-catalog.xml但是这个方法会导致archetype列表加载非常非常的慢,会一直显示Retrieving archetypes,所以我
环境操作系统:macOS High Sierra Version10.13.4Homebrew:1.6.4步骤安装Homebrew,详细步骤参见Homebrew官网。 brew doctor确认brew在正常工作。 brew update更新包。 brew install mysql安装mysql。其中运行mysql_secure_installation 可以设置...
1、前端实现pdf文件在线预览功能方式一、pdf文件理论上可以在浏览器直接打开预览但是需要打开新页面。在仅仅是预览pdf文件且UI要求不高的情况下可以直接通过a标签href属性实现预览方式二、通过jquery插件jquery.media.js实现 这个插件可以实现pdf预览功能(包括其他各种媒体文件)但是对word等类型的文件无能为力。 实现方式: js代码:html结构:调用方式:$('#han...