diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..0328b5d --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,12 @@ +# These are supported funding model platforms + +github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] +patreon: # Replace with a single Patreon username +open_collective: # Replace with a single Open Collective username +ko_fi: # Replace with a single Ko-fi username +tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel +community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry +liberapay: # Replace with a single Liberapay username +issuehunt: # Replace with a single IssueHunt username +otechie: # Replace with a single Otechie username +custom: https://github.com/yjhjstz/deep-into-node/blob/master/alipay.jpg diff --git a/README.md b/README.md index cdb3187..81c3ce2 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,7 @@ Node.js 的源码分析,基于node v6.0.0。 * [文件 IO](chapter11/chapter11-5.md) * [Fs 精粹](chapter11/chapter11-6.md) * [进程](chapter13/README.md) - * [进程](chapter13/chapter13-1.md) + * [子进程](chapter13/chapter13-1.md) * [Cluster](chapter4/chapter4-1.md) * [Node.js 的坑](chapter14/chapter14-5.md) * [其他](chapter14/README.md) diff --git a/SUMMARY.md b/SUMMARY.md index 26fc352..d5dfba5 100644 --- a/SUMMARY.md +++ b/SUMMARY.md @@ -1,6 +1,6 @@ # Table of content -* [惊鸿一瞥](chapter1/README.md) +* [Node简介](chapter1/README.md) * [架构一览](chapter1/chapter1-0.md) * [为啥是libuv](chapter1/chapter1-1.md) * [V8 概念](chapter2/chapter2-0.md) diff --git a/chapter1/README.md b/chapter1/README.md index 19d9660..a23fd3f 100644 --- a/chapter1/README.md +++ b/chapter1/README.md @@ -1 +1,6 @@ # Chapter1 + +* [架构一览](/chapter1/chapter1-0.md) +* [为啥是libuv](/chapter1/chapter1-1.md) +* [V8 概念](/chapter2/chapter2-0.md) +* [C++和JS 交互](/chapter2/chapter2-1.md) diff --git a/chapter10/README.md b/chapter10/README.md index e69de29..06643aa 100644 --- a/chapter10/README.md +++ b/chapter10/README.md @@ -0,0 +1,4 @@ +# Chapter10 + +* [HTTP Server](/chapter10/chapter10-1.md) +* [HTTP Client](/chapter10/chapter10-2.md) diff --git a/chapter10/chapter10-2.md b/chapter10/chapter10-2.md index 1517eaa..71c5fb2 100644 --- a/chapter10/chapter10-2.md +++ b/chapter10/chapter10-2.md @@ -200,5 +200,8 @@ function parserOnIncomingClient(res, shouldKeepAlive) { ### 参考 +*https://nodejs.org/api/http.html +*https://github.com/nodejs/node/blob/master/lib/_http_client.js +*https://github.com/nodejs/http-parser diff --git a/chapter11/README.md b/chapter11/README.md index 33514ef..f800dfb 100644 --- a/chapter11/README.md +++ b/chapter11/README.md @@ -1 +1,8 @@ -# Chapter11 \ No newline at end of file +# Chapter11 + +* [文件系统](/chapter11/chapter11-2.md) +* [文件抽象](/chapter11/chapter11-1.md) +* [IO 那些事儿](/chapter11/chapter11-3.md) +* [libuv的选型](/chapter11/chapter11-4.md) +* [文件 IO](/chapter11/chapter11-5.md) +* [Fs 精粹](/chapter11/chapter11-6.md) diff --git a/chapter11/chapter11-1.md b/chapter11/chapter11-1.md index d9002a9..b4b789e 100644 --- a/chapter11/chapter11-1.md +++ b/chapter11/chapter11-1.md @@ -23,6 +23,7 @@ fs模块是文件操作的封装,它提供了文件的读取、写入、更名 - read - write - close + 上面的操作比较简单,就不是细说,后面会写文章再介绍读文件、写文件、刷新数据这几个重要的操作。如果有兴趣,可以通过man 2 read 命令来查看帮助文档。 @@ -53,7 +54,7 @@ fs模块是文件操作的封装,它提供了文件的读取、写入、更名 void *mmap(void *addr, size_t len, int prot, int flags, int fd, off_t offset); ``` -通过mmap系统调用,把一个文件映射到进程虚拟址址空间上。也就是说磁盘上的文件,现在在在系统看来是一个内存数组了,这样应用程序访问文件就不需要系统IO调用,而是直接读取内存。 +通过mmap系统调用,把一个文件映射到进程虚拟地址空间上。也就是说磁盘上的文件,现在在系统看来是一个内存数组了,这样应用程序访问文件就不需要系统IO调用,而是直接读取内存。 #### 优点: * 1、从内存映像文件中读写,避免了read、write多余的拷贝。 diff --git a/chapter11/chapter11-2.md b/chapter11/chapter11-2.md index 10b1467..c593751 100644 --- a/chapter11/chapter11-2.md +++ b/chapter11/chapter11-2.md @@ -4,7 +4,7 @@ 磁盘的0号扇区为主引导记录(Master Boot Record,MBR),用来引导计算机。 -在MBR的结尾是分区表,该分区表标识了每个分区的起始和结束地址。表中的一个分区被标识为活动分区,在计算机被引导时,BIOS读入并执行MBR。MBR做首先做的是确定活动分区,读入它的第一个块,称为引导块,并执行。 +在MBR的结尾是分区表,该分区表标识了每个分区的起始和结束地址。表中的一个分区被标识为活动分区,在计算机被引导时,BIOS读入并执行MBR。MBR首先做的是确定活动分区,读入它的第一个块,称为引导块,并执行。 ### 一种常见的文件系统(分区)结构图 @@ -19,12 +19,12 @@ - 用来管理磁盘块,标识磁盘块是空闲还是被使用了。 ### 文件系统实现 -文件系统一个重要的功能是记录文件使用了哪些磁盘块以及磁盘块的管理,标识磁盘块是否被使用,还是空闲。实现思路有下面几种。 +文件系统一个重要的功能是记录文件使用了哪些磁盘块以及磁盘块的管理,标识磁盘块是被使用,还是空闲。实现思路有下面几种。 * 连续分配 * 把每个文件存储在相邻的磁盘块上。如磁盘块大小2KB,200KB的文件,需要 100个磁盘块,如果磁盘块大小为4KB,刚需要50个磁盘块。 - 由于每个文件都是从一个新的磁盘块开始的,这样如果一个文件只占了磁盘块大小的一半,那么另一半就被浪费了,没法被别的文件使用,不过连续分配的实现,实现比较简单,只需要记录文件的第一个磁盘块位置和块数,另外读取性能,因为只需要一次寻道,之后不需要导道和旋转延迟。 + 由于每个文件都是从一个新的磁盘块开始的,这样如果一个文件只占了磁盘块大小的一半,那么另一半就被浪费了,没法被别的文件使用,不过连续分配的实现,实现比较简单,只需要记录文件的第一个磁盘块位置和块数,另外读取快速,因为只需要一次寻道,之后不需要寻道和旋转延迟。 随着时间推移,磁盘碎片比较严重。因为反复写文件,删除文后,容易在磁盘块上形成空洞。 @@ -57,9 +57,9 @@ Unix/Linux文件系统的实现是采用i节点的方案,文件系统ext2、ex C++、JAVA程序员很容易想到利用多态来实现,对,操作系统也是采用类似的思路。对不同的文件系统,抽象出所有文件系统都支持的、基本的、概念上的数据结构和接口 ,如前文描述的关于文件和目录的基本操作。 -这些统一抽象组件,叫着虚拟文件系统(Virtual File System ,VFS),VFS作为系统内核组件,为用户空间程序提供了文件和文件系统相关的接口。 +这些统一抽象组件,叫做虚拟文件系统(Virtual File System ,VFS),VFS作为系统内核组件,为用户空间程序提供了文件和文件系统相关的接口。 -VFS使用得用户可以直接调用write、read 这样的文件系统调用,而不用考虑底层的文件是什么文件系统。 +VFS使用得用户可以直接调用write、read 这样的文件系统调用,而不用考虑底层是什么文件系统。 横向地看下它们的关系,如下图所示 ![](read-write.png) diff --git a/chapter11/chapter11-3.md b/chapter11/chapter11-3.md index 75cbac6..1c1667b 100644 --- a/chapter11/chapter11-3.md +++ b/chapter11/chapter11-3.md @@ -1,4 +1,4 @@ -## 异步那些事儿 +## IO 那些事儿 Linux 异步 I/O 是 Linux 内核中提供的一个相当新的增强。它是 2.6 版本内核的一个标准特性,AIO 背后的基本思想是允许进程发起很多 I/O 操作,而不用阻塞或等待任何操作完成。稍后或在接收到 I/O 操作完成的通知时,进程就可以检索 I/O 操作的结果。 ### I/O 模型 @@ -52,4 +52,4 @@ select 函数所提供的功能(异步阻塞 I/O)与 AIO 类似。不过, ### 参考 -- https://www.ibm.com/developerworks/cn/linux/l-async/ \ No newline at end of file +- https://www.ibm.com/developerworks/cn/linux/l-async/ diff --git a/chapter11/chapter11-4.md b/chapter11/chapter11-4.md index 78f932f..0e3afa3 100644 --- a/chapter11/chapter11-4.md +++ b/chapter11/chapter11-4.md @@ -6,8 +6,8 @@ Linux native aio 有两种API,一种是libaio提供的API,一种是利用系 - io_setup : 是用来设置一个异步请求的上下文,第一个参数是请求事件的个数,第二个参数唯一标识一个异步请求。 - io_commit: 是用来提交一个异步io请求的,在提交之前,需要设置一下结构体`iocb`。 -- io_getevents: 用来获取完成的io事件,参数`min_nr`是事件个数的的最小值,`nr`是事件个数的最大值,如果没有足够的事件发生,该函数会阻塞。 -- io_destroy:在所有时间处理完之后,调用此函数销毁异步io请求。 +- io_getevents: 用来获取完成的io事件,参数`min_nr`是事件个数的最小值,`nr`是事件个数的最大值,如果没有足够的事件发生,该函数会阻塞。 +- io_destroy:在所有事件处理完之后,调用此函数销毁异步io请求。 #### 限制 aio只能使用于常规的文件IO,不能使用于socket,管道等IO,但对于 libuv 的 fs 模块使用需求已经足够了。 @@ -99,3 +99,7 @@ node.js 异步 IO 的脉络已经清晰,我们清楚的看到这样的一个 T * 支持任务优先级。 ### 参考 +* [GitHub Issue #28 关于 libuv 在 Linux 上 AIO 实现的讨论](citehttps://github.com/libuv/libuv/issues/28) +* [GitHub Issue #461 对 native AIO 实现稳定性的讨论](citehttps://github.com/libuv/libuv/issues/461) +* [基于 libuv 的 native AIO 实现提交记录](citehttps://github.com/yjhjstz/libuv/commit/2748728635c4f74d6f27524fd36e680a88e4f04a) +* [简书文章《什么是 SSL?》及相关加密技术讨论](citehttp://www.jianshu.com/p/a8b87e436ac7) diff --git a/chapter11/chapter11-5.md b/chapter11/chapter11-5.md index ed2e42d..bad35a7 100644 --- a/chapter11/chapter11-5.md +++ b/chapter11/chapter11-5.md @@ -179,14 +179,29 @@ worker线程往下执行,从wq取出w,执行w->work()。 ### 回调 +在一次异步文件 I/O 操作中,回调函数是整个流程的最后一环,用以将底层 I/O 操作的结果传递给上层应用。具体流程如下: +1. **工作线程完成任务** + 工作线程从全局工作队列中取出一个 `uv__work` 请求,执行其中封装的 `uv__fs_work` 函数,该函数完成实际的文件 I/O 操作(例如文件读取)。 +2. **通知主线程** + 完成 I/O 操作后,工作线程调用 `uv_async_send`,向主线程发送信号。这一机制确保了回调函数不会在工作线程中执行,而是延迟到主线程中被调度,从而保证了 JavaScript 层代码的单线程执行环境。 +3. **执行回调函数** + 主线程在检测到异步事件后,会调用 `uv__fs_done` 回调函数。该函数负责将 `uv_fs_t` 请求对象中的操作结果(如读取的数据、错误码等)传递给上层 C++ 模块中的 `After` 函数。随后,`After` 函数会调用用户在 JavaScript 层传入的回调函数,将操作结果反馈给用户。 + +4. **事件循环与回调协调** + 由于 Node.js 的 JavaScript 代码运行在单线程的事件循环中,因此所有用户回调都必须在主线程中执行。这一设计确保了回调函数能够安全地访问共享状态,而无需担心线程安全问题,同时也使得异步 I/O 操作与事件循环的协同工作成为可能。 + +这种回调机制将底层的异步 I/O 操作与上层的 JavaScript 回调解耦合,通过工作线程与主线程之间的信号通知,实现了高效且安全的异步操作流程。 ### 总结 通过对各层请求对象的梳理,也详细梳理出了一次 read 请求的脉络, 使读者有了一个理性的认识。 ### 参考 - +- https://github.com/nodejs/node/blob/master/src/node_file.cc +- https://github.com/libuv/libuv +- https://github.com/iojs/io.js/pull/718 +- https://github.com/libuv/libuv/blob/v1.x/src/unix/fs.c diff --git a/chapter12/chapter12-1.md b/chapter12/chapter12-1.md index 5091ae9..1022585 100644 --- a/chapter12/chapter12-1.md +++ b/chapter12/chapter12-1.md @@ -1,7 +1,7 @@ ## Global对象 -所有属性都可以在程序的任何地方访问,即全局变量。在javascript中,通常window是全局对象,而node.js的全局对象是global,所有全局变量都是global对象的属性,如:console、process等。 +所有属性都可以在程序的任何地方被访问,即全局变量。在javascript中,通常window是全局对象,而node.js的全局对象是global,所有全局变量都是global对象的属性,如:console、process等。 ### 全局对象与全局变量 @@ -28,7 +28,7 @@ node.js中不可能在最外层定义变量,因为所有的用户代码都是 - require():用于加载模块。 - Buffer():用于操作二进制数据。 -**Node提供两个全局变量,都以两个下划线开头。** +**伪全局变量。** * _filename:指向当前运行的脚本文件名。 * _dirname:指向当前运行的脚本所在的目录。 除此之外,还有一些对象实际上是模块内部的局部变量,指向的对象根据模块不同而不同,但是所有模块都适用,可以看作是伪全局变量,主要为module, module.exports, exports等。 @@ -71,10 +71,10 @@ console.log(Person.sex); // undefined | | | | V V - function Object + Object function ``` -而node.js导出的,永远是module。exports指向的对象,在这里就是function。所以exports指向的那个object,现在已经不会被导出了,为其增加的属性当然也就没用了。 +而node.js导出的,永远是module.exports指向的对象,在这里就是function。所以exports指向的那个object,现在已经不会被导出了,为其增加的属性当然也就没用了。 如果希望把sex属性也导出,就需要这样写: @@ -98,4 +98,7 @@ exports.sex = "male"; ### 参考 +*https://nodejs.org/api/globals.html +*https://nodejs.org/api/modules.html +*https://medium.com/geekculture/understanding-the-difference-between-module-exports-and-exports-in-node-js-264fc500a409 diff --git a/chapter13/README.md b/chapter13/README.md index e69de29..30fa0d2 100644 --- a/chapter13/README.md +++ b/chapter13/README.md @@ -0,0 +1,4 @@ +# Chapter13 + +* [进程](/chapter13/chapter13-1.md) +* [Cluster](/chapter4/chapter4-1.md) diff --git a/chapter13/chapter13-1.md b/chapter13/chapter13-1.md index e8300c7..efbe9e4 100644 --- a/chapter13/chapter13-1.md +++ b/chapter13/chapter13-1.md @@ -120,14 +120,14 @@ send方法可以发送的对象包括如下集中: 传递的过程: -** 主进程 **: +**主进程**: - 传递消息和句柄。 - 将消息包装成内部消息,使用 JSON.stringify 序列化为字符串。 - 通过对应的 handleConversion[message.type].send 方法序列化句柄。 - 将序列化后的字符串和句柄发入 IPC channel 。 -** 子进程 ** +**子进程**: - 使用 JSON.parse 反序列化消息字符串为消息对象。 - 触发内部消息事件(internalMessage)监听器。 diff --git a/chapter14/README.md b/chapter14/README.md index e69de29..791dc54 100644 --- a/chapter14/README.md +++ b/chapter14/README.md @@ -0,0 +1,5 @@ +# Chapter14 + +* [Node.js & Android](/chapter14/chapter14-1.md) +* [Node.js & Docker](/chapter14/chapter14-2.md) +* [Node.js 调优](/chapter14/chapter14-3.md) diff --git a/chapter2/chapter2-0.md b/chapter2/chapter2-0.md index 857f481..f81e5b9 100644 --- a/chapter2/chapter2-0.md +++ b/chapter2/chapter2-0.md @@ -9,7 +9,7 @@ V8 更加直接的将抽象语法树通过 JIT 技术转换成本地代码,放 在 V8 生成本地代码后,也会通过 Profiler 采集一些信息,来优化本地代码。虽然,少了生成字节码这一阶段的性能优化, 但极大减少了转换时间。 -> PS: Tuborfan 将逐步取代 Crankshaft +> PS: TurboFan 将逐步取代 Crankshaft 在使用 v8 引擎之前,先来了解一下几个基本概念:句柄(handle),作用域(scope),上下文环境(可以简单地理解为运行环境)。 @@ -17,7 +17,7 @@ V8 更加直接的将抽象语法树通过 JIT 技术转换成本地代码,放 > An isolate is a VM instance with its own heap. It represents an isolated instance of the V8 engine. > V8 isolates have completely separate states. Objects from one isolate must not be used in other isolates. -一个 Isolate 是一个独立的虚拟机。对应一个或多个线程。但同一时刻 只能被一个线程进入。所有的 Isolate 彼此之间是完全隔离的, 它们不能够有任何共享的资源。如果不显示创建 Isolate, 会自动创建一个默认的 Isolate。 +一个 Isolate 是一个独立的虚拟机。对应一个或多个线程。但同一时刻 只能被一个线程进入。所有的 Isolate 彼此之间是完全隔离的, 它们不能够有任何共享的资源。如果不显式创建 Isolate, 会自动创建一个默认的 Isolate。 后面提到的 Context、Scope、Handle 的概念都是一个 Isolate 内部的, 如下图: ![](Context.png) @@ -90,17 +90,17 @@ Context 为准的,当退出这个函数时,又恢复到了原来的 Context 在深入研究垃圾回收器的内部工作原理之前,首先来看看堆是如何组织的。V8 将堆分为了几个不同的区域: ![2015-11-17 下午 3.09.08](http://alinode-assets.oss-cn-hangzhou.aliyuncs.com/2336435d-bdd4-4d86-8e28-b253e7d7ad6a.png) -** 新生区 **:大多数对象开始时被分配在这里。新生区是一个很小的区域,垃圾回收在这个区域非常频繁,与其他区域相独立。 +**新生区**:大多数对象开始时被分配在这里。新生区是一个很小的区域,垃圾回收在这个区域非常频繁,与其他区域相独立。 -** 老生指针区 **:包含大多数可能存在指向其他对象的指针的对象。大多数在新生区存活一段时间之后的对象都会被挪到这里。 +**老生指针区**:包含大多数可能存在指向其他对象的指针的对象。大多数在新生区存活一段时间之后的对象都会被挪到这里。 -** 老生数据区 **:这里存放只包含原始数据的对象(这些对象没有指向其他对象的指针)。字符串、封箱的数字以及未封箱的双精度数字数组,在新生区经历一次 Scavenge 后会被移动到这里。 +**老生数据区**:这里存放只包含原始数据的对象(这些对象没有指向其他对象的指针)。字符串、封箱的数字以及未封箱的双精度数字数组,在新生区经历一次 Scavenge 后会被移动到这里。 -** 大对象区 **:这里存放体积超过 1MB 大小的对象。每个对象有自己 mmap 产生的内存。垃圾回收器从不移动大对象。 +**大对象区**:这里存放体积超过 1MB 大小的对象。每个对象有自己 mmap 产生的内存。垃圾回收器从不移动大对象。 -**Code 区 **:代码对象,也就是包含 JIT 之后指令的对象,会被分配到这里。 +**Code 区**:代码对象,也就是包含 JIT 之后指令的对象,会被分配到这里。 -**Cell 区、属性 Cell 区、Map 区 **:这些区域存放 Cell、属性 Cell 和 Map,每个区域因为都是存放相同大小的元素,因此内存结构很简单。 +**Cell 区、属性 Cell 区、Map 区**:这些区域存放 Cell、属性 Cell 和 Map,每个区域因为都是存放相同大小的元素,因此内存结构很简单。 > 如上图:在 node-v4.x 之后,区域进行了合并为:新生区,老生区,大对象区,Map 区,Code 区 diff --git a/chapter2/chapter2-1.md b/chapter2/chapter2-1.md index aafde6f..247a66c 100644 --- a/chapter2/chapter2-1.md +++ b/chapter2/chapter2-1.md @@ -3,7 +3,7 @@ ### 数据及模板 -由于 C++ 原生数据类型与 JavaScript 中数据类型有很大差异,因此 V8 提供了 Value 类,从 JavaScript 到 C++,从 C++ 到 JavaScrpt 都会用到这个类及其子类,比如: +由于 C++ 原生数据类型与 JavaScript 中数据类型有很大差异,因此 V8 提供了 Value 类,从 JavaScript 到 C++,从 C++ 到 JavaScript 都会用到这个类及其子类,比如: ```c++ Handle Add(const Arguments& args){ int a = args[0]->Uint32Value(); diff --git a/chapter2/chapter2-2.md b/chapter2/chapter2-2.md index 42ff4a0..6af1443 100644 --- a/chapter2/chapter2-2.md +++ b/chapter2/chapter2-2.md @@ -5,7 +5,7 @@ npm 世界最大的模块仓库,我们看几个数据: * ~21 万模块数量 * 每天亿级模块下载量 -* 每周 10 亿级的模块下周量 +* 每周 10 亿级的模块下载量 由此诞生了一家做 npm 包管理的公司 `npmjs.com`. @@ -143,7 +143,7 @@ Module.prototype.require = function(path) { }; ``` -首先 assert 模块进行简单的 path 变量的判断,需要传人的 `path` 是一个 string 类型。 +首先 assert 模块进行简单的 path 变量的判断,需要传入的 `path` 是一个 string 类型。 ```js // Check the cache for the requested file. @@ -260,7 +260,7 @@ NativeModule.prototype.compile = function() { }; ``` -`wrap` 函数将 http.js 包裹起来, 交由 `runInThisContext` 编译源码,返回 fn 函数, 依次将参数传人。 +`wrap` 函数将 http.js 包裹起来, 交由 `runInThisContext` 编译源码,返回 fn 函数, 依次将参数传入。 diff --git a/chapter3/chapter3-1.md b/chapter3/chapter3-1.md index d5083e4..358d1a5 100644 --- a/chapter3/chapter3-1.md +++ b/chapter3/chapter3-1.md @@ -169,7 +169,7 @@ static int timer_less_than(const struct heap_node* ha, 102 return 0; 103 } ``` -L94,检查 handle, 如果是非获取的,则说明没有启动过,则返回成功。 +L94,检查 handle, 如果是非活跃的,则说明没有启动过,则返回成功。 L97-L99, 从最小堆中删除 timer的节点。 L100, 重置句柄,并减少计数。 diff --git a/chapter3/chapter3-2.md b/chapter3/chapter3-2.md index c79c71d..312be13 100644 --- a/chapter3/chapter3-2.md +++ b/chapter3/chapter3-2.md @@ -23,7 +23,7 @@ function* fibonacci() { ### yield与异步 yield可以暂停运行流程,那么便为改变执行流程提供了可能。这和Python的coroutine类似。 -Geneartor之所以可用来控制代码流程,就是通过yield来将两个或者多个Geneartor的执行路径互相切换。这种切换是语句级别的,而不是函数调用级别的。其本质是CPS变换。 +Generator之所以可用来控制代码流程,就是通过yield来将两个或者多个Generator的执行路径互相切换。这种切换是语句级别的,而不是函数调用级别的。其本质是CPS变换。 yield之后,实际上本次调用就结束了,控制权实际上已经转到了外部调用了generator的next方法的函数,调用的过程中伴随着状态的改变。那么如果外部函数不继续调用next方法,那么yield所在函数就相当于停在yield那里了。所以把异步的东西做完,要函数继续执行,只要在合适的地方再次调用generator 的next就行,就好像函数在暂停后,继续执行。 @@ -242,7 +242,7 @@ L2297从 result 寄存器中取出 value, L2299调用 `RUNTIME_FUNCTION(Runtime_ ### 延伸 我们看到node.js依托 v8层面实现了协程,有兴趣的同学可以关心下 fibjs, 它是用 C库实现了协程,遇到异步调用就 "yield" 放弃 CPU, -交由协程调度,也解决了 callcack hell 的问题。 +交由协程调度,也解决了 callback hell 的问题。 本质思想上两种方案没本质区别: * Generator是利用yield特殊关键字来暂停执行,而fibers是利用Fiber.yield()暂停 * Generator是利用函数返回的Generator句柄来控制函数的继续执行,而fibers是在异步回调中利用Fiber.current.run()继续执行。 diff --git a/chapter4/chapter4-1.md b/chapter4/chapter4-1.md index e6bc14c..9e61112 100644 --- a/chapter4/chapter4-1.md +++ b/chapter4/chapter4-1.md @@ -9,16 +9,16 @@ ### 竞争模型 -最初的 Node.js 多进程模型就是这样实现的,master 进程创建 socket,绑定到某个地址以及端口后,自身不调用 listen 来监听连接以及 accept 连接,而是将该 socket 的 fd 传递到 fork 出来的 worker 进程,worker 接收到 fd 后再调用 listen,accept 新的连接。但实际一个新到来的连接最终只能被某一个 worker 进程 accpet 再做处理,至于是哪个 worker 能够 accept 到,开发者完全无法预知以及干预。这势必就导致了当一个新连接到来时,多个 worker 进程会产生竞争,最终由胜出的 worker 获取连接。 +最初的 Node.js 多进程模型就是这样实现的,master 进程创建 socket,绑定到某个地址以及端口后,自身不调用 listen 来监听连接以及 accept 连接,而是将该 socket 的 fd 传递到 fork 出来的 worker 进程,worker 接收到 fd 后再调用 listen,accept 新的连接。但实际一个新到来的连接最终只能被某一个 worker 进程 accept 再做处理,至于是哪个 worker 能够 accept 到,开发者完全无法预知以及干预。这势必就导致了当一个新连接到来时,多个 worker 进程会产生竞争,最终由胜出的 worker 获取连接。 相信到这里大家也应该知道这种多进程模型比较明显的问题了 -* 多个进程之间会竞争 accpet 一个连接,产生惊群现象,效率比较低。 +* 多个进程之间会竞争 accept 一个连接,产生惊群现象,效率比较低。 * 由于无法控制一个新的连接由哪个进程来处理,必然导致各 worker 进程之间的负载非常不均衡。 -### round-robin (轮训) +### round-robin (轮询) 上面的多进程模型存在诸多问题,于是就出现了基于round-robin的另一种模型。 主要思路是master进程创建socket,绑定好地址以及端口后再进行监听。该socket的fd不传递到各个worker进程,当master进程获取到新的连接时,再决定将accept到的客户端socket fd传递给指定的worker处理。我这里使用了指定, 所以如何传递以及传递给哪个worker完全是可控的,round-robin只是其中的某种算法而已,当然可以换成其他的。 @@ -84,8 +84,8 @@ function onconnection(message, handle) { ``` 至此,也总结一下: -* 所有请求先同一经过内部TCP服务器。 -* 在内部TCP服务器的请求处理逻辑中,有负载均衡地挑选出一个worker进程,将其发送一个newconn内部消息,随消息发送客户端句柄。 +* 所有请求先统一经过内部TCP服务器。 +* 在内部TCP服务器的请求处理逻辑中,有负载均衡地挑选出一个worker进程,向其发送一个newconn内部消息,随消息发送客户端句柄。 * Worker进程接收到此内部消息,根据客户端句柄创建net.Socket实例,执行具体业务逻辑,返回。 ### listen 端口复用 @@ -232,4 +232,4 @@ function listen(self, address, port, addressType, backlog, fd, exclusive) { ### 总结 -### 参考 \ No newline at end of file +### 参考 diff --git a/chapter5/chapter5-1.md b/chapter5/chapter5-1.md index 334f656..2af1cc1 100644 --- a/chapter5/chapter5-1.md +++ b/chapter5/chapter5-1.md @@ -65,11 +65,11 @@ more用来标识是否进行下一轮循环。 env->event_loop()会返回之前 ### process.nextTick ![](settimeout.jpeg) -带着这个问题,我们看看 JS 层的 nextTick 是怎么被驱动。 +带着这个问题,我们看看 JS 层的 nextTick 是怎么被驱动的。 在入口点 `src/node.js`, `processNextTick` 方法构建了 `process.nextTick` API。 -`process._tickCallback ` 作为 nexttick 的回调函数,挂到了 `process` 对象上,由 C++层面回调使用。 +`process._tickCallback ` 作为 nextTick 的回调函数,挂到了 `process` 对象上,由 C++ 层回调使用。 ```js startup.processNextTick = function() { @@ -149,7 +149,7 @@ Module.runMain = function() { `Module._load` 加载主脚本后,就调用 `_tickCallback`, 处理第一次的 tick 了。 -所以上面的疑问有了答案,`nextTick` 主要在 `uv__io_poll` 驱动。为什么说主要呢? 因为还 +所以上面的疑问有了答案,`nextTick` 主要在 `uv__io_poll` 驱动。为什么说主要呢?因为还 可能在 Timer模块驱动,具体细节留给读者去研究啦。 diff --git a/chapter7/chapter7-1.md b/chapter7/chapter7-1.md index 0b9977d..f015621 100644 --- a/chapter7/chapter7-1.md +++ b/chapter7/chapter7-1.md @@ -25,7 +25,7 @@ ### Event.js 实现 EventEmitter 允许我们注册一个或多个函数作为 listeners。 在特定的事件触发时被调用。如下图: -![](2016-05-09 14.13.19.png) +![](https://github.com/yjhjstz/deep-into-node/blob/master/chapter7/2016-05-09%2014.13.19.png) #### listeners 存储 一般观察者的设计模式的实现逻辑是类似的,都是有一个类似map的结构,存储监听事件和回调函数的对应关系。 ```js diff --git a/chapter9/README.md b/chapter9/README.md index e69de29..d47be1e 100644 --- a/chapter9/README.md +++ b/chapter9/README.md @@ -0,0 +1,5 @@ +# Chapter9 + +* [Socket](/chapter9/chapter9-1.md) +* [构建应用](/chapter9/chapter9-2.md) +* [加密](/chapter9/chapter9-3.md) diff --git a/chapter9/chapter9-1.md b/chapter9/chapter9-1.md index 53ff5aa..a1d57ef 100644 --- a/chapter9/chapter9-1.md +++ b/chapter9/chapter9-1.md @@ -158,11 +158,11 @@ util.inherits(Socket, stream.Duplex); ### 粘包 -> 一般所谓的TCP粘包是在一次接收数据不能完全地体现一个完整的消息数据。TCP通讯为何存在粘包呢?主要原因是TCP是以流的方式来处理数据,再加上网络上MTU的往往小于在应用处理的消息数据,所以就会引发一次接收的数据无法满足消息的需要,导致粘包的存在。处理粘包的唯一方法就是制定应用层的数据通讯协议,通过协议来规范现有接收的数据是否满足消息数据的需要。 +> 一般所谓的TCP粘包是在一次接收数据不能完全地体现一个完整的消息数据。TCP通讯为何存在粘包呢?主要原因是TCP是以流的方式来处理数据,再加上网络上MTU的值往往小于在应用处理的消息数据,所以就会引发一次接收的数据无法满足消息的需要,导致粘包的存在。处理粘包的唯一方法就是制定应用层的数据通讯协议,通过协议来规范现有接收的数据是否满足消息数据的需要。 #### 情况分析 -TCP粘包通常在流传输中出现,UDP则不会出现粘包,因为UDP有消息边界,发送数据段需要等待缓冲区满了才将数据发送出去,当满的时候有可能不是一条消息而是几条消息合并在换中去内,在成粘包;另外接收数据端没能及时接收缓冲区的包,造成了缓冲区多包合并接收,也是粘包。 +TCP粘包通常在流传输中出现,UDP则不会出现粘包,因为UDP有消息边界。使用TCP协议发送数据段需要等待缓冲区满了才将数据发送出去,当满的时候有可能不是一条消息而是几条消息存在于缓冲区内,为了优化性能(Nagle算法),TCP会将这几个小数据包合并为一个大的数据包,造成粘包;另外在接收数据端,如果没能及时接收缓冲区的包,也会造成缓冲区多包合并接收,这也是粘包。 #### 解决办法 diff --git a/chapter9/chapter9-2.md b/chapter9/chapter9-2.md index c359a03..7958ac2 100644 --- a/chapter9/chapter9-2.md +++ b/chapter9/chapter9-2.md @@ -59,7 +59,7 @@ server.on('connection', function(sock) { 1120 util.inherits(Server, EventEmitter); ``` -`Server` 继承了 `EventEmitter`, 如果传人 callback 函数, L1086,L1091 则把传人的函数作为监听者 +`Server` 继承了 `EventEmitter`, 如果传入 callback 函数, L1086,L1091 则把传入的函数作为监听者 绑定到 `connnection`事件上, 然后 listen 。我们看看作为 server 端连接到来的回调处理。 ```js 1400 function onconnection(err, clientHandle) { @@ -139,7 +139,7 @@ client.on('close', function() { 创建 `Socket`对象后,client 端向server端发起连接,在真正的连接之前,需要进行 DNS 查询(提供 IP 的不用), 调用 `lookupAndConnect`, 之后才是调用 `function connect(self, address, port, addressType, localAddress, localPort) `发起连接。 -我们注意到五元祖: ``, 他们唯一的标识了一个网络连接。 +我们注意到五元组: ``, 他们唯一的标识了一个网络连接。 建立起全双工的 Socket 后,用户程序就可以监听 「data」事件,获取数据了。 diff --git a/chapter9/chapter9-3.md b/chapter9/chapter9-3.md index 6cc9d8f..470a109 100644 --- a/chapter9/chapter9-3.md +++ b/chapter9/chapter9-3.md @@ -67,15 +67,22 @@ MD2 | MD5 | MDC2 | SHA | RIPEMD | DSS ### 总结 -加密的安全性,主要是以下两种因素共同决定 -使用的加密算法 -* 密钥(key)的长度 -* 在相同的算法下,密钥长度增加一位,暴力破解的难度指数级增加。如果使用对称加密,现在AES-256足够安全。 +加密技术的安全性和性能主要取决于以下几个关键因素: -不过据说RSA的1024位密钥已经能被政府用非常昂贵的设备暴力破解,所以在使用RSA算法时,密钥长度要选2048,并没有决对的安全。 +1. **加密算法的选择** + 不同的加密算法在安全性和性能上存在差异。对称加密算法(例如 AES-256)在性能上通常优于非对称加密,但它们需要安全的密钥交换方式。非对称加密算法(如 RSA、DSA 或 EC)虽然在密钥管理和交换上更为便捷,但由于计算量较大,通常只用于密钥交换或数字签名。在实际应用中,例如 TLS/SSL 协议,就巧妙地结合了两者的优势:利用非对称加密进行安全的密钥交换,再使用对称加密进行数据传输,从而既保证了安全性,也提高了性能。 -加密的性能上, +2. **密钥长度与管理** + 密钥(key)的长度直接影响加密强度。在相同的算法下,密钥长度每增加一位,暴力破解的难度呈指数级增长。当前,AES-256 被认为足够安全,而对于非对称加密来说,RSA 的 1024 位密钥已不再安全,推荐至少使用 2048 位密钥。此外,合理的密钥管理策略也是保障整个加密体系安全的重要环节。 + +3. **证书与身份验证** + 在 TLS/SSL 协议中,服务器在建立连接时会向客户端发送签名证书,客户端则通过内置的权威 CA 列表对证书的签名机构进行验证。只有通过验证的证书才被视为可信,否则可能存在中间人攻击的风险。这一机制在确保通信双方身份真实可靠方面发挥着至关重要的作用。 + +4. **最新标准与实践** + 随着技术的发展,安全标准也在不断更新。例如,TLS 1.2 和 TLS 1.3 相较于早期的 SSL/TLS 版本在安全性和性能上都有显著提升。采用最新的安全协议和算法,并定期更新和审查系统配置,可以有效防止潜在的安全漏洞和攻击。 + +综合来看,安全加密不仅依赖于算法本身的强度和密钥长度,还需要在实际应用中合理地结合对称与非对称加密技术,以及配合严格的身份验证与证书管理策略。只有这样,才能在确保数据传输安全的同时,兼顾加密性能,构建一个既高效又安全的通信系统。 ### 参考 -* http://www.jianshu.com/p/a8b87e436ac7 \ No newline at end of file +* http://www.jianshu.com/p/a8b87e436ac7