node 基础知识
本知识点总结自朴灵大神的 《深入浅出 node.js》
模块机制 | 包与 NPM | Node 的异步 I/O | Node 的非 I/O 的异步 API
Node 异步编程 | 事件发布/订阅模式 | 内存控制 | Buffer
构建 TCP 服务 | 构建 UDP 套接字 | 构建 HTTP 服务 | 构建 WebSocket 服务
Node
先推荐以下链接:
node 基础教程
node Api 文档
总结 node 特点:
- 异步 I/O 从文件读取到网络请求,调用之间无须等待之前的 I/O 结束,多个任务的时间为最长耗时的任务耗时
- 事件与回调函数
- 单线程
- 跨平台
模块机制
CommonJs 为 JavaScript 制定了一个美好的愿景–希望 JavaScript 能够在任何地方运行,涵盖了模块,二进制,Buffer,I/O 流,web 服务器网关接口等规范
CommonJs 对模块的定义,主要分为模块引用,模块定义,模块标识 3 个部分
模块引用
模块引用,存在 require()方法,这个方法接收模块标识,以此引入一个模块的 API 到当前上下文var math=require('math')
模块定义
上下文提供 exports 对象用于导出当前模块的方法或变量,而 module 对象代表模块自身,exports 是 module 的属性
1 | exports.add = function () { |
模块标识
require()
内的参数,必须符合小驼峰命名的字符串,或者以.,..开头的相对路径或绝对路径,它可以没有文件名后缀.js
包与 NPM
包实际上是一个存档文件,即一个目录打包成压缩文件,安装后解压还原为目录,完全符合 CommonJs 规范的包目录应该包含以下文件:
- package.json: 包描述文件
- bin: 存放可执行二进制文件
- lib:存放 JavaScript 代码
- doc:存放文档
- test:存放单元测试用例的代码
NPM 安装依赖包
执行语句 npm install express
执行该命令后,NPM 会在当前目录下创建node_modules
目录,然后在node_modules
目录下创建express
目录,接着将包解压到这个目录下
安装好依赖包后,直接在代码中调用require('express')
即可引入该包
Node 的异步 I/O
异步 I/O 与非阻塞 I/O:
- 阻塞 I/O 是调用之后一定要等到系统内核完成所有操作,调用才结束,返回数据
- 非阻塞 I/O 在调用之后,不带数据直接返回,要返回数据,还需要通过文件描述符再次读取,因此需要通过轮询技术判断操作是否完成
Node 的异步 I/O 模式:
事件循环
进程启动时,Node 便会创建一个类似于 while 的循环,每执行一次循环体的过程称为 Tick。每个 Tick 的过程就是查看有无事件待处理,
如果有,就取出事件及相关的回调函数。如果存在相关的回调函数,就执行他们。然后进入下一个循环,直到不再有事件处理观察者
每个事件循环中有一个或多个观察者,判断是否有事件要处理的过程就是向这些观察者询问是否有事要处理
异步 I/O,网络请求等源源不断为 Node 提供不同类型的事件,这些事件被传递到对应的观察者那里,事件循环从观察者那里取出事件并处理请求对象
执行回调
Node 的非 I/O 的异步 API
定时器
setTimeout()与 setInterval()与浏览器中的 API 一致,实现原理与异步 I/O 比较类似,只是不需要 I/O 线性池的参与
创建的定时器会被插入到定时器观察者内部的一个红黑树中,每次 Tick 执行时,会从该红黑树中迭代取出定时器对象,检查是否超过定时事件,
如果超过,就形成一个事件,它的回调事件将立即执行,因此定时器实际执行时间会受事件循环影响process.nextTick()
调用 process.nextTick()方法,只会将回调函数放入队列中,在下一轮 Tick 中取出来执行,达到立即异步执行一个任务的效果setImmediate()
setImmediate()方法与 process.nextTick()方法类似,都是将异步函数延迟执行,
但 process.nextTick()中的回调函数执行的优先级要高于 setImmediate(), process.nextTick()的回调函数保存在一个数组中,setImmediate()则是保存在链表中,
在每轮循环时,将数组中的回调函数全部执行完,而只执行链表中的一个回调函数
Node 异步编程基础
高阶函数
高阶函数是可以把函数作为参数,或是将函数作为返回值的函数
1 | //高阶函数实例 |
偏函数
偏函数用法是指创建一个调用另一个部分——参数或者变量已经预设好的函数——的函数用法
1 | //偏函数实例 |
闭包(内包)
作用域链上的对象访问只能向上,外部作用域无法向内部作用域访问
在 JavaScript 中,实现外部作用域对内部作用域中的变量访问的方法称为闭包,这得益于高阶函数的特性:函数可以作为参数和返回值
1 | //闭包例子 |
异步编程解决方案——事件发布/订阅模式
事件发布/订阅模式可以实现一个事件与多个回调函数的关联,这些回调函数又称为事件侦听器
通过 emit()发布事件后,消息会给立即传递给当前事件的所有侦听器执行
1 | //订阅 |
在事件订阅/发布模式中,通常有一个 once()方法,通过它添加的侦听器只能执行一次,在执行之后就会将它与事件的关联移除。这个特性常常可以帮我们过滤一些重复性的事件响应,解决雪崩问题
内存控制
在 V8 引擎中,主要将内存分为新生代和老生代两代,新生代中的对象为存活时间较短的对象,老生带中为存活时间较长的对象
在分代的基础上,新生代中的对象主要通过 Cheney 算法进行垃圾回收
Cheney 算法是一种采用复制的方式实现的垃圾回收算法,将内存分为 From 空间和 To 空间,分配对象时先在 From 空间进行分配
当开始进行垃圾回收时,会检查 From 空间中存活的对象,这些存活的对象将被复制到 To 空间中,而非存活空间占用的空间将被释放,完成复制之后,From 空间和 To 空间的角色发生对换
而当一个对象经过多次复制依然存活时,它会被认为是生命周期较长的对象,随后会被移动到老生带中,这个过程称为晋升
在分代的基础上,老生带中的对象通过 Mark-Compact 方法管理,标志整理的意思
在标记阶段遍历堆中所有的对象,并标记活着的对象,在整理阶段,先清除没有标记的对象,再将活着的对象往一端移动,防止出现内存空间不连续的问题
Buffer
Buffer 是一个像 Array 的对象,主要用于操作字节
1 | //Buffer声明实例 |
构建 TCP 服务
通过 net.createServer()
构建 TCP 服务器
TCP 服务的事件分为服务器事件与连接事件
服务器事件:
- listening:在调用 server.listen()绑定端口或者 Domain Socket 后触发,简洁写法为
server.listen(port,listeningListener)
,通过第二个参数传入 - connection:每个客户端套接字连接到服务器端时触发,简洁写法为通过 net.createServer(),最后一个参数传递
- close:服务器关闭时触发,调用 server.close()后,服务器将停止接收新的套接字连接,但保持当前连接,所有连接断开后,会触发该事件
- error:当服务器发生异常,将会触发
连接事件:
- data:当一端调用 write()方法发送数据时,另一端会触发 data 事件,事件传递的参数即是 write()发送的数据
- end:当连接中任意一端发送了 FIN 数据时,会触发该事件
- connect:当套接字与服务器连接成功时会被触发
- drain:当任意一端用 write()发送数据时,当前这端会触发该事件
- error:当异常发生时,触发
- close:套接字完全关闭时,触发
- timeout:当一段时间后,时间不再活跃,触发
构建 UDP 套接字
通过 dgram.createSocket()
创建 UDP 套接字
UDP 不是面向连接的
TCP 中连接一旦建立,所有的会话都基于连接完成,客户如果要与另一个 TCP 服务通信,需要令创建一个套接字
而在 UDP 中,一个套接字可以与多个 UDP 服务通信
想要让 UDP 套接字接收网络信息,只要调用 dgram.bind(port,[address])
方法对网卡和端口进行绑定即可
当套接字对象 socket 用在客户端时,可以调用send()
方法发送信息到网络中:socket.send(buf,offset,length,port,address,[callback])
参数分别为:发送的 Buffer,Buffer 的偏移,Buffer 的长度,目标端口,目标地址,发送完成后的回调
UDP 套接字事件:
- message:当 UDP 套接字侦听网卡端口后,接收到信息时触发事件,触发携带的数据为消息 Buffer 对象和一个远程地址信息
- listening:当 UDP 套接字开始侦听时触发
- close:调用 close()方法时触发,并且不再触发 message 事件
- error:发生异常时触发
构建 HTTP 服务
通过 http.createServer(function(req,res){})
构建 http 服务
对 TCP 连接的读操作,http 模块将其封装为 ServerRequest 对象,对应 req 的具体操作,响应对象则对应 res
HTTP 服务端事件:
- connection:在开始 http 请求和响应前,客户端与服务器需要建立底层的 TCP 连接,这个连接开启了 keep-alive,服务器触发 connection 事件
- request:建立 TCP 连接后,http 模块底层会从数据流中抽象出 http 请求和 http 响应,当请求数据发送到服务端,解析出 http 请求头后,会触发该事件
- close:与 TCP 服务器行为一致
- checkContinue:当客户端发送较大数据,先发送一个特殊头部带,服务器解析出后,触发该事件
- connect:客户端发送 CONNECT 请求时触发
- upgrade:当客户端要求升级协议,在请求头带上 Upgrade 字段,服务器端接收到后触发该事件
- clientError:连接的客户端触发 error 事件时,错误会传递到服务器端,此时触发该事件
HTTP 客户端事件:
- response:与服务器端的 request 事件对应的客户端在请求发出后得到服务器端响应时,触发 response 事件
- socket:底层连接池中建立的连接分配给当前请求对象时,触发 socket 事件
- connect:客户端向服务器端发送 CONNECT 请求时,如果服务器端响应了 200 状态码,客户端会触发 connect 事件
- upgrade:客户端向服务器端发送了 Upgrade 请求时,如果服务器端响应了正确状态,客户端会触发该事件
- continue:客户端企图发送大数据量,发送了头信息,服务器端响应正确状态时,客户端会触发该事件
构建 WebSocket 服务
new WebSocket('')
使用 WebSocket,客户端只需一个 TCP 连接就可完成双向通信,在频繁通信时,无须频繁断开连接和重发请求
两端只建立一个 TCP 连接,并且服务器端可以推送数据到客户端,远比 http 模式灵活高效
建立 WebSocket,需要两个部分:握手和数据连接
握手时,需要请求服务器端升级协议为 WebSocket,请求头部添加:
- Upgrade:websocket
- Connection:Upgrade
结语
本文只大致描述 node 基础知识,若有帮助,可以打个赏哦亲~~