Node的诞生
NodeJS 诞生于2009年,由Ryan Dahi开发而成。它既不会一门语言也不是一门框架,它是基于Google V8 引擎的JS运行环境,使JS具备了 DOM操作(浏览器)、I/O、文件读写、操作数据库(服务器) 等能力。 NodeJS 的包管理工具--NPM,已经成为世界开源包管理中最大的生态社区
总的来说NodeJS具备以下特点:
事件驱动
非阻塞IO模型(异步)
轻量、高效
Node核心API
Buffer
在引入 TypedArray 之前,javascript 并没有处理媒体文件二进制数据流的机制。后来 NodeJS 引入 Buffer,同于TCP流、文件系统操作,以及其他上下文中与八位字节流进行交互。
Buffer的特点
Buffer跟数组十分类似,但是可以存储二进制数据Buffer的大小在创建时确定,且无法更改Buffer类在全局作用域,不需要 requireBuffer内的数据是以二进制形式存储的,但是以二进制显示数据会很长,所以控制台打印Buffer会是十六进制的形式显示。Buffer内的元素(字节)的范围都是 00-ff(两位十六进制范围)、0-255(十进制范围)、00000000-11111111(二进制范围)
计算机内存会被划分为无数个小格、每个小格以电信号形式(0或1)存储数据,这个小格就称为比特位(bit),8bit = 1 byte(字节),Buffer的一个元素占一个字节
Buffer的长度代表占内存的大小,Buffer 中的英文占一个字节 中文占三个字节
Buffer的使用
// 创建长度为10个字节的buffer。默认每个字节以 0 填充
const buf1 = Buffer.alloc(10);
console.log("buf1", buf1);
// 将0号字节位 用10进制值 88 填充
buf1[0] = 88
// 将1号字节位 用10进制值 255 填充
buf1[1] = 255
// 将2号字节位 用16进制值 0xaa 填充
buf1[2] = 0xaa
// 将3号字节位 用10进制值 257 填充,257转换为二进制为1 0000 0001 超过1111 1111最大上限左侧超过8个比特位部分直接被舍掉
buf1[3] = 257
console.log("修改buf1字节位后", buf1);
// 创建长度为10个字节的buffer。每个字节以 1 填充
const buf2 = Buffer.alloc(10, 1);
console.log("buf2", buf2);
// 创建长度为 10 的未初始化的缓冲区。
// 这比调用 Buffer.alloc() 快,
// 但返回的缓冲区实例可能包含旧数据,
// 需要使用 fill()、write() 、
// 或其他填充缓冲区内容的函数重写。
const buf3 = Buffer.allocUnsafe(10);
console.log("buf3", buf3);
// 创建包含字节 [1, 2, 3] 的缓冲区。
const buf4 = Buffer.from([1, 2, 3]);
console.log("buf4", buf4);
// 创建包含字节 [1, 1, 1, 1] 的缓冲区,
// 所有条目都使用 `(value & 255)` 截断以符合范围 0–255。
const buf5 = Buffer.from([257, 257.5, -255, '1']);
console.log("buf5", buf5);
// 创建包含字符串 'tést' 的 UTF-8 编码字节的缓冲区:
// [0x74, 0xc3, 0xa9, 0x73, 0x74](十六进制)
// [116, 195, 169, 115, 116](十进制)
const buf6 = Buffer.from('tést');
console.log("buf6", buf6);
// 创建包含 Latin-1 字节 [0x74, 0xe9, 0x73, 0x74] 的缓冲区。
const buf7 = Buffer.from('tést', 'latin1');
console.log("buf7", buf7);
let str = "Hello 二进制"
const buf8 = Buffer.from(str);
console.log("buf8", buf8);
console.log("字符串长度", str.length, "buffer长度", buf8.length);
字符编码
人有人的语言,计算机有计算机的语言。计算机的机器语言是通过二进制处理数据,人的语言要想跟机器语言进行交互,就需要一个 字典(字符集)。
字符集为每一个字符分配唯一的编号,通过这个编号可以找到对应的字符。 字符依据字符集转换成唯一的编号这个过程称之为 字符编码,反之为 字符解码。
ASCII码对照表
字符A的ASCII码对照关系表:
| BIN | Symbol |
|---|---|
| 0100 0001 | A |
对于每个字符编号转换、内存存储,ASCII 制定了一个规范。ASCII 码有8位数,每位是一个比特 (bit),8位就是一个字节 (byte)。除了第一位是0, 其他7位都可以有0 或者 1 两个选择,所以 ASCII 一共可以表示 2^7 ,也就是 128 个字符。
对于英语来说,128个字符包含了字母、数组和标点符号等。但是对于其他语言,比如汉语,是远远不够的
Unicode统一编码
如果用一个标准方案来展示世界上所有语言中的所有字符,Unicode 的出现很好的解决了这个问题。它是一个很大的字符集合,计算机支持的字符都在其中。它可以容纳几百万个字符。每个字符的编号(码点)都不一样。比如,U+0041表示英语的大写字母A,U+4E25表示汉字严。可以查看 unicode字符对照表 或 unicode汉字对应表
Unicode的问题
Unicode 只是一个符号集,它只规定了符号的二进制代码,却没有规定这个二进制代码应该如何存储。
比如:汉字严的 Unicode 是十六进制数4E25,转换成二进制数足足有15位(100111000100101),这意味着表示这一个字符严,至少需要两个字节。表示其他字符可能需要三个或四个,甚至更多。
这里涉及了两个问题:
- 编码表意不明,无法区别 Unicode 和 ASCII。对于计算机而言,他无法判断三个字节代表一个字符还是三个字符。
- 浪费存储空间,用Unicode表示字符可能需要3、4个字节,表示英文一个字节就够了,如果都统一用4个字节来表示字符,英文的其他两个或三个字节都用0来填充。 对存储来说是极大地浪费。
UTF-8
Unicode 只提供了字符集,没有说明这些码点的编码方式是什么。后来 UTF-8 成为在互联网上使用最广的一种 Unicode 的实现方式,其他实现方式还包括 UTF-16(字符用两个字节或四个字节表示)和 UTF-32(字符用四个字节表示),不过在互联网上基本不用。重复一遍,这里的关系是,UTF-8 是 Unicode 的实现方式之一。
UTF-8 最大的一个特点,就是它是一种变长的编码方式。它可以使用1~4个字节表示一个符号,根据不同的符号而变化字节长度。
1)对于单字节的符号,字节的第一位设为0,后面7位为这个符号的 Unicode 码。因此对于英语字母,UTF-8 编码和 ASCII 码是相同的。
2)对于n字节的符号(n > 1),第一个字节的前n位都设为1,第n + 1位设为0,后面字节的前两位一律设为10。剩下的没有提及的二进制位,全部为这个符号的 Unicode 码。
Dgram
dgram 模块提供了 UDP 数据报套接字的实现。
- 新建 dgram/server.js,并执行
node server.js运行服务// 服务端 const dgram = require('dgram') const server = dgram.createSocket('udp4') // 发生错误 server.on('error', (err) => { console.log(`服务端异常:\n${err.stack}`) server.close() }) // 接收请求 server.on('message', (msg, info) => { // 获取客户端IP+端口号 console.log(`服务端接收来自 ${info.address}:${info.port} 的 ${msg}`) }) server.on('listening', () => { const address = server.address() console.log(`服务器监听 ${address.address}:${address.port}`) }) // 在3005端口运行服务 server.bind(41234) - 新建 dgram/client.js,并执行
node server.js访问服务// 客户端 const dgram = require('dgram') const message = Buffer.from('一些字符串') const client = dgram.createSocket('udp4') console.log('客户端执行了'); client.send(message, 41234, 'localhost', (err) => { // 链接建立成功后,要及时关闭否则会一直占用资源 client.close() })
Event
Node的大部分核心API都是通过 异步事件驱动架构 实现的,在该架构中,某些类型的对象(称为"触发器")触发命名事件,使 Function 对象("监听器")被调用
例如:net.Server 对象在每次有连接时触发事件;fs.ReadStream 在打开文件时触发事件;流在每当有数据可供读取时触发事件。
所有触发事件的对象都是 EventEmitter 类的实例,这些实例提供 EventEmitter.on(事件类型,事件回调) 监听某个时间,EventEmitter.emit(事件类型)派发
示例
const EventEmitter = require('events')
class MyEmitter extends EventEmitter { }
const myEmitter = new MyEmitter()
myEmitter.on('sayHello', () => {
console.log("触发 sayHello 事件");
})
myEmitter.emit('sayHello')
事件回调 this 指向
const EventEmitter = require('events')
class MyEmitter extends EventEmitter { }
const myEmitter = new MyEmitter()
// 如果事件回调为普通函数,事件回调的this 指向 myEmitter
myEmitter.on('sayHello', function (a, b) {
console.log(a, b, this, this === myEmitter);
})
// 如果事件回调为箭头函数,事件回调的this指向会丢失
myEmitter.on('sayHello', (a, b) => {
console.log(a, b, this, this === myEmitter);
})
myEmitter.emit('sayHello', '参数1', '参数2')
只触发一次事件
const EventEmitter = require('events')
class MyEmitter extends EventEmitter { }
const myEmitter = new MyEmitter()
myEmitter.once('sayHellow', function () {
console.log("触发发 sayHello 事件");
})
myEmitter.emit('sayHellow')
// 使用 once 注册事件 事件触发一次后会自动解绑
myEmitter.emit('sayHellow')
fs
在 NodeJS 中所有文件模块都是通过 fs 模块实现的,包括文件目录的创建、删除和查询以及文件的读取和写入。在 fs 模块中,所欲方法都分为同步、异步两种实现方法,具有 sync 后缀的为同步方法
权限位 mode
fs 用来操作文件,这个涉及文件的权限问题
| 权限项 | 读 | 写 | 执行 |
|---|---|---|---|
| 字符表示 | r | w | x |
| 数字表示 | 4 | 2 | 1 |
如上表,文章的操作权限分为三种,读、写和操作。数字表示为八进制位,具有权限的八进制为4、2、1,不具备权限位0
标识位 flag
NodeJS中,标识位代表着对文件的操作方式,如可读、可写、既可读又可写等。下面是各个标识符和其对应的含义
| 符号 | 含义 |
|---|---|
| r | 读取文件,如果文件不存在就抛出异常 |
| r+ | 读取并写入文件,如果文件不存在就抛出异常 |
| rs | 读取并写入文件,指示操作系统绕开本地文件系统缓存 |
| w | 写入文件,文件不存在会被创建。存在则清空后写入 |
| wx | 写入文件,排它方式打开 |
| w+ | 读取并写入文件,文件不存在则创建文件,存在则清空后写入 |
| wx+ | 和w+类似,排它方式打开 |
| a | 追加写入,文件不存在则创建文件 |
| ax | 与a类似,排它方式打开 |
| a+ | 读取并追加写入,不存在则创建 |
| ax+ | 与a+类似,排它方式打开 |
简单概括一下就是:
- r:读取
- w:写入
- s:同步
- +:增加相反操作
- x:排它方式
r+ 和 w+ 的区别,当文件不存在时,r+ 不会创建文件。二回抛出异常,但是 w+ 会创建文件;如果文件存在 r+ 不会自动清空文件,单 w+ 会自动把文件已有内容清空
读取文件
-
同步读取
同步读取方法
readcFileSync,它有两个参数:- 第一个参数为读取 文件路径 或 文件描述符
- 第二个参数为 配置对象(options) 或者 字符串(encoding) ,默认值为
nullencoding:编码类型,默认为nullflag:标识位,默认为r
const fs = require('fs') const path = require('path') // 同步读取文件 const buffer = fs.readFileSync(path.resolve(__dirname, './1.text')) // 同步步读取文件 使用 UTF-8 编码读取 const text = fs.readFileSync(path.resolve(__dirname, './1.text'), 'utf-8') console.log('读取结果', buffer, text); -
异步读取
异步读取方法
readcFile,它有三个参数:- 前两个参数与 readcFileSync 一样
- 第三个参数为 回调函数,函数有两个参数,分别为 err(错误)和 data(数据)
const fs = require('fs') const path = require('path') // 异步读取文件 fs.readFile(path.resolve(__dirname, './1.text'), 'utf-8', (err, data) => { console.log('读取结果', err, data); })
写入文件
-
同步写入
同步写入方法
writeFileSync,它有三个参数:- 第一个参数为读取 文件路径 或 文件描述符
- 第二个参数为写入的数据,类型为 String 或 Buffer
- 第三个参数为 配置对象(options) 或者 字符串(encoding) ,默认值为
nullencoding:编码类型,默认为 utf-8flag:标识位,默认为 wmode:权限位,默认为 0o666
const fs = require('fs') const path = require('path') const buffer = Buffer.from('hello code') // 同步写入 写入String fs.writeFileSync(path.resolve(__dirname, '2.text'), 'hello world') // 同步写入 写入Buffer fs.writeFileSync(path.resolve(__dirname, '3.text'), buffer) -
异步写入
异步写入方法
writeFile,它有四个参数:- 前三个参数与
writeFileSync一样 - 第四个参数为 回调函数,函数有一个参数,为 err(错误)
const fs = require('fs') const path = require('path') // 异步写入 字符串 fs.writeFile(path.resolve(__dirname, '4.text'), 'hello world', (err) => { // 写入完成 if (!err) { // 读取刚写入的内容 const content = fs.readFileSync(path.resolve(__dirname, '4.text'), 'utf-8') console.log('content', content); } }) - 前三个参数与
-
同步追加写入
前面的写入方式都是以覆盖的形式写入内容,同步追加写入为
appendFileSync,他跟writeFileSync一样也是有三个参数const fs = require('fs') const path = require('path') // 追加写入内容 fs.appendFileSync(path.resolve(__dirname, '1.text'), 'Hello Code') // 读取文件 const content = fs.readFileSync(path.resolve(__dirname, './1.text'), 'utf-8') console.log('content', content); -
异步追加写入
异步写入方法
appendFile,它有四个参数,每个参数用法跟writeFile一样const fs = require('fs') const path = require('path') // 异步写入 字符串 fs.appendFile(path.resolve(__dirname, '1.text'), 'hello zhangsan', (err) => { // 写入完成 if (!err) { // 读取刚写入的内容 const content = fs.readFileSync(path.resolve(__dirname, '1.text'), 'utf-8') console.log('content', content); } })
fs 模块高级用法
- 打开文件
操作文件前需要选对文件进行打开操作。open方法用于打开文件描述符,它有四个参数:- path:文件路径
- flag:标识位
- mode:权限位,默认 0o666
- callback:回调函数,有两个参数 err(错误)和 fd(文件描述符),打开文件后执行
const fs = require('fs')
const path = require('path')
fs.open(path.resolve(__dirname, '4.text'), 'r', (err, fd) => {
console.log('data', err, fs)
})
- 关闭文件
close方法有两个参数:- 第一个参数为:关闭文件的文件描述符
- 第二个参数为:回调函数,回调函数有一个参数 err(错误),关闭文件会执行
const fs = require('fs')
const path = require('path')
fs.open(path.resolve(__dirname, '4.text'), 'r', (err, fd) => {
// 关闭文件描述符
fs.close(fd, err => {
console.log('关闭成功')
})
})
- 读取文件
可以使用read方法读取文件,它与readFile不同,一般针对文件太大,无法一次性读取全部内容到缓存中或文件大小未知的情况,多是多次读取到Buffer中
read方法中有六个参数: - fd:文件描述符,需要先用
open打开 - buffer:要将内容读取到
Buffer中 - offset:整数,向
Buffer写入的初始位置 - length:整数,读取文件的长度
- position:整数,读取文件初始位置
- classback:回调函数,有三个参数
err(错误)、bytesRead(实际读取的字节数)、buffer(被写入的缓存区对象)、读取执行完成后执行。
const fs = require('fs')
const path = require('path')
// 创建一个占用内存6的缓存区
const buf = Buffer.alloc(6)
fs.open(path.resolve(__dirname, '1.text'), 'r', (err, fd) => {
console.log('fd', err, fd);
// 读取文件
fs.read(fd, buf, 0, 3, 0, (err, bytesRead, buffer) => {
console.log(bytesRead, buffer, buffer.toString());
})
})
- 创建可读流
也可以通过 createReadStream 来创建可读流,它有两个参数:- 第一个参数为 文件路径 或 文件描述符
- 第二个参数为 配置对象,默认为
null- flags:标识位,默认:'r'
- encoding:编码类型
- ......
const fs = require('fs')
const path = require('path')
const reader = fs.createReadStream(path.resolve(__dirname, '1.text'), { encoding: 'utf-8' })
// 读取文件发生错误
reader.on('error', (err) => {
console.log('发生异常', err);
})
// 已打开要读取的文件
reader.on('open', (fd) => {
console.log('文件已打开', fd);
})
// 文件已就位,可用于读取事件
reader.on('ready', () => {
console.log('文件已准备好...');
})
// 文件读取中
reader.on('data', (chunk) => {
console.log('读取文件数据', chunk);
})
// 文件读取完成
reader.on('end', () => {
console.log('读取已完成...');
})
// 文件已关闭
reader.on('close', () => {
console.log('文件已关闭...');
})
HTTP模块
创建一个简单的HTTP服务
const http = require('http')
// 创建一个服务
const server = http.createServer()
// 监听请求
server.on('request', function (request, response) {
// 配置响应头
response.writeHead(200, { 'Content-Type': 'text/html;charset=utf-8' })
// 通过 request.write 响应数据
response.write('<h1>Hello World</h1>')
// 通过 response.end 结束响应,防止客户端一直等待
response.end('<p>响应结束了</p>')
})
// 绑定端口号,启动服务
server.listen(3002, function () {
console.log('服务在3002端口启动了');
})
创建HTTP2服务
const http2 = require('http2')
const fs = require('fs')
// http2默认使用https https = http + ssl协议
const server = http2.createSecureServer({
// 配置公钥、私钥。 这俩一般需要去第三方证书机构认证
// 这里可以通过openssl 生成公钥 私钥
key: fs.readFileSync('localhost-privkey.pem'),
cert: fs.readFileSync('localhost-cert.pem')
});
server.on('error', (err) => console.error(err));
server.on('stream', (stream, headers) => {
stream.respond({
'content-type': 'text/html; charset=utf-8',
':status': 200
});
stream.end('<h1>Hello World</h1>');
});
// 将服务挂载8443端口运行
server.listen(8443);
HTTP发展历史
HTTP创建之初主要是为了,将超文本标记语言(HTML)从web服务器发送到客户端浏览器中。随着互联网的普及和发展,WEB使用环境也变得越来越复杂。我们不得不对HTPP进行优化。
HTTP的基本优化
影响HTTP 网络请求的因素主要有两个:带宽 和 延迟
带宽:现在网络基建做的很好,带宽得到了很大的提升。我们不再会担心带宽影响网速。
延迟:
- 浏览器阻塞:浏览器会因为一些原因阻塞请求,浏览器对同一个域名,同时只能有4个连接(根据浏览器不同内核可能会有差异)。超过浏览器最大连接数限制,后续请求就会被阻塞。
- DNS查询:浏览器访问域名时,需要通过DNS查询将域名转换为 IP 地址。这个过程可以通过DNS缓存来进行优化
- 建立连接:HTTP 是基于TCP协议,每个HTTP请求都需要进行三次握手才能成功建立连接。但是这些连接无法复用,会导致每次请求都经历三次握手和慢启动。
HTTP 1.0、1.1的区别
-
缓存处理
在HTTP1.0中,主要使用header中的 If-Modifyied-Since、Expires 来作为缓存判断标准,HTTP1.1这引入了更多缓存控制策略例如 Entity tag 、If-Unmodified-Since、If-Match、If-None-Match等更多可供选择的缓存头控制缓存策略 -
带宽及网络连接的使用
HTTP1.0中,存在一些浪费带宽的现象,例如客户端只需要某个对象的一部分,而服务器却将整个对象传输过来,并且不支持断点续传功能,HTTP1.1则在请求头引入了 range 头域,它允许只请求资源的某个部分,即返回码是206,这样既提高了开发的自由性,同时充分利用了带宽和连接 -
错误通知的管理 在HTTP1.1中新增了24个错误状态码,例如:409表示请求的资源与资源当前的状态发生冲突,410代表服务器的某个资源被永久性删除
-
Host头处理
在HTTP1.0中认为每台服务器都绑定一个唯一的IP地址,因此,请求消息中的URL并没有传递主机名(hostname),但是随着虚拟机技术的发展,在同一台物理服务器可以存在多个虚拟主机,并且它们共享一个IP地址,例如有一台 ip 地址为61.135.169.125的服务器,在这台服务器上部署着谷歌、百度、淘宝的网站。为什么我们访问https://www.google.com时,看到的是 Google 的首页而不是百度或者淘宝的首页,这就是 Host 起的作用
HTTP1.1的请求消息和响应消息都应支持Host头域,且请求消息中没有Host头域会报告一个 400 错误 -
长链接
HTTP1.1支持长连接和请求流水线处理,在一个TCP连接上可以传送多个请求和响应,减少了建立和关闭连接的等待和延迟。HTTP1.1中 Connection:keep-alive 默认是开启长连接的
HTTP2.0的新特性
- 新的二进制格式
HTTP1.X的解析是基于文本,基于文本协议的格式解析存在天然缺陷,文本的表现形式有多样性,必须要根据它的应用场景考虑其健壮性。二进制则不同,只存在0和1方便且健壮。 - 多路复用
多路复用可以看做HTTP1.1长连接的升级,在HTTP1.0每次连接只能发送一次请求,请求结束后关闭连接。 HTTP1.1的长连接可以在一个连接中发送若干个请求,但是会出现线头阻塞问题,即若干请求排队单线程依次执行处理,当某个请求超时,后续请求只能被阻塞。
多路复用可以做到连接中的若干请求随机混杂一起各不影响,当某个任务耗时严重也不会影响到其他请求执行。 - header压缩
请求的header有大量信息,并且重复发送。HTTP2.0通过encoder方式来减少传输的header大小,通讯双方各自一份 缓存的header fields表,通过表对照传输的数据。这样既避免了header重复传输又减少了传输数据的大小 - 服务端推送 即 服务端具有 server push 功能
HTTPS模块
Node 的 HTTPS 模块与 HTTP2 模块API基本相似,这里不再过多阐述可以查看 HTTPS模块使用文档 这里重点介绍下 HTTPS 的发展
HTTP请求是明文传输的,数据有被拦截的风险。为了解决这个问题 Netscape 公司制定了 HTTPS协议,HTTPS 可以将数据加密传输,也就是传输密文,及时传输数据被拦截也无法破译,这就保证了网络通讯的安全。
openssl
openssl 是一个安全套接字层密码库,囊括主要的密码算法、常用密钥、证书封装管理功能及实现ssl协议。我们可以使用它来完成数据的加解密。
-
安装
进入 openssl.exe下载地址 随便选择一个适合自己版本下载,安装时 一定要 记住安装路径!!! -
配置环境变量
桌面 -- 右击我的电脑 -- 属性 -- 高级系统设置 -- 环境变量 -- 编辑path -
在任意地方新建文件夹存放私钥公钥,在该文件夹下打开命令栏窗口
-
输入指令生成公钥
openssl genrsa 1024 > key.pem -
输入指令生成私钥
openssl req -x509 -new -key key.pem > key-cert.pem输入指令后,会依次输入国家、省份、地市等信息,输入完成自动生成私钥,如果输入上面指令报错 如下:
这个错误是公钥编码格式不对导致的,可以借助vscode将公钥保存为utf-8格式解决
-
搭建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);
-
密码学基础
在深入了解HTTPS协议之前,我们要知道一些密码学的知识。
-
明文:明文指的是未被加密过的原始数据
-
密文:明文通过某种加密算法加密之后,会变成密文。密文也可以解密得到原始的明文
-
密钥:密钥是一种参数,明文与密文之间通过某个算法相互转换,密钥作为算法的参数使用
-
对称加密:对称加密又叫做私钥加密,即数据的接收方和发送方使用同一个密钥去加密解密数据。对称加密的特点是:算法公开、加密和解密速度快,适合于对大数据量进行加密。常见的算法有DES、3DES、TDEA、Blowfish、RCS和IDEA
- 加密过程:明文+加密算法+密钥 => 密文
- 解密过程:密文+解密算法+密钥 => 明文
对称加密中用到的密钥叫做私钥,加密解密使用的是同一个私钥。由于算法是公开的,私钥一旦泄露密文很容易被破解,所以对称加密的缺点就是密钥的安全管理比较困难。
-
非对称加密:非对称加密也叫做公钥加密,它比对称加密安全性更好。非对称加密使用一堆密钥,即公钥和私钥,并且二者成对出现,私钥被自己保存,不能对对泄露。公钥是公共的密钥,任何人都可以获得该密钥,用公钥或私钥中的任何一个进行加密,用另一个进行解密
HTTPS通信过程
在 HTTPS 数据传输的过程中,需要用 SSL/TLS 对数据进行加密和解密,需要用HTTPS对加密后的数据进行传输,由此可以看出 HTTPS 是由 HTTP 和 SSL / TLS 一起合作完成的,即 HTTPS协议 = HTTP协议 + SSL/TLS协议
HTTPS为了兼顾效率和安全,同时使用了对称加密和非对称加密,数据通过对称加密传输的,对称加密过程需要客户端的一个密钥,为了确保安全,通过非对称加密对该密钥进行加密传输给服务器。即对数据进行对称加密,对称加密使用的密钥通过非对称加密传输。
服务端的公钥和私钥,用来进行非对称加密
客户端生成的随机密钥,用来进行对称加密
数字证书
HTTP不会对通信双方进行身份校验,所以身份有可能会被伪造造成安全问题,数字证书得出现就是为了解决这个问题。使用流程如下:
- 服务器先向一个权威的第三方机构申请一个身份证书
- 客户端向服务端建立通信前要先向服务器端请求获取服务器的证书
- 服务器接收到请求后,将数字证书发送给客户端
- 客户端获取服务器证书后,与可信任的第三方机构证书进行验证,验证通过后可进行正常的通信。
数字签名
HTTP不会对数据的完整性进行验证,这样如果通信过程中数据被篡改了通信的双方也没办法知道,所以有了数字签名技术
数字签名主要有两个作用:
- 验证数据是否为目标对象发出的
- 对数据的完整性进行验证,验证数据是否被篡改过。
- 对发送的内容进行摘要
这个过程跟webpack每次打包生成的hashmap非常类似,通信双方约定每次发送方对数据通过哈希算法得到哈希值,这样每次数据发生变化就会生成新的哈希值,发送方会将哈希值作为数据的摘要传给另一方。
另一方接收到数据和摘要后,通过约定的哈希算法将数据进行哈希,得到哈希值后跟传送过来的摘要进行比对,从而判断数据的完整性。 - 对摘要信息进行签名
对摘要进行签名主要是确认数据发送人的身份,签名技术使用了非对称加密原理
- 对发送的内容进行摘要