❤ NodeJSの进阶【2】— 构建TCP、UDP、HTTP、HTTPS服务

基于Node的网络编程

  • Node是面向网络而生的平台
  • Node为事件驱动、无阻塞、单线程
  • Node的API十分贴合网络,适合构建灵活的网络服务
  • Node可以非常方便的搭建网络服务器,传统的 web 平台大都需要专门的 web 服务器作为容器,例如ASP需要IIS作为服务器、PHP需要搭载 ApacheNginx 环境等
  • Node提供了 netdgramhttphttps 4个模块,分别处理TCPUDPHTTPHTTPS 适用于服务器端和客户端

网络模型

网络七层模型

705728-20160424234824085-667046040.png

为了便于学习和理解这里用五层网络模型来表述

网络五层模型

src=http___www.pianshen.com_images_853_188eed3fde622e2499667f824c43237d.png&refer=http___www.pianshen.jfif

层与协议

网络模型的每一层都是为了完成一种功能,为了实现这些功能,就需要大家遵守公共的规则,大家遵守的规则,就叫做 协议

实体层

计算机之间要实现组网通信,首先要通过光缆、电缆、无线电波等物理方式连接起来。这种将计算机通过物理手段连接的方式就称为 “实体层”,它的作用只负责传输 01 的电信号

链接层

单纯的 01 没有任何意义,必须要规定解读方式,多少个电信号为一组?每个信号位有什么意义?
这就是 “链接层” 的功能,它在 “实体层” 的上层规定了电信号01的分组方式。

以太网协议

早期,每家公司都有自己的电信号分组方式。后来 以太网协议 逐渐占据了主导地位。
以太网协议 规定了一组电信号构成一个数据包,叫做 “帧”,每一帧分为 标头(Head)数据(data) 两部分

image.png
标头:包含了数据的说明项,比如数据的发送者、接收者、数据类型等,“数据” 则是数据包的具体内容

MAC地址

标头中包含了接收者和发送者,那么两者的信息是怎么标识的。
以太网协议 规定连入网络的计算机必须有网卡接口,数据包从一个网卡传输到另一个网卡。网卡的 MAC地址 就是用来标识通信双方的地址信息,通过它可以定位网卡和数据包的路径。

每块网卡在出厂时,就会有一个在世界上独一无二的MAC地址,它是一个长度为48位的二进制数,通常同12个十六进制数表示

image.png
MAC地址前6个十六位制数为厂商编号,后6个为该厂商的网卡流水号

广播

在以太网协议中,通信使用一种 “原始” 的方式,系统会将数据包发送到本子网络中所有计算机,让每台计算机自己判断是否为数据接收方。
例如同一个子网络中有五台计算机,1号计算机想给3号计算机发送数据包,所有计算机都会接收到这个包,它们会读取包的 标头,找到接收方的 MAC 地址跟自己 MAC 地址做对比,如果一致就进一步处理否则就丢弃,这个过程称之为 广播
有了数据包的定义、网卡的MAC地址、广播发送方式,链接层可以在计算机之间传输数据了。

网络层

通过广播的方式传输数据包不仅效率低,局限性很大。通信双方不在同一子网络中,无法通过广播的方式传递数据包。所以我们需要区分哪些 MAC 地址属于同一子网络。

网络层通过一套新的地址(网络IP地址)来区分不同的计算机是否属于同一子网络。这样每台计算机就有两个地址 Mac地址网络地址
这两个地址没有任何联系MAC地址 是绑定到网卡上的,网络地址 是网络管理员分配的,它们只是随机的组合在一起。网络地址可以确定数据包接收方所在的子网络,MAC地址 可以将数据包传送给子网络中的目标网卡

IP协议

规定网络地址的协议称之为 IP协议 ,它所表示的地址成为 IP地址
目前,广泛采用的是IP协议第四版(IPv4),这个版本规定网络地址由 32个二级制位 组成

image.png
我们通常用四段十进制数表示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协议 可以为各种应用传输数据,比如 EmailWWWFTP,这些应用也要根据不同协议规定数据的规范,这些应用的协议就构成 应用层

应用层 是最高的一层,它是直接面向用户的,他的数据放在 TCP 的数据包部分 即如下:

image.png

搭建TCP服务

通信双方建立TCP连接时,需要进行三次握手。三次握手成功确保TCP成功建立连接后,开始进行通信。

TCP三次握手

客户端 首先向 服务器端 发送数据,进行 第一次握手服务器端 接收到数据后向 客户端 作出响应,进行 第二次握手客户端 接收到响应后,会进行 第三次握手 ,对 服务器端 再次发送数据告知 服务器端 可以接收到数据。
客户端服务器端 经过三次握手后,TCP连接建立成功接下来就可以传输数据了。

net模块

Nodenet 模块可以搭建 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)
  })

})
复制代码

image.png

搭建UDP服务

UDP

UDP 全称为 User Datagram Protocol,又称为用户数据包协议

  • UDPTCP 相同,都是位于 网络传输层 用于传输数据包
  • 无连接、传输速度快、数据传输不可靠,不提供数据包分组,无法对数据包进行排序,数据是无序的,无法保证传输时数据的完整性、正确性。
  • 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)}`); 
})
复制代码

image.png

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)
复制代码

image.png

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)
复制代码

image.png

搭建HTTP服务

TCPUDP 是网络传输层的协议,它们可以构建高效的网络应用,但是对于经典的浏览器和服务器通信场景使用传输层协议会很麻烦。
对于上述场景,基于传输层之上制定了更上一层的通信协议:HTTP,由于 HTTP 协议本身并不不考虑数据如何传输和其他细节问题,所以他属于网络应用层。
Node 提供 httphttps 模块,用于 HTTPHTTPS 的封装

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('服务运行成功');
})
复制代码

image.png

image.png

image.png

响应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('服务运行成功');
})
复制代码

image.png

处理项目中的静态资源

当后端响应的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 文件夹,存放 cssjs 文件
/* 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('服务运行成功');
})
复制代码

image.png

对资源进行统一处理

在上述操作中,对静态资源进行了处理。但是每种资源单独判断处理增加了很多重复代码,也不利于后期新增其他类型资源的维护工作,这里可以对资源进行统一处理。

  • 只能请求的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是一对公钥/私钥结构,传输的数据采用对称加密,对数据加密的密钥采用非对称进行加密,在一定程度上保证了数据的安全。
但是如此还是无法解决,第三方冒充身份参与通信的问题,例如,正常是客户端与服务器直接通信,如果第三方介入的话,他可以伪装成服务器截取密钥对,对数据造成泄漏。

image.png

这个解决有第三方冒充身份参与通信的问题,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);
复制代码

猜你喜欢

转载自juejin.im/post/7047066596962992136
今日推荐