NodeJS核心编码实战·进阶

背景

最近读完了《Nodejs 硬实战 115 个实战技巧》这本书,书分成了三部分来讲:node 核心模块、web 开发、编写 npm 包。

其中,第一部分收获很大,一些编码的实操加深了对二进制、流、进程、系统、性能等各个方面的理解。第二部分的知识有些过时,主要是围绕着 express 来展开的,更推荐去看 nestjs。

本文围绕文件系统、tcp/ip、性能测试、多进程开发、二进制编码等几个专题,整理了我在动手敲写书上代码的过程中所学到的相关知识。内容以 QA 的形式呈现,不展示相关代码(太乱了),但会给出代码的位置。

🔍 关注公众号“心谭博客” / 👉 查看原文: xxoo521.com / 更多前端与算法的系列文章

二进制编码

Q:将图片转换为 DataURI 字符串格式

A:使用 Buffer 对二进制进行 base64 编码。

代码地址

Q:解析二进制地理数据 .dbf 后缀文件

A:根据二进制协议内容,使用 Buffer 读取文件信息。

代码地址

Q:自定义基于二进制的数据传输协议

A:客户端按照协议生成二进制数据,在完成压缩后传给服务端。服务端读取二进制数据,检查索引,对数据进行解压,并存入“模拟数据库”中。

代码地址

文件系统

Q:使用异步接口,实现递归遍历查找指定文件

A:相较于同步接口,异步不阻塞主线程,速度更快。

异步的处理技巧是使用一个变量 asyncOps,标记当前正在进行的异步操作数量。每次异步开始前,+1;结束后,-1。每次-1 后,检查 asyncOps 是否为 0,如果为 0,那么所有异步都完成,触发回调函数。

为了防止回调函数重复触发,使用一个变量进行标识。

代码地址

Q:实现文件锁模块

A:文件锁用于多进程同时操作某个资源(例如写入一个文件),防止交叉使用造成不可预测后果。使用文件锁,使得在同一时间,资源只能被一个进程使用。

它本身是一种“约定”,有两种实现方法:以 O_EXCL 打开文件、使用 mkdir。

代码地址

Q:设计实现一个文件数据库

A:一个设计了 5 个方法,load(加载数据库文件)、close(关闭数据库)、get/del/set(操作数据库数据)。数据库类继承自 EventEmitter,load 和 close 调用完成后,会触发对应事件;get/del/set 使用 async/await 函数。

这样的好处是:外界在使用的时候,数据库的具体操作会放在事件的回调函数作用域内。

代码地址

性能测试

Q:定位内存泄漏问题

A:使用 heapdump.js 模块,定时抓取内存快照。然后将其当如 chrome 调试工具,剩下的就和前端调试一样了。

代码地址

Q:使用 profile 工具进行性能优化

A:nodejs 的新版本自带了 profile 工具。分析 profile 的结果有 3 步:

    1. 观察 Summary,查看 c++/js 占比
    1. 选择占比高的部分,进行观察分析
    1. Bottom up (heavy) profile 部分是函数占比(堆栈形式展示)

代码地址

Q:追踪系统调用过程

A:使用自带的 strace 工具。也可以追踪指定进程。

代码地址

Q:精确统计运行时间和基准测试

A:microtime.js 可以精确到微秒,代码地址

benchmark.js 可以进行基准测试,代码地址

流编程

Q:利用可写流,将输入转换为带有颜色的输出

A:可写流使用流 API 向底层的 I/O 输出数据。本题就是向 process.stdout 输出加工后的数据。

代码地址

Q:利用可读流,按行读取存放 json 的大文件

A: 可读流使用流 API「包装」来自底层的 I/O 源。由于涉及到 json,所以开启objectMode选项把流实例切换到对象模式,这样可以直接 push 一个对象。

代码地址

Q:利用双工流,完成一个带有交互提示的命令行

A:双工流既有 _read 方法,也有 _write 方法,所以它本身是可读可写的。可以在类的内容调用this._push(),也可以在外部对类调用 write() 方法。

