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技术架构
随着Node.js的版本已经从0.8升级到12.11.1,其架构也在一直变化中
如果要看源代码,推荐看0.10版本,github中找到nodejs选择0.10版本即可
因为这一版本使用了很久一段时间,而且源代码比新版少很多
如果想要了解更多,可以查看 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++ 交互
插件通常会暴露可以从 Node.js 中运行的 JavaScript 访问的对象和函数。 当从 JavaScript 调用函数时,输入参数和返回值必须映射到 C/C++ 代码和从 C/C++ 代码映射。
在test.js就实现了通过JS引入addon这样的node.js文件,调用了addon.cc(C/C++)中的add方法
C++ 调用JS回调
插件中的常见做法是将 JavaScript 函数传给 C++ 函数并从那里执行它们
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的依赖
在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
顺序示意图
- 读官方文档是最好的方式
- 或者juejin.cn/post/684490…
重点阶段
- 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工作流程
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与学习思路
接下来我们只需要架构图中最上面的一块 Node.js API(官方提供的函数)
API文档
官方地址
- 英文文档 nodejs.org/api/
- 中文文档 nodejs.cn/api/
这些文档更像是字典,实在是太多了,没有欲望通过阅读文档去学习
民间版本(推荐)
- devdics.io: devdocs.io/
- 进入之后开启Node.js 10 LTS
- 搜索功能非常方便
- 可开启主题
- 可离线观看
API到底有哪些功能
重点关注黄色标注的API
- 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,学习任何技术最快的方式)
- 学习调试工具和思路