11 KiB
Node.js学习
nodejs核心api
核心api:
Buffer
JavaScript 语言没有用于读取或操作二进制数据流的机制。 Buffer
类是作为 Node.js API 的一部分引入的,用于在 TCP 流、文件系统操作、以及其他上下文中与八位字节流进行交互。(js无法给数组固定长度进行操作,nodejs中引入Buffer这个api方便操作数组)
dgram
dgram
模块提供了 UDP 数据包 socket 的实现。
Events事件触发器
Node.js 核心 API 构建于惯用的异步事件驱动架构,其中某些类型的对象(又称触发器,Emitter)会触发命名事件来调用函数(又称监听器,Listener)。
例如,net.Server
会在每次有新连接时触发事件,fs.ReadStream
会在打开文件时触发事件,stream会在数据可读时触发事件。
所有能触发事件的对象都是 EventEmitter
类的实例。 这些对象有一个 eventEmitter.on()
函数,用于将一个或多个函数绑定到命名事件上。 事件的命名通常是驼峰式的字符串,但也可以使用任何有效的 JavaScript 属性键。。
当 EventEmitter
对象触发一个事件时,所有绑定在该事件上的函数都会被同步地调用。 被调用的监听器返回的任何值都将会被忽略并丢弃。
例子,一个简单的 EventEmitter
实例,绑定了一个监听器。 eventEmitter.on()
用于注册监听器, eventEmitter.emit()
用于触发事件。
const EventEmitter = require('events');
class MyEmitter extends EventEmitter {}
const myEmitter = new MyEmitter();
myEmitter.on('event', () => {
console.log('触发事件');
});
myEmitter.emit('event');
eventEmitter.emit()
方法可以传任意数量的参数到监听器函数。 当监听器函数被调用时, this
关键词会被指向监听器所绑定的 EventEmitter
实例。
fs模块
在 NodeJS 中,所有与文件操作都是通过 fs
核心模块来实现的,包括文件目录的创建、删除、查询以及文件的读取和写入,在 fs
模块中,所有的方法都分为同步和异步两种实现,具有 sync
后缀的方法为同步方法,不具有 sync
后缀的方法为异步方法,文件的前置知识,如文件的权限位 mode
、标识位 flag
、文件描述符 fd
等。
因为 fs
模块需要对文件进行操作,会涉及到操作权限的问题,所以需要先清楚文件权限是什么,都有哪些权限。
http模块
var http = require('http');
http.createServer(function (req, res) {
res.writeHead(200, { 'Content-Type': 'text/html' });
res.write('<h1>Node.js</h1>');
res.end('<p>Hello World</p>');
}).listen(3000);
console.log("HTTP server is listening at port 3000.");
http2模块
const http2 = require('http2');
const fs = require('fs');
const server = http2.createSecureServer({
key: fs.readFileSync('rsa_private.key'),
cert: fs.readFileSync('cert.crt')
});
server.on('error', (err) => console.error(err));
server.on('stream', (stream, headers) => {
// stream is a Duplex
stream.respond({
'content-type': 'text/html',
':status': 200
});
stream.end('<h1>Hello World</h1>');
});
server.listen(8443);
Nodejs网络通信
构建TCP服务
新建server.js文件,创建server服务
const net = require('net')
// 使用net的createServer方法创建服务
const server = net.createServer()
// 声明一个数组储存多个客户端socket
const clients = []
// 使用服务的on方法监听服务器信息
server.on('connection', clientSocket的on方法监听传入的数据(二进制) => {
// 获取多个客户的存入clients
clients.push(clientSocket)
// 使用clientSocket的on方法监听传入的数据(二进制)
clientSocket.on('data', data => {
console.log(data.toString());
// 给多个客户端发送数据除发送者外
clients.forEach(socket => {
if(socket !== clientSocket) {
socket.write(data)
}
})
})
// 向客户端发送数据
clientSocket.write('hello')
})
server.on('error', (err) => {
console.error('Server error:', err);
});
// 把服务启用在3000端口上
server.listen(3000, () => {
console.log('Server running');
})
新建client.js文件,创建客户端
const net = require('net')
// 创建TCP连接到指定的主机端口上
const client = net.createConnection({
host:'127.0.0.1',
port: 3000
})
// 监听TCP与服务端的连接
client.on('connect', () => {
console.log('成功连接服务器!!');
//客户端发消息给服务端
process.stdin.on('data', data => {
// console.log(data.toString());
client.write(data.toString().trim());
})
// client.write('Hi Lilei!!');
})
// 获取服务端的数据
client.on('data', (data) => {
console.log(data.toString());
})
构建UDP服务
UDP和TCP一样是在网络传输层处理数据包的,最大特点不需要连接、传输速度快、数据传输不可靠。
server.js
const dgram = require('dgram')
const server = dgram.createSocket('udp4')
server.on('listening', () =>{
const address = server.address()
console.log(`server running ${address.address}:${address.port}`);
})
server.on('message', (msg, remoteInfo) =>{
console.log(`server got ${msg} form ${remoteInfo.address}:${remoteInfo.port}`);
})
server.on('error', (err) =>{
console.log(`server error`, err);
})
server.bind(3000)
clent.js:
const dgram = require('dgram')
const client = dgram.createSocket('udp4')
client.send('hello', 3000, 'localhost')
server.on('listening', () =>{
const address = server.address()
console.log(`client running ${address.address}:${address.port}`);
})
server.on('message', (msg, remoteInfo) =>{
console.log(`client got ${msg} form ${remoteInfo.address}:${remoteInfo.port}`);
})
server.on('error', (err) =>{
console.log(`client error`, err);
})
// server.bind(8000)
构建HTTP服务
基本的HTTP服务
const http = require('http')
const hostname = '127.0.0.1'
const port = 3000
const server = http.createServer((req, res) => {
res.statusCode = 200,
res.setHeader('Content-Type', 'text/plan')
res.end('hello world\n')
})
server.listen(port, hostname, () => {
console.log(`sever running at http://${hostname}:${port}/`);
})
根据URL解析内容
const http = require('http')
const hostname = '127.0.0.1'
const port = 3000
const server = http.createServer((req, res) => {
// 根据url 处理响应
const url = req.url
if(url === '/') {
res.end('hello world\n')
} else if(url === '/a') {
res.end('hello a\n')
} else if(url === '/b') {
res.end('hello nb\n')
} else{
res.statusCode = 404
res.end('404 not found')
}
})
server.listen(port, hostname, () => {
console.log(`sever running at http://${hostname}:${port}/`);
})
构建HTTPS服务
https原理及安装本地证书
https协议 = http协议 + SSL/TLS协议
构建方式就是和http一样就是多了一个ca安全证书
Nodejs事件循环和多进程
事件循环的概念:在浏览器或者nodejs环境中,运行时对js脚本的调度方式就叫做事件循环
浏览器事件循环
因为浏览器js的作用是操作DOM,这决定了js只能是单线程,否则会带来很复杂的同步问题。比如,假定JavaScript同时有两个线程,一个线程在某个DOM节点上添加内容,另一个线程删除了这个节点,这时浏览器应该以哪个线程为准?出现冲突
任务队列:单线程就意味着所有任务需要排队,如果因为任务cpu计算量大还好,但是I/O操作cpu是闲着的。所以js就设计成了一门异步的语言,不会做无畏的等待。任务可以分成两种,一种是同步任务(synchronous),另一种是异步任务(asynchronous)。
(1)所有同步任务都在主线程上执行,形成一个执行栈(execution context stack)。
(2)主线程之外,还存在一个"任务队列"(task queue)。只要异步任务有了运行结果,就在"任务队列"之中放置一个事件。
(3)一旦"执行栈"中的所有同步任务执行完毕,系统就会读取"任务队列",看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。
(4)主线程不断重复上面的第三步。

