基于Node的网络编程
- Node是面向网络而生的平台
- Node为事件驱动、无阻塞、单线程
- Node的API十分贴合网络,适合构建灵活的网络服务
- Node可以非常方便的搭建网络服务器,传统的 web 平台大都需要专门的 web 服务器作为容器,例如ASP需要IIS作为服务器、PHP需要搭载 Apache 或 Nginx 环境等
- Node提供了 net、dgram、http、https 4个模块,分别处理TCP、UDP、HTTP、HTTPS 适用于服务器端和客户端
网络模型
网络七层模型
为了便于学习和理解这里用五层网络模型来表述
网络五层模型
层与协议
网络模型的每一层都是为了完成一种功能,为了实现这些功能,就需要大家遵守公共的规则,大家遵守的规则,就叫做 协议
实体层
计算机之间要实现组网通信,首先要通过光缆、电缆、无线电波等物理方式连接起来。这种将计算机通过物理手段连接的方式就称为 “实体层”,它的作用只负责传输 0 和 1 的电信号
链接层
单纯的 0 和 1 没有任何意义,必须要规定解读方式,多少个电信号为一组?每个信号位有什么意义?
这就是 “链接层” 的功能,它在 “实体层” 的上层规定了电信号0和1的分组方式。
以太网协议
早期,每家公司都有自己的电信号分组方式。后来 以太网协议 逐渐占据了主导地位。
以太网协议 规定了一组电信号构成一个数据包,叫做 “帧”,每一帧分为 标头(Head) 和 数据(data) 两部分
标头:包含了数据的说明项,比如数据的发送者、接收者、数据类型等,“数据” 则是数据包的具体内容
MAC地址
标头中包含了接收者和发送者,那么两者的信息是怎么标识的。
以太网协议 规定连入网络的计算机必须有网卡接口,数据包从一个网卡传输到另一个网卡。网卡的 MAC地址 就是用来标识通信双方的地址信息,通过它可以定位网卡和数据包的路径。
每块网卡在出厂时,就会有一个在世界上独一无二的MAC地址,它是一个长度为48位的二进制数,通常同12个十六进制数表示
MAC地址前6个十六位制数为厂商编号,后6个为该厂商的网卡流水号
广播
在以太网协议中,通信使用一种 “原始” 的方式,系统会将数据包发送到本子网络中所有计算机,让每台计算机自己判断是否为数据接收方。
例如同一个子网络中有五台计算机,1号计算机想给3号计算机发送数据包,所有计算机都会接收到这个包,它们会读取包的 标头,找到接收方的 MAC 地址跟自己 MAC 地址做对比,如果一致就进一步处理否则就丢弃,这个过程称之为 广播
有了数据包的定义、网卡的MAC地址、广播发送方式,链接层可以在计算机之间传输数据了。
网络层
通过广播的方式传输数据包不仅效率低,局限性很大。通信双方不在同一子网络中,无法通过广播的方式传递数据包。所以我们需要区分哪些 MAC 地址属于同一子网络。
网络层通过一套新的地址(网络IP地址)来区分不同的计算机是否属于同一子网络。这样每台计算机就有两个地址 Mac地址 和 网络地址。
这两个地址没有任何联系MAC地址 是绑定到网卡上的,网络地址 是网络管理员分配的,它们只是随机的组合在一起。网络地址可以确定数据包接收方所在的子网络,MAC地址 可以将数据包传送给子网络中的目标网卡
IP协议
规定网络地址的协议称之为 IP协议 ,它所表示的地址成为 IP地址
目前,广泛采用的是IP协议第四版(IPv4),这个版本规定网络地址由 32个二级制位 组成
我们通常用四段十进制数表示IP地址,即 0.0.0.0 到 255.255.255.255
IP协议主要有两个作用
- 为每一台计算机分配IP地址
- 确定哪些地址在一个子网络中
传输层
计算机在 网络层 完成双方的通信,再往上进入 传输层
端口
计算机上很多程序都需要用到数据包,当从互联网上获取一个数据包该怎么确定给哪个程序使用,这时候就需要用到 端口。端口 就是网卡的程序的编号,每个数据包发到主机特定的 端口,所以不同数据能取到自己需要的数据。
端口 是0到65535的一个整数,正好为16个二进制位。0到1023的端口被系统所占用,用户只能选用大于1023的端口,不管浏览网页还是聊天,应用程序会随机选用一个端口,与服务器响应端口联系
传输层 的功能建立了 端口到端口 的通信,这种客户端和服务器端之间 端口与端口 的通信方式也叫做 套接字通信(Socket)。相比之下,网络层 的功能则建立了 主机到主机的通信,在确定 主机 和 端口 后就可以实现程序之间的交流。
UDP协议
在数据包中加入端口信息,就得规范一个新协议,最简单的实现就是 UDP协议。
UDP数据包也是由 “标头” 和 “数据” 组成,UDP数据包 的 “标头” 定义了接收和传送的端口,“数据” 部分包含了具体数据。而整个 UDP数据包 被放置在 IP数据包 的 数据 部分,如果从 以太网数据包 算起,整个数据包的结构关系大概如下
以太网数据包 包含了 以太网数据标头 + 以太网数据
以太网数据 包含了 IP数据标头 + IP数据
IP数据 包含了 UDP数据标头 + UDP数据
UDP 的 标头 部分一共只有8个字节,总长度不超过65535字节,可以正好放进一个 IP数据包 中。
UDP协议 本身不太可靠,通信双方在传输数据时,可能会造成数据丢失,双方进行会话时,无法确保对方是否成功接收到数据,无法保证数据的完整一致性。
TCP协议
UDP协议作为一种非面向连接的协议,具有不可靠性,TCP 的出现就是为了解决这个问题。
TCP协议 是一种面向连接、可靠的、基于字节流的传输层通信协议。TCP协议 在发送数据前要通过三次握手建立连接,当因为某些原因数据发送失败时,会重新建立连接。当数据发送完成后,还需要断开连接减少系统资源的占用。
TCP协议与UDP协议的区别
特性/协议 | TCP | UDP |
---|---|---|
是否面向连接 | 面向连接 | 非面向连接 |
传输可靠性 | 可靠 | 不可靠 |
使用场景 | 少量数据 | 大量数据 |
速度 | 慢 | 快 |
总的来说 TCP协议是面向连接的,它传输数据可靠性高,但是占用系统资源高传输效率低。如果对数据完整性、数据正确顺序有要求应该使用 TCP协议,例如,浏览器中的数据、网络邮箱的数据。UDP协议协议虽然不太可靠、容易丢包,但是传输效率极高,一些大量数据流场景常用到它,例如,语音通话、视频通话、直播等。
应用层
无论是 TCP协议 还是 UDP协议 ,相比较而言都算是偏底层的协议。我们常接触到的基本在应用层中。
传输层 实现数据的传输和接收,由于互联网是开放架构,数据来源也是五花八门,我们需要制定规范解读数据,实现双方的数据通讯。
应用层 的作用就是规定不同应用程序的数据格式
TCP协议 可以为各种应用传输数据,比如 Email、WWW、FTP,这些应用也要根据不同协议规定数据的规范,这些应用的协议就构成 应用层。
应用层 是最高的一层,它是直接面向用户的,他的数据放在 TCP 的数据包部分 即如下:
搭建TCP服务
通信双方建立TCP连接时,需要进行三次握手。三次握手成功确保TCP成功建立连接后,开始进行通信。
TCP三次握手
客户端 首先向 服务器端 发送数据,进行 第一次握手。服务器端 接收到数据后向 客户端 作出响应,进行 第二次握手 ,客户端 接收到响应后,会进行 第三次握手 ,对 服务器端 再次发送数据告知 服务器端 可以接收到数据。
客户端 和 服务器端 经过三次握手后,TCP连接建立成功接下来就可以传输数据了。
net模块
Node 的 net 模块可以搭建 TCP 服务
// 服务器端
const Net = require('net')
const app = Net.createServer()
// 当客户端建立连接成功
app.on('connection', clientSocket => {
console.log('建立连接成功');
// 向单当前连接的客户端发送数据
clientSocket.write('hello')
})
// 将服务放到3000端口上启动
app.listen(3000, function () {
console.log('TCP服务运行');
})
// 客户端
const Net = require('net')
// 客户端创建连接
const client = Net.createConnection({
// 服务器的域名、端口号
host: '127.0.0.1',
port: '3000'
})
// 连接成功时
client.on('connect', () => {
console.log('成功连接到服务器');
})
// 服务器端响应数据时
client.on('data', data => {
console.log('服务器返回数据', data.toString());
})
复制代码
客户端和服务器端实现双向通信
// 服务器端
// 服务器端在建立连接成功 监听客户端发送得数据
// 当客户端建立连接成功
app.on('connection', clientSocket => {
console.log('建立连接成功');
// 向当前连接的客户端发送数据
clientSocket.write('hello')
// 监听客户端发送得数据
clientSocket.on('data', (data) => {
console.log('客户端传输数据:', data.toString());
})
})
// 客户端
// 连接成功时
client.on('connect', () => {
console.log('成功连接到服务器');
// 向服务器端发送数据
client.write('Hello,我是客户端')
})
复制代码
客户端将终端信息发送到服务器端
这里实现一个小功能,将客户端终端输入得数据发送到服务器端。
// 客户端
client.on('connect', () => {
console.log('成功连接到服务器');
client.write('Hello,我是客户端')
// 建立连接后,获取终端输入内容并传输给服务器端
process.stdin.on('data', data => {
client.write(data)
})
})
复制代码
搭建UDP服务
UDP
UDP 全称为 User Datagram Protocol,又称为用户数据包协议
- UDP 与 TCP 相同,都是位于 网络传输层 用于传输数据包
- 无连接、传输速度快、数据传输不可靠,不提供数据包分组,无法对数据包进行排序,数据是无序的,无法保证传输时数据的完整性、正确性。
- UDP 支持一对一通信,也支持一对多通信。
- UDP 适合对传输速度要求高,对数据传输质量要求不严谨的应用。例如,流媒体、多人游戏、实时音视频等。
UDP传播方式
UDP 传播数据有三种方式,分别为:单播、广播(多播)、组播
- 单播
单播 指的是 UDP 以点对点,单一目标的一种传播方式。地址范围为0.0.0.0 - 223.255.255.255 - 广播
UDP 还支持 广播 的形式传输数据,广播 目标地址为当前局域网内的所有设备
地址范围分为 受限广播 和 直接广播- 受限广播 只在当前路由或交换机中广播,不会转发到其他路由中。受限广播的目标IP地址的网络字段和主机字段全为1,即地址为255.255.255.255
- 直接广播 会被路由转发,IP地址网络字段会定义这个地址,主机字段则为1,例如192.168.10.255
- 组播
广播 和 组播 都是一对多传输数据,组播 是将同样的数据传输给一组目标主机,广播 是将数据传输给局域网中的所有主机,组播 则是将多个组件组成一组并发送数据。
不同传播在 “一对多” 场景的应用
- 单播 实现 “一对多”
服务器在UDP协议中,以 单播 方式向多个主机传输数据。需要发送多个 单播 数据包,发送次数与接收者数目相同,且每个数据包中都有确切目标主机的IP地址。如果接受者成百上千,就会加大服务器的负担。 - 广播 实现 “一对多”
广播 被限制在局域网中,一旦有设备发送广播数据,则广播域内所有设备都会收到数据,并耗费资源去处理。大量广播数据包会消耗网络带宽和设备资源
在IPV6,广播报文传输方式被取消 - 组播 实现 “一对多” 组播 非常适合“一对多的”传输场景, 只有加入特定 组播组 的主机才能接收到组播数据。传输组播数据包时,无需发送多个仅发送一个就可以,组播设备会根据情况进行转发或拷贝组播数据。
相同的组播报文,在同链路上只有一份报文,这样大提高了网络资源利用率。
dgram 模块
在Node中,使用dgram模块搭建UDP服务
使用dgram来创建套接字,套接字既可以作为客户端接收数据,也可以作为服务器端发送数据
const dgram = require('dgram')
const socket = dgram.createSocket('udp4')
复制代码
socket的方法
API | 说明 |
---|---|
bind() | 绑定端口和主机 |
address() | 返回Socket地址对象 |
close() | 关闭Socket并停止监听 |
send() | 发送信息 |
addMembership() | 添加组播成员 |
dropMembership() | 移除组播成员 |
setBroadcast() | 设置是否启动广播 |
setTTL() | 设置数据报存活时间 |
setMulticastTTL() | 设置组播数据报存活时间 |
socket事件
事件名 | 触发时机 |
---|---|
listening | 监听成功时触发,仅触发一次 |
message | 接收到消息时触发 |
error | 发生错误时触发 |
close | 关闭socket时触发 |
UDP单播实现
服务器端
const dgram = require('dgram')
const server = dgram.createSocket('udp4')
// 绑定端口号成功时触发
server.on('listening', () => {
const address = server.address()
console.log('服务器运行在:' + address.address + ':' + address.port);
})
// 接收到消息时触发
server.on('message', (msg, remoteInfo) => {
console.log(`服务器接收到来自:${remoteInfo.address}:${remoteInfo.port}的信息:${msg}`);
// 向客户端发送信息
server.send('客户端你好,我是服务器端', remoteInfo.port, remoteInfo.address);
})
// 发生错误时触发
server.on('error', err => {
console.log(`发生错误了,${JSON.stringify(err)}`);
})
// 将服务绑定到某个端口上运行
server.bind(3000)
复制代码
客户端
const dgram = require('dgram')
const client = dgram.createSocket('udp4')
// 向localhost:3000 发送信息
client.send('hello world',3000,'localhost')
// 绑定端口号成功时触发
client.on('listening',()=>{
const address = client.address()
console.log('客户端运行在:'+address.address+':'+address.port);
})
// 接收到消息时触发
client.on('message',(msg,remoteInfo)=>{
console.log(`客户端接收到来自:${remoteInfo.address}:${remoteInfo.port}的信息:${msg}`);
})
// 发生错误时触发
client.on('error',err=>{
console.log(`发生错误了,${JSON.stringify(err)}`);
})
复制代码
UDP广播实现
客户端
const dgram = require('dgram')
const client = dgram.createSocket('udp4')
client.on('listening', () => {
const address = client.address()
console.log('客户端运行在 ' + address.address + ':' + address.port);
})
client.on('message', (msg, remoteInfo) => {
console.log(`客户端接收到来自 ${remoteInfo.address}:${remoteInfo.port}的信息:${msg}`);
})
client.on('error', err => {
console.log(`发生错误了,${JSON.stringify(err)}`);
})
// 客户端绑定在8000端口上运行
client.bind(8000)
复制代码
服务器端
const dgram = require('dgram')
const server = dgram.createSocket('udp4')
// 广播次数
let sendTime = 1
server.on('listening', () => {
const address = server.address()
console.log('服务器运行在 ' + address.address + ':' + address.port);
// 服务运行成功后启用广播传输方式
server.setBroadcast(true)
// 关闭广播
// server.setBroadcast(false)
// 每隔两秒广播一次数据
setInterval(() => {
server.send('客户端你好,我是服务器端广播数据 *' + sendTime++, 8000, '255.255.255.255');
}, 2000)
})
server.on('message', (msg, remoteInfo) => {
console.log(`服务器接收到来自 ${remoteInfo.address}:${remoteInfo.port}的信息:${msg}`);
server.send('客户端你好,我是服务器端', remoteInfo.port, remoteInfo.address);
})
server.on('error', err => {
console.log(`发生错误了,${JSON.stringify(err)}`);
})
// 将服务绑定到某个端口上运行
server.bind(3000)
复制代码
UDP组播实现
客户端
const dgram = require('dgram')
const client = dgram.createSocket('udp4')
client.on('listening', () => {
const address = client.address()
console.log('客户端运行在 ' + address.address + ':' + address.port);
// 当前客户端添加到组播组中
client.addMembership('224.0.1.100')
})
client.on('message', (msg, remoteInfo) => {
console.log(`客户端接收到来自 ${remoteInfo.address}:${remoteInfo.port}的信息:${msg}`);
})
client.on('error', err => {
console.log(`发生错误了,${JSON.stringify(err)}`);
})
// 客户端绑定在8000端口上运行
client.bind(8000)
复制代码
服务器端
const dgram = require('dgram')
const server = dgram.createSocket('udp4')
// 组播次数
let sendTime = 1
server.on('listening', () => {
const address = server.address()
console.log('服务器运行在 ' + address.address + ':' + address.port);
// 每隔两秒组播一次数据
setInterval(() => {
server.send('客户端你好,我是服务器端组播数据 *' + sendTime++, 8000, '224.0.1.100');
}, 2000)
})
server.on('message', (msg, remoteInfo) => {
console.log(`服务器接收到来自 ${remoteInfo.address}:${remoteInfo.port}的信息:${msg}`);
server.send('客户端你好,我是服务器端', remoteInfo.port, remoteInfo.address);
})
server.on('error', err => {
console.log(`发生错误了,${JSON.stringify(err)}`);
})
// 将服务绑定到某个端口上运行
server.bind(3000)
复制代码
搭建HTTP服务
TCP 和 UDP 是网络传输层的协议,它们可以构建高效的网络应用,但是对于经典的浏览器和服务器通信场景使用传输层协议会很麻烦。
对于上述场景,基于传输层之上制定了更上一层的通信协议:HTTP,由于 HTTP 协议本身并不不考虑数据如何传输和其他细节问题,所以他属于网络应用层。
Node 提供 http 和 https 模块,用于 HTTP 和 HTTPS 的封装
http模块
const HTTP = require('http')
const server = HTTP.createServer()
复制代码
server方法
方法 | 说明 |
---|---|
close | 关闭服务 |
listening | 获取服务状态 |
server事件
方法 | 说明 |
---|---|
close | 关闭服务 |
require | 获取服务状态 |
请求对象request
属性 | 说明 |
---|---|
method | 请求方式 |
url | 请求地址 |
headers | 请求头 |
httpVersion | 请求HTTP协议版本 |
响应对象response
API | 说明 |
---|---|
end() | 结束响应 |
setHeader(name,value) | 设置响应头 |
removeHeader(name,value) | 删除响应头 |
statusCode | 设置响应状态码 |
write | 写入响应数据 |
writeHead | 写入响应头 |
创建基本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/plain')
// 结束响应,返回数据
res.end('Hello World\n')
})
// 服务运行成功
server.listen(port, hostName, () => {
console.log(`服务运行在:${hostName}:${port}`)
})
复制代码
获取url
const http = require('http')
const port = 3000
const hostName = '127.0.0.1'
const server = http.createServer((req, res) => {
// 获取url
const url = req.url
if (url === '/') {
res.end('Hello Home')
} else if (url === '/a') {
res.end('Hello A')
} else if (url === '/b') {
res.end('Hello B')
} else {
// 访问接口成功,响应状态码默认为200, 这里要手动设置为404
res.statusCode = 404
res.end('404 Not Found')
}
})
server.listen(port, hostName, () => {
console.log('服务运行成功');
})
复制代码
响应HTML内容
http 响应的内容默认为文本格式的,可以通过设置响应头响应 HTML 内容
const http = require('http')
const port = 3000
const hostName = '127.0.0.1'
const server = http.createServer((req, res) => {
// 设置响应头,返回内容为HTML格式,为了避免中文乱码 编码采用UTF-8
res.setHeader('Content-Type', 'text/html;charset=utf-8')
res.end('<h1>Hello World></h1><div>你好,世界</div>')
})
server.listen(port, hostName, () => {
console.log('服务运行成功');
})
复制代码
处理项目中的静态资源
当后端响应的HTML内容中包含静态资源,这些静态资源也会以请求的形式发送给后端。
- 新建 index.html ,访问根路径时向前端返回 html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<link rel="stylesheet" href="./assets/css/index.css">
</head>
<body>
<h1>Hello World</h1>
<div>你好,世界</div>
<script src="./assets/js/index.js"></script>
</body>
</html>
复制代码
- 新建 assets 文件夹,存放 css、js 文件
/* assets/css/index.css */
h1 {
color: pink;
}
// assets/js/index.js
console.log('assets/index.js 加载了');
复制代码
- 搭建web服务
const http = require('http')
const fs = require('fs')
const path = require('path')
const port = 3000
const hostName = '127.0.0.1'
const server = http.createServer((req, res) => {
// 获取url
const url = req.url
// 访问根路径返回html资源
if (url === '/') {
// 获取html资源的绝对路径
const HTML_Path = path.resolve(__dirname, 'index.html')
fs.readFile(HTML_Path, (err, data) => {
if (err) throw err
// 设置响应状态码
res.statusCOde = 200
// 设置响应内容的文本类型、编码格式
res.setHeader('Content-Type', 'text/html;charset=utf-8;')
// 响应html资源
res.end(data)
})
// Html中的访问css资源
} else if (url === '/assets/css/index.css') {
// 获取css资源的绝对路径
const HTML_Path = path.resolve(__dirname, 'assets/css/index.css')
fs.readFile(HTML_Path, (err, data) => {
if (err) throw err
// 设置响应状态码
res.statusCOde = 200
// 设置响应内容的文本类型、编码格式
res.setHeader('Content-Type', 'text/css;charset=utf-8;')
// 响应html资源
res.end(data)
})
} else if (url === '/assets/js/index.js') {
// 获取js资源的绝对路径
const HTML_Path = path.resolve(__dirname, 'assets/js/index.js')
fs.readFile(HTML_Path, (err, data) => {
if (err) throw err
// 设置响应状态码
res.statusCOde = 200
// 设置响应内容的文本类型、编码格式
res.setHeader('Content-Type', 'text/javascript;charset=utf-8;')
// 响应html资源
res.end(data)
})
}
})
server.listen(port, hostName, () => {
console.log('服务运行成功');
})
复制代码
对资源进行统一处理
在上述操作中,对静态资源进行了处理。但是每种资源单独判断处理增加了很多重复代码,也不利于后期新增其他类型资源的维护工作,这里可以对资源进行统一处理。
- 只能请求的url以 /assets/ 开头是视为在请求资源
- 使用npm包 mime 可以根据文件后缀名,获取对应的http内容格式,例如,.css的资源类型为 text/css
const http = require('http')
const fs = require('fs')
const path = require('path')
const mime = require('mime')
const port = 3000
const hostName = '127.0.0.1'
const server = http.createServer((req, res) => {
// 获取url
const url = req.url
// 访问根路径返回html资源
if (url === '/') {
// 获取html资源的绝对路径
const HTML_Path = path.resolve(__dirname, 'index.html')
fs.readFile(HTML_Path, (err, data) => {
if (err) throw err
// 设置响应状态码
res.statusCOde = 200
// 设置响应内容的文本类型、编码格式
res.setHeader('Content-Type', 'text/html;charset=utf-8;')
// 响应html资源
res.end(data)
})
} else if (url.startsWith('/assets/')) {
// 获取资源路径
const assetsPath = path.resolve(__dirname, url)
fs.readFile(assetsPath, (err, data) => {
if (err) throw err
// 设置响应状态码
res.statusCOde = 200
const type = mime.getType(url)
// 设置响应内容的文本类型、编码格式
res.setHeader('Content-Type', type + ';charset=utf-8;')
})
}
})
server.listen(port, hostName, () => {
console.log('服务运行成功');
})
复制代码
搭建HTTPS服务
HTTP协议非常不安全,它存在以下隐患
- 通信内容被窃听,第三方可以截获并查看通信内容
- 通信内容被篡改,第三方可以截获并修改通信内容
- 通信身份被冒充,第三方可以冒充客户端和服务器端参与通信
HTTPS的出现就是为了解决这些问题,HTTPS是基于TLS/SSL的HTTP协议,在HTTP中数据是以明文形式传输的,而HTTPS是将数据加密后进行传输。所以HTTPS可以说是HTTP的安全版本。
HTTPS协议 = HTTP协议+SSL/TLS协议
在HTTPS数据传输过程中,需要用SSL/TLS对数据进行加密和解密,需要用HTTP对加密后的数据进行传输;TLS/SSL是一对公钥/私钥结构,传输的数据采用对称加密,对数据加密的密钥采用非对称进行加密,在一定程度上保证了数据的安全。
但是如此还是无法解决,第三方冒充身份参与通信的问题,例如,正常是客户端与服务器直接通信,如果第三方介入的话,他可以伪装成服务器截取密钥对,对数据造成泄漏。
这个解决有第三方冒充身份参与通信的问题,TLS/SSL引入了数字证书来认证,数字证书是指在网络通信各方身份信息的一个数字认证,人们可以在网上用它来识别对方的身份。他与直接使用公钥不同,数字证书包含了服务器的名称和主机名、服务器的公钥、签名颁发机构的名称、来自签名颁发机构的签名,连接建立前,会通过证书中的签名确认收到的公钥是否是来自目标服务器的,而非冒充身份的服务器。
数字证书是由第三方数字证书授权机构(简称CA机构)所颁发的,CA机构也有很多代理商,例如阿里腾讯等。
模拟CA机构,生成本地证书
数字证书去可以在阿里、腾讯那里申请,大部分是收费的而且申请数字证书的服务器有域名等要求,这里只模拟CA机构,生成本地证书
// 执行以下代码生成 本地证书
openssl req -newkey rsa:2048 -nodes -keyout rsa_private.key -x509 -days 365 -out cert.crt
复制代码
证书、私钥和公钥都是通过 openssl 生成的,使用 openssl 需要先安装配置环境,详情请移步 ❤ NodeJSの进阶【1】— 核心API
搭建https服务器
const https = require('https');
const fs = require('fs');
const options = {
// 绑定公钥、私钥
key: fs.readFileSync('test/fixtures/keys/agent2-key.pem'),
cert:fs.readFileSync('test/fixtures/keys/agent2-cert.pem') };
https.createServer(options, (req, res) => {
res.writeHead(200); res.end('hello world\n');
}).listen(8000);
复制代码