代码地址

Q:利用转换流,将 csv 文件内容转换为内部数据结构(数组+对象)

A:转换流是一种特殊的双工流,它不要实现 _read 方法和 _write 方法,但要实现 _transform 方法。

代码要读取 csv 文件,并且要考虑没有读取完整数据的情况。代码利用「状态机」的思想进行了巧妙处理。

代码地址

Q:编写流的单元测试(以前面的转换流为例)

A:代码地址

TCP/IP 编程

Q:建立一个 tcp 服务,并进行测试

A:nodejs 中编写 tcp 服务使用 net 模块。

tcp 是面向连接的协议,这点体现在:net 创建服务后,connection 事件的回调函数的参数就是一个 socket,可以通过这个 socket 向连接的另一端写入数据,也可以接受来自连接的另一端的数据。

代码地址

Q:基于 C/S 架构,编写 udp 的客服端和服务端,服务端会对接受的消息进行广播

A:nodejs 中编写 udp 服务使用 dgram 模块。

udp 是面向数据包,且数据包大小有限制,默认不能超过 64kb,否则会被丢弃。由于不像 tcp 协议那样面向连接,所以它的 s 端,直接接收消息,也就是在 message 事件的回调中进行处理。它的 c 端也不需要先建立连接,而是直接向 ip + port 发送数据。

代码地址

Q:本地签发证书

A:为了方便本地进行 https 开发,需要使用 openssl 在本地签发证书。效果是访问时(例如https://localhost:8888),浏览器会显示「绿锁」。

代码地址

Q:实现一个支持 http 和 https 协议的 VPN

A:本质上是利用 nodejs 实现 http 代理和 https 代理。坑点在于 https 代理中,需要特别处理connect事件。

代码地址

多进程编程

Q:使用 nodejs 创建孤儿进程、僵尸进程和守护进程

A:孤儿进程是指父进程先退出,子进程由 pid 为 1 的 init 进程托管。

僵尸进程是指子进程先退出,但是父进程没有获取子进程的状态信息,导致子进程的进程描述符仍然保存在系统中。僵尸进程是有危害的,处理方法是退出主进程,init 进程会以父进程的身份对僵尸进程状态进行处理。

守护进程是在「后台运行」不受「终端控制」的进程(如输入、输出等)。在 nodejs 中,开启守护进程需要满足三个条件:

  • 使子进程成为进程组的头
  • 中断父子进程的 i/o
  • 去除父进程的事件循环中对子进程的引用

代码地址

Q:使用 nodejs 模拟操作系统管道运算符|

A:利用支持流的spawn函数和pipe即可。

代码地址

Q: 安全创建子进程

A:因为 execFile 不会衍生 shell,所以不会用到 shell 的解析功能。这不是缺陷,是 feature。对于需要接受并执行来自用户的输入的场景,是十分有用的。

代码地址

Q:nodejs 中进程通信的方法

A:常见的有四种方法,这里总结一下:

  • 普通流 pipe:child.stdin/stdout
  • nodejs 本身提供的 ipc 管道:process.send()process.on('message')
  • sockets 通信:process.send可以传递 socket 句柄。参考孤儿进程例子
  • 第三方工具:redis、消息队列

代码地址

Q:实现进程池,用于 cpu 密集型计算

A:按照《深入浅出 nodejs》,在处理 cpu 密集型问题的时候,应该使用 master/worker 编程模型,以充分利用现代计算机的多核优势。

但对于 nodejs 来说,每次进行计算都启动一个实例是非常浪费时间的(v8、加载库、开辟进程空间等等)。所以可以准备一个进程池,池中实例可以重复利用,并且支持排队操作。

代码地址

扫描下方二维码,回复「博客文章」获取解锁验证码

步骤:打开微信 => 扫描二维码 => 关注「心谭博客」公众号 => 发送「博客文章」即可解锁博客全部文章

输入验证码:

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×