Node.js技术架构

JS如何调用C++

Node.js不是什么

不是web框架

  • Node.js并不是web后端框架
  • 所以不能把Node.js与Flask或Spring比较

不是编程语言

  • Node.js并不是后端的JS
  • 所以不能把Node.js与Python或PHP对比

Node.js是什么

是一个平台

  • 它将多种技术组合起来
  • 让JavaScript也能调用系统接口、开发后端应用

Node.js组合了很多技术

  • V8引擎
  • libuv
  • C/C++实现的c-ares(域名解析)、http-parser(解析http)、OpenSSL(做https)、zlib(做加密)等库

Node.js技术架构

image.png

随着Node.js的版本已经从0.8升级到12.11.1,其架构也在一直变化中

如果要看源代码,推荐看0.10版本,github中找到nodejs选择0.10版本即可

github.com/nodejs/node…

因为这一版本使用了很久一段时间,而且源代码比新版少很多

如果想要了解更多,可以查看 github.com/yjhjstz/dee…

我们大部分时候,只需要学习Node.js标准库即可

对于其他模块,有一个大概的了解就行

等对Node.js内部实现感兴趣的时候,再去了解标准库之外的东西

不过你可能要写好 C / C++

什么是bindings

背景

  • C/C++实现了一个http_parser库,很高效
  • 我们只会写JS,但是又想调用这个库
  • 直接调用肯定是不能成功的,我们需要一个中间的桥梁

bindings

  • Node.js用C++对http_parser进行封装,使它符合某些要求(如:它的数据类型和JS数据类型做了一一对应的封装),封装的文件叫做http_parser_bindings.cpp(C++的代码)
  • 用Node.js提供的编译工具将其编译为.node文件
  • JS代码可以直接require这个.node文件
  • 这样JS就能调用C++库,中间的桥梁就是binding(http_parser_bindings.cpp)
  • 由于Node.js提供了很多binding,所以加个s
  • 这就是bindings

JS 与 C++ 交互

查看: nodejs.cn/api/addons.…

插件通常会暴露可以从 Node.js 中运行的 JavaScript 访问的对象和函数。 当从 JavaScript 调用函数时,输入参数和返回值必须映射到 C/C++ 代码和从 C/C++ 代码映射。

image.png

在test.js就实现了通过JS引入addon这样的node.js文件,调用了addon.cc(C/C++)中的add方法

C++ 调用JS回调

查看: nodejs.cn/api/addons.…

插件中的常见做法是将 JavaScript 函数传给 C++ 函数并从那里执行它们

image.png

args[0]就是JS中的那个函数,但是args[0]不能直接给C++,C++是看不懂JS函数的,Node.js提供了Cast的工具,它可以把JS的函数转化为C++能看懂的函数cb。JS中的函数addon就会被C++调用,调用的时候传了参数hello world(C++中构造了JS能看懂的string) ,于是就输出了hello world

只要能让JS调用C++的代码,JS的能力就会被无限扩大

Node.js提供的binding我们可以使用,如果现在有一个新的C++库,Node.js没有把它绑定进来怎么办?

Node.js还提供另外一个功能: 允许你自己写C++,把C++库绑定到JS上,JS就可以调用C++库了,这就是Node.js的另外一个能力,C/C++插件(自定义其他能力)

Node.js的依赖

image.png

在Node.js V0.10 版本中有7个依赖,我们主要需要掌握两个依赖v8和uv(也就是libuv),其他的优先级往后放

  • cares:DNS
  • http_parser: http
  • npm: 包管理器
  • openssl: https
  • zlib: 压缩

libuv & v8 的功能

libuv是什么

背景

  • FreeBSD 系统上有kqueue
  • Linux系统上有epoll
  • Windows 系统上有IOCP
  • Ryan为了实现一个跨平台的异步I/O(所有的输入和输出,如:写文件,访问网络,连接打印机打印文件即系统和外界进行的所有交互)库,开始写libuv
  • libuv会根据系统自动选择合适的方案(kqueue/epoll/IOCP)

功能

  • 可以用于TCP(http是基于TCP/IP,只要能操作TCP就可以做http服务器)/UDP(QQ聊天)/DNS(baidu.com对应的IP)/文件(文件的读取)等的异步操作
  • 有了这些功能Node.js就可以摆脱IO瓶颈了,涉及到IO的操作交给C语言去做,JS只负责简单的调用