宏任务与微任务:JavaScript 单线程中的任务可以细分为宏任务(macrotask)和微任务(microtask)
-
macrotask: script(整体代码), setTimeout, setInterval, setImmediate, I/O, UI rendering。
-
microtask:process.nextTick, Promise, Object.observe, MutationObserver。
- 宏任务进入主线程,执行过程中会收集微任务加入微任务队列。
- 宏任务执行完成之后,立马执行微任务中的任务。微任务执行过程中将再次收集宏任务,并加入宏任务队列。
- 反复执行1,2步骤
每轮事件循环执行一个宏任务和所有的微任务,而且任务队列一定会保持先进先出的顺序执行。
nodejs事件循环
当Node.js启动时会初始化event loop, 每一个event loop都会包含按如下六个循环阶段,nodejs事件循环和浏览器的事件循环完全不一样。
-
timers(定时器) : 此阶段执行那些由
setTimeout()
和setInterval()
调度的回调函数. -
I/O callbacks(I/O回调) : 此阶段会执行几乎所有的回调函数, 除了 close callbacks(关闭回调) 和 那些由 timers 与
setImmediate()
调度的回调.setImmediate 约等于 setTimeout(cb,0)
-
idle(空转), prepare : 此阶段只在内部使用
-
poll(轮询) : 检索新的I/O事件; 在恰当的时候Node会阻塞在这个阶段
-
check(检查) :
setImmediate()
设置的回调会在此阶段被调用 -
close callbacks(关闭事件的回调): 诸如
socket.on('close', ...)
此类的回调在此阶段被调用
在事件循环的每次运行之间, Node.js会检查它是否在等待异步I/O或定时器, 如果没有的话就会自动关闭.
如果event loop进入了 poll阶段,且代码未设定timer,将会发生下面情况:
- 如果poll queue不为空,event loop将同步的执行queue里的callback,直至queue为空,或执行的callback到达系统上限;
- 如果poll queue为空,将会发生下面情况:
- 如果代码已经被setImmediate()设定了callback, event loop将结束poll阶段进入check阶段,并执行check阶段的queue (check阶段的queue是 setImmediate设定的)
- 如果代码没有设定setImmediate(callback),event loop将阻塞在该阶段等待callbacks加入poll queue,一旦到达就立即执行
如果event loop进入了 poll阶段,且代码设定了timer:
- 如果poll queue进入空状态时(即poll 阶段为空闲状态),event loop将检查timers,如果有1个或多个timers时间时间已经到达,event loop将按循环顺序进入 timers 阶段,并执行timer queue.