v8是什么

功能

  • 将JS源代码变成本地代码(01)并执行
  • 维护调用栈,确保JS函数的执行顺序
  • 内存管理,为所有对象分配内存
  • 垃圾回收,重复利用无用的内存(垃圾回收的目的是为了再次重复利用,因为内存是有限的,比如:你用完了2k的内存,用完了就得还给我,还要给下一程序使用呢!)
  • 实现JS的标准库(如:数组的sort、数组的splice等函数)
  • v8本身是多线程的

注意

  • V8不提供DOM API(如:在V8中不能操作douemnt.createElement()...,它是浏览器提供的)
  • V8执行JS是单线程的(V8本身是多线程的)
  • 可以开启两个线程分别执行JS(并不是真正意义上的多线程,这两个线程之间毫无瓜葛)
  • V8本身是包含多个线程的,如:垃圾回收为单独线程
  • 自带 eventloop 但 Node.js并没有使用自带的eventloop而是基于libuv自己做了一个eventloop

eventloop

Event Loop 是什么

什么是Event

  • 计时器到了就会产生一个事件,这个事件产生就会执行回调(内部的)
  • 文件可以读取了、读取出错了的时候是操作系统要单独生成一个事件告诉JS。如:拷贝文件到U盘上,如果发现此时已经有另外一个文件也在拷贝,那么此时第二个拷贝文件就会很慢,因为硬盘的读写速度是有极限的(外部的,文件再在硬盘上,而硬盘和操作系统是分开的)
  • socket有内容了、关闭了。如:用户请求了我们的服务器,socket有内容了操作系统得告诉JS要开始读用户的内容。socket关闭了,也得告诉JS(外部的,socket一般为另外一台机器传过来的)

什么是Loop

  • Loop 就是循环,比如: while(true) 循环
  • 由于事件是分优先级的,所以处理起来也是分先后的

举例:

三种不同的事件

setTimeout(f1,100)        // 计时器到时了
fs.readFile('/1.txt',f2)  // 文件可以读了
server.on('close',f3)     // 服务器关闭了

如果同时触发,Node会怎么办?执行f1?f2?f3

- 肯定会有某种顺序(优先级)
- 这种顺序应该是人为规定的(如果有读文件就先读文件 => 如果有请求就先处理请求=>如果有计时器就执行计时器 ====> 进入循环Loop => 如果有读文件就先读文件 => 如果有请求就先处理请求 => .......)
  • 所以Node.js需要按照顺序轮询每种事件
  • 这种轮询往往都是循环的,1->2->3->1->2->3

EventLoop

  • 操作系统可以触发事件,JS可以处理事件
  • Event Loop 就是对事件处理顺序的管理

Event Loop

顺序示意图

image.png

重点阶段

  • timers 检查计时器
  • poll 轮询阶段,处理大部分请求(如:读文件,http请求),检查系统事件
  • check 检查阶段,处理 setImmediate 回调
  • close callbacks 看下有没有sockert关闭的回调
  • 再回到timers 检查计时器。。。 如此循环下去
  • 如果Node.js发现没有什么事做就会停在poll(轮询)阶段,如果发现有事情做,如执行setImmediate就会进入下个阶段check(while循环,一直问操作系统有没有文件可以读、有没有网络请求可以处理、。。。。)。poll阶段是Node.js停留时间最长的、优先级最高的
  • 其他阶段用的较少

大部分时候timers中的定时器是后执行的(虽然它在最前面),因为定时器至于在大于或等于时间的时候才会被执行,如果时间没到就会执行timers下面的阶段,等其他阶段如poll阶段或者check阶段执行完了,计时器的时间到了或者超了再执行timers

一个面试题

请问下面的代码哪个先执行?

setTimeout(f1,0)
setImmediate(f2)

===》 不确定

(1) 大部分情况下是setImmediate先执行,因为大部分时候,Node.js都是停留在poll 阶段,这个时候如果要执行JS就会先经过check阶段,这个阶段就是处理setImmediate

(2) 有一个情况是例外的: Node.js第一次进来时会先看下有没有timers

注意

  • 大部分时间,Node.js都停留在poll轮询阶段(如果这个阶段比较耗时,那么timers就会自动往后推)
  • 大部分事件都在poll阶段被处理,如文件、网络请求

总结

  • 用libuv进行异步I/O操作(如:让libuv读文件,文件读完后Node.js接替后面的事情(Node.js可以处理文件了!!!)
  • 用event loop 管理不同事件处理顺序(先polls(操作系统) -> check -> close -> timers -> polls(操作系统) ... 如此循环) (Node.js可以管理事件的顺序了!!!)
  • 用C/C++ 库高效处理DNS/HTTP...(Node.js又可以处理网络了!!!)(因为Node.js是借助C++处理网络请求,因此处理网络请求速度很快)
  • 用bindings 让JS能和C/C++沟通(JS也可以调用C++的代码了!!!)(把C++代码编译成.node文件给JS去require,require的过程中JS传个函数给C++,C++调用JS)
  • 用V8运行用户写的JS(写好了JS代码在哪里运行JS代码呢?)
  • 用Node.js标准库进化JS代码(都让用户写JS,用户就不爽了,帮用户写好了一些东西,用户直接用)
  • 这就是Node.js

Node.js工作流程

image.png

  • Application就是我们写的JS
  • 我们写JS放到V8上运行
  • 运行的过程中发现JS中写了一个定时器
  • Node.js调用Node.js的bindings / API ,把定时器放到 Event Loop 中,告诉Event Loop 100毫秒之后要执行一个函数
  • Event Loop 中有很多不同的队列(如:timers、poll、check等),Event Loop 会等待恰当的时机去执行队列中的代码
  • 如果此时在poll阶段需要读一个文件,这时Event Loop 就会利用libuv开一个线程去读这个文件(JS不参与),读完文件之后,操作系统就会返回一个事件给Event Loop,Event Loop 发现文件读好了,就传回给V8,然后就传回到我们写代码的地方
  • 处理网络请求也一样libuv得到了用户传过来的网络请求,就会把它放到 Event Loop 中的poll阶段,poll阶段得到这个请求之后,就会调用JS代码把它在v8中执行,将结果返回到写diamagnetic的地方

整个过程我们发现:V8 和 libuv是最重要的,而Node.js中的bindings / API 就是V8 和 libuv 中间的桥梁,我们写的代码只是整个平台中很小的一部分,这就是Node.js的工作流程

我们写的代码 ==> V8上运行 ==> V8通过Node.js的bindings/API ==> 使用libuv提供的功能 / 其他的C++提供的功能完成用户所需要的功能

因此说Node.js不是一门语言,也不是一个框架,它就是一个平台!!!

Node.js API与学习思路

image.png

接下来我们只需要架构图中最上面的一块 Node.js API(官方提供的函数)

API文档

官方地址

这些文档更像是字典,实在是太多了,没有欲望通过阅读文档去学习

民间版本(推荐)

  • devdics.io: devdocs.io/
  • 进入之后开启Node.js 10 LTS
  • 搜索功能非常方便
  • 可开启主题
  • 可离线观看

image.png

API到底有哪些功能

重点关注黄色标注的API

image.png

  • Buffer: 一小段缓存(大文件一点一点上传)
  • Child Processes: 子进程(Node.js的分身)
  • Cluster: 集群 (把多个Node.js集合到一起,每个Node.js做不同的事情,它们之间的关系就是主要和次要的关系)
  • Debugger: 调试
  • Events: 对应的就是EventHub,发布订阅模式(一个对象提供了on、off、emit)
  • File System: 文件系统,操作文件/目录
  • Globals: 全局变量,如:__dirname:当前文件所在目录;__filename: 当前文件的文件名
  • Http
  • Path: 路径
  • Query Strings: 处理URL
  • Stream: 流格式的数据的处理
  • Timers: settimeout 、setInterval、setImmediate
  • URL
  • Worker Threads*

学习路线

基础 - Web - 框架

  • 先学基础,以任务为导向学习
  • 逐个学习文件、HTTP、Stream等模块
  • 在学Web、数据库、AJAX相关知识
  • 最后学习框架,以项目为导向
  • 以Express为切入点,制作完整网站

约定

  • 记笔记,写博客
  • CRM学习法贯穿整个学习过程(copy => run => modify,学习任何技术最快的方式)
  • 学习调试工具和思路

猜你喜欢

转载自juejin.im/post/7246583632482713660