网络编程

01 - OSI七层模型

OSI七层模型.png

02 - TCP - 三次握手

  1. seq,序列号随机生产的
  2. ack确认号ack = seq + 1
  3. ACK 确认序列号有效
  4. SYN 发起新连接
  5. FIN完成
tcp三次握手.png
  1. 首先客户端会发送一个段这个段就是$报文,想跟服务端进行连接,并且会携带一个序列号,下次发送的数据序列号还会进行+1。
  2. 服务端收到了SYN+seq字段之后,服务端也会生成一个对应服务端seq序列号,这时候就会携带ACK确认号,表示之前的SYN收到了,还会有一个小写的ack把客户端的seq+1
  3. 客户端收到服务端的响应之后会发送一个ACK确定序列号有效,并且还会发送seq注意这里的seq会通过算法计算出来是否跟服务端的ack值相等,然后会发送一个新的ack这里的ack是服务端的seq值+1,确保一切正常。

03 - TCP - 四次挥手

TCP四次挥手.png
  1. 断开连接服务端和客户端都可以主动发起我们拿客户端举例,客户端进行断开操作先发送却IN包生成客户端的seq序列号随后进入wait1状态,这是第一次挥手。
  2. 服务端收到FN包表示自己进入了关闭等待状态,然后向客户端使用ack验证,验证成功打上ACK标记,随后生成服务端的seq值发送给客户端,这是第二次挥手,服务端此时还可以发送未完成的数据。
  3. 等待服务端所有任务操作完成之后服务端开始进入最后确认状态,向客户端发送FIN包,并且验证ck,使用客户端第一次的seq+1去验证,验证成功打上ACK标记,并且生成一个新的序列号$q发送给客户端,这是第三次挥手。
  4. 客户端收到之后进入超时等待状态2MSL(1-4分钟),经过等到后客户端关闭连接,而服务端收到信息验证完成ck成功之后打上ACk标记随后将关闭连接。

为什么需要超时等待时间?

这是为了保证服务端收到ACK包,假设如果没有2MSL的等待时间,ACK包丢失了,那服务端将永远不会断开连接,有了2MSL,如果一旦发生丢包将会进行超时重传,实现可靠连接。

04 - 浏览器输入url到底发生了什么

(第一步)DNS查询

为什么要有DNS?

因为DNS进行解析对应的域名,使其域名解析到对应的ip地址上去,方便大家使用。

DNS查询如下,若其中一步成功则直接跳到建立链接部分:

  1. 浏览器自身DNS
  2. 操作系统DNS(一般在user/etc/dns文件路径下)
  3. 本地hosts文件
  4. 向域名服务器发送请求

(第二步)向域名服务器发送请求(规则)

客户端先向本地DNS服务器去查找看有没有对应的ip,没有向根域名服务器中去查找(.),再没有向顶级域名服务器去查找(.com),再没有向权威域名服务器查找(baidu.com)

(第三步)发送请求

options 请求怎么来的?

当发送跨域的POST请求时,浏览器会先发送一次OPTIONS请求,这是因为浏览器的同源策略。OPTIONS请求被称为预检请求,它是CORS(跨源资源共享)机制中的一部分。

预检请求的目的是为了确保实际请求(例如POST、PUT等)对目标服务器是安全的。在实际请求之前,浏览器向服务器发送一个预检请求,询问服务器是否允许跨域请求以及允许哪些HTTP方法、头部字段等。服务器通过响应头信息告诉浏览器它支持哪些方法和头部字段。

(第四步)浏览器缓存

  1. 强缓存

    强缓存:指的是让浏览器强制缓存服务端提供的资源(两者共存)

    Cache-Control:max-age=31536000

    Expires:Wed,21 Oct 2015 07:28:00 GMT

这个是要后端进行设置的

设置缓存.png

在服务器第一次访问之后,浏览器将缓存存在硬盘缓存中,之后通过硬盘缓存进行读取,就不通过服务器进行访问,当多次刷新时有几率就会触发内存缓存进行读取。

from disk cache 硬盘缓存      from memory cache 内存缓存

硬盘缓存和内存缓存.png
2. 协商缓存

协商缓存就是通过服务器来判断缓存是否可用
Last-Modify搭配lf-Modify–Since:浏览器第一次请求一个资源的时候,服务器返回的header中会加上Last-Modify,Last-modify是该资源的最后修改时间;当浏览器再次请求该资源时,request的请求头中会包含If-Modify–Since,该值服务端header中返回的Last-Modify。服务器收到lf-Modify–Since)后,根据资源的最后修改时间判断是否命中缓存。

​ Etag搭配lf-None-Match:web服务器响应请求时,会在header中加一个Etag用来告诉浏览器当前资源在服务器的唯一标识(生成规则由服务器决定)。则再次向web服务器请求时带上头If-None-Match(Etag的值)。web服务器收到请求后将If-None-Match-与Etag进行比对,决定是否命中协商缓存;

ETag和ast-Modified的作用和用法,他们的区别:

  1. Etag要优于Last-Modified。Last-Modified的时间单位是秒,如果某个文件在1秒内改变了多次,那么他们的Last-Modified:其实并没有体现出来修改,但是Etag每次都会改变确保了精度。
  2. 在性能上,Etag要逊于Last-Modified,毕竟Last-Modified只需要记录时间,而Etag需要服务器通过算法来计算出一个hash值;
  3. 在优先级上,服务器校验优先考虑Etag。如果服务器收到的请求没有Etag值,则将f-Modified-Since和被请求文件的最后修改时间做比对,一致则命中协商缓存,返回304;不一致则返回新的last-modified和文件并返回200

(第五步)拿到HTML页面开始渲染

dom树.png

(第六步)CSS样式计算

渲染引擎将CSS样式表转化为浏览器可以理解的styleSheets,计算出DOM节点的样式。

CSS样式来源主要有3种,分别是:

  1. 通过 link 引用的外部css文件
  2. style标签内的CSS
  3. 元素的style属性内嵌的CSS

其样式计算过程主要为:

/*原本属性*/
body{font-size:2em}
p{color:blue}
span{display:none}
div{font-weight:bold}
/*转换后结果*/
body{font-size:32px}
p{color:rgba(0,0,255)}
span{display:none}
div{font-weight:700}

可以看到上面的CSS文本中有很多属性值,如2em、blue、bold,这些类型数值不容易被渲染引擎理解,所以需要将所有值转换为渲染引擎容易理解的、标准化的计算值,这个过程就是属性值标准化。处理完成后再处理样式的继承和层叠,有些文章将这个过程称为CSSOM的构建过程。

(第七步)浏览器绘制遇到的机制

  1. 回流(reflow)=》大流(大小相关属性)

    Render Tree 中部分或全部元素尺寸、结构、或某些属性发生改变时,浏览器重新渲染部分或全部文档的过程称为回流。

    会导致回流的操作:

    • 页面首次渲染
    • 浏览器窗口大小发生改变
    • 元素尺寸或位置发生改变
    • 元素内容变化(文字数量或图片大小等等)
    • 元素字体大小变化
    • 添加或者删除可见的DOM元素
    • 激活CSS伪类(例如::hover
    • 查询某些属性或者调用某些方法

一些常用且会导致回流的属性和方法:

  • clientWidthclientHeightclientTopclientLeft

  • offsetWidthoffsetHeightoffsetTopoffsetLeft

  • scrollWidthscrollHeightscrollTopscrollLeft

  • scrollIntoViewscrollIntoViewIfNedded( )

  • getComputedStyle

  • getBoundingClientRect( )

  • scrollTo( )

  1. 重绘(Repaint)=》绘色(绘制颜色)

当页面中元素样式的改变并不影响它在文档流中的位置时,(例如:colorbackground-colorvisibility等),浏览器会将新样式赋予给元素并重新绘制它,这个过程称为重绘。

(第八步)V8引擎绘制

V8引擎解析.png

输出结果(计算机cpu):

cpu机制.png

05 - CDN内容分发

概念:CDN(Content Delivery Network)内容分发网络

作用:CDN是用来优化网络资源请求的时间的

CDN缓存.png

工作过程:

如果配置了CDN,DNS会将最终的域名解析权交给CNAME(别名指向)指向的CDN专用DNS服务器。

CDN工作原理.png

负载均衡:

负载均衡.png

06 - 跨域 + 四种解决办法

处于浏览器的同源策略限制,浏览器会拒绝跨域请求。

同源策略:请求的时候拥有相同的协议、域名、端口,只要有一个不同的就属于跨域。

主机(http://xiaopan.com) 是否跨域 原因
https://xiaopan.com 协议不同http、https
http://xiaopan.com:8001 端口不同 80 、8001
http://www.baidu.com 域名不同xiaopan、baidu
http://xiaopan.com/a.html 协议、端口、域名、全部相同

解决方案:

  1. jsonp(缺点:只能发送get请求)

    前端:jsonp跨域(前端).png

​ 后端:

jsonp跨域(后端).png
  1. 前端代理解决

    可以使用webpack、vite等构建工具,里面有代理(服务器与服务器之间请求不会发生跨域)。

    注意:前端代理只对开发环境有效

​ 以vite为例:

  • // 前端 vite.config.ts
    import {defineConfig} from "vite"
    
    export default definConfig({
        server:{
            proxy:{
                '/api':{
                    target:'http://localhost:3000',
                    changeOrgin:true
                }
            }
        }
    })
    
    // 前端访问
    fetch('/api/json').then(res=>res.json()).then(res=>{
        console.log(res)
    })
    

    - ```ts
    // 后端
    const express = require('express')

    const app = express()
    app.get('/api/json',(req,res)=>{
    res.send({name:'xiaopan'})
    })
  1. 后端设置请求头

    // 后端
    const express = require('express')

    const app = express()
    app.get('/api/json',(req,res)=>{
    // 表示允许任何源进行访问,这样就可以解决跨域问题
    res.setheader('Access-Control-Allow-Orgin','*')
    //还可以进行设置白名单,进行跨域(安全)
    // 注意遇到一些携带cookie的请求要具体设置目标源
    res.setheader('Access-Control-Allow-Orgin','http://localhost:5500')
    res.send({name:'xiaopan'})
    })
  2. nginx代理

​ 一般使用在网站上线后

​ 在nginx中的配置文件中设置

location /api {
proxy_pass 当前需要访问的地址;
}

07 - XMLHttpRequest全套

  1. 需要创建xhr实例通过XMLHttpRequest使用XMLHt tpRequest可以通过JavaScript发起HTTP请求,接收来自服务器的响应,并动态地更新网页中的内容。这种异步通信方式不会阻塞用户界面,有利于增强用户体验。

  2. 我们需要使用ope(0方法打开一个请求,该方法会初始化一个请求,但并不会发送请求。它有三个必填参数以及一个可选参数

    • method:请求的HTTP方法,例如GET、POST等。
    • url:请求的URL地址。
    • async:是否异步处理请求,默认为true,即异步请求。
  3. onreadystatechange一个回调函数,在每次状态发生变化时被调用。

    • readyState 0:未初始化,XMLHttpRequest对象已经创建,但未调用open方法
    • readyState1:已打开,open方法已经被调用,但send方法未被调用readyState2:已发送,send方法已经被调用,请求已经被服务器接收。
    • readyState3:正在接收,服务器正在处理请求并返回数据。
    • readyState4:完成,服务器已经完成了数据传输。

send向后端传递参数例如he.send(params)

Ajax的主要优点包括:

  • 提高用户体验:通过减少页面的重载和刷新,使得网站变得更加灵活和动态。
  • 减轻服务器负载:通过使用Ajx,可以有效减少服务器接收到的请求次数和需要响应的数据量,从而减轻服务器的负担。
  • 提高响应速度:使用Ajx可以异步获取数据并更新页面,从而提高响应速度。
  • 增加交互性:通过使用Ajx,可以使得页面变得更加动态和交互性。

然而也需要注意一些问题:

  • Aax对搜索引擎优化(Seo)劣势较大,对于需要SEO的项目需要谨慎选择使用Ajax技术。
  • 在使用Ajx时,需要考虑数据安全性和网络安全性问题,并采取相应的措施加以防范。
  • 不合适的使用Ajax,可能会造成降低网站质量和效率的问题,所以需要根据实际需求来决定是否采用该技术。

前端:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>

</style>
</head>
<body>
<button id="btn">发送</button>
<button id="stop">终止</button>
<span id="progress"></span>
</body>
<script>
const btn = document.getElementById('btn');
const stop = document.getElementById('stop');
const progress = document.getElementById('progress');
btn.addEventListener('click',()=>{
const xhr = new XMLHttpRequest();
xhr.open('GET','http://127.0.0.1:3000/api');
xhr.timeout = 1000
//onreadystatechange可以简写
// xhr.onreadystatechange = (res) =>{
// if(xhr.readyState === 4 && xhr.status === 200){
// console.log(JSON.parse(xhr.responseText));
// }
// }
// 简写成
xhr.onload = (res)=>{
console.log(xhr.responseText);
}
// 终止
xhr.addEventListener('abort',(res)=>{
console.log('我被终止了');
})
// 进度
xhr.addEventListener('progress',(res)=>{
console.log(res);
progress.innerHTML = (res.loaded/res.total*100).toFixed(2) + '%';

})
// 超时
xhr.addEventListener('timeout',(res)=>{
console.log('请求超时');
})
xhr.send(null);
stop.addEventListener('click',()=>{
xhr.abort();
})
})
</script>
</html>

后端:

import express from 'express';
import fs from 'fs'

const app = express();

app.get('/api', (req, res) => {
res.setHeader('Access-Control-Allow-Origin','*');
const file = fs.readFileSync('./Test.text', 'utf-8');
res.send(file);
})

app.listen(3000, () => {
console.log('Server is running on http://localhost:3000');
})

08 - fatch全套

概述:Fech是一种网络通信协议,用于在客户端和服务器之间传输数据。该协议使用HTTP请求和响应进行通信,与传统的AJAX方式相比,Fetch更加简单易用,并提供了许多现代化的功能。

使用Fetch可以方便地向服务器发送请求,并将响应返回给客户端。你可以使用Fetch获取文本、JSON、图像和文件等数据,并进行各种处理。Fetch还支持流式传输和取消请求等高级功能,使得处理大型数据集和长时间运行的操作变得更加简单和可靠。
Fetch API也是Javascript中常用的API之一,它提供了一组方法和属性,可以在浏览器端与服务器进行通信。通过Fetch API,你可以轻松地使用Fetch协议进行数据传输,并对请求和响应进行操作和处理。

fetch和xhr差别:

  1. api设计与使用模式

    fetch的API设计更加现代化、简洁和易于使用,使用起来更加直观和方便。相比之下,XHR的API设计比较繁琐,需要进行多个参数的配置和回调函数的处理

  2. 支持的请求方法

    fetch APl默认只支持GET和POST请求方法,而XHR则支持所有标准的HTTP请求方法。

  3. 请求头部

    在fetch中设置请求头部的方式更加清晰和直接,可以通过Headers对象进行设置,而XHR的方式相对较为繁琐。

  4. 请求体

    在发送POST请求时,fetch APl要求将请求体Q数据作为参数传递给fetch方法中的options对象,而XHR可以直接在send()方法中没置请求体数据

  5. 支持的数据类型

    在解析响应数据时,fetch APl提供了多种方法,括.json( ), .b1ob( ) , .arrayBuffer( )等,而XHR只支持文本和二进制数据两种数据类型。

  6. 跨域请求

    在进行跨域请求时,fetch APl提供了一种简单而强大的解决方案一使用CORS(跨域资源共享)头部实现跨域请求,而XHR则使用了一个叫做XMLHttpRequest Level2的规范,在代码编写上相对较为繁琐。

fetch返回格式

  1. text( )):将响应体解析为纯文本字符串并返回。
  2. json( ):将响应体解析为JSON格式并返回一个JavaScript对象。
  3. blob( ):将响应体解析为二进制数据并返回一个Blob对象。
  4. arrayBuffer( ):将响应体解析为二进制数据并返回一个ArrayBuffer对象。
  5. formData( ):将响应体解析为FormData对象。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>

</style>
</head>
<body>
<button id="btn">发送</button>
<button id="stop">终止</button>
<span id="progress"></span>
</body>
<script>
const btn = document.getElementById('btn');
const stop = document.getElementById('stop');
const progress = document.getElementById('progress');
// 创建一个AbortController对象
// 调用AbortController对象的abort方法来终止请求
const abortCont = new AbortController();

const single = async()=>{
timeout(1500)
const data = await fetch('http://localhost:3000/api',{
signal:abortCont.signal
})
// fetch 实现滚动条
const response = data.clone()
const reader = response.body.getReader()
const contentLength = data.headers.get('content-length')
let loaded = 0
while(true){
const {done, value} = await reader.read()
if(done) break
loaded += value.length
progress.innerHTML = `${(loaded / contentLength * 100).toFixed(2)}%`
}
}
btn.addEventListener('click',single)
//中断请求
stop.addEventListener('click',()=>{
abortCont.abort();
})
//设置超时
const timeout = (time)=>{
setTimeout(()=>{
abortCont.abort();
console.log('超时了');
},time)
}

</script>
</html>

09 - SSE全套(※数据大屏)

简介:SSE(Server–Sent Events)是一种用于实现服务器主动向客户端推送数据的技术,也被称为“事件流”(Event Stream)。它基于HTTP协议,利用了其长连接特性,在客户端与服务器之间建立一条持久化连接,并通过这条连接实现服务器向客户端的实时数据推送。

适用于场景

  1. chatGPT返回的数据就是使用的SSE技术
  2. 实时数据大屏如果只是需要展示实时的数据可以使用SSE技术而不是非要使用webSocket

API 用法
EventSource 对象是 HTML5 新增的一个客户端 API,用于通过服务器推送实时更新的数据和通知。在使用 EventSource 对象时,可以通过以下方法进行配置和操作:

  1. EventSource() 构造函数
    EventSource 的构造函数接收一个 URL 参数,通过该 URL 可以建立起与服务器的连接,并开始接收服务器发送的数据。
const eventSource = new EventSource(url, options);

url:String 类型,表示与服务器建立连接的 URL。必填。

options:Object 类型,表示可选参数。常用的可选参数包括:

  • withCredentials:Boolean 类型,表示是否允许发送 Cookie 和 HTTP 认证信息。默认为 false。

  • headers:Object 类型,表示要发送的请求头信息。

  • retryInterval:Number 类型,表示与服务器失去连接后,重新连接的时间间隔。默认为 1000 毫秒。

  1. EventSource.readyState 属性
    readyState 属性表示当前 EventSource 对象的状态,它是一个只读属性,它的值有以下几个:
  • CONNECTING:表示正在和服务器建立连接。

  • OPEN:表示已经建立连接,正在接收服务器发送的数据。

  • CLOSED:表示连接已经被关闭,无法再接收服务器发送的数据。

  1. EventSource.close() 方法

    close() 方法用于关闭 EventSource 对象与服务器的连接,停止接收服务器发送的数据。

    eventSource.close()
  2. EventSource.onopen 事件

    onopen 事件表示 EventSource 对象已经和服务器建立了连接,并开始接收来自服务器的数据。当 EventSource 对象建立连接时,触发该事件。

    eventSource.onopen = function(event) {
    console.log('连接成功!', event);
    };
  3. EventSource.onerror 事件

    onerror 事件表示在建立连接或接收服务器数据时发生了错误。当出现错误时,触发该事件。

    eventSource.onerror = function(event) {
    console.log('发生错误:', event);
    };
  4. EventSource.onmessage 事件

    onmessage 事件表示已经接收到服务器发送的数据,当接收到数据时,触发该事件。

    eventSource.onmessage = function(event) {
    console.log('接收到数据:', event);
    };

以上就是 EventSource 对象的常用 API 介绍,需要注意的是,在使用 EventSource 对象的过程中,如果服务器没有正确地设置响应头信息(如:Content-Type: text/event-stream),可能会导致 EventSource 对象无法接收到服务器发送的数据。

// 后端
import express from "express";
import fs from "fs";

const app = express();

app.get("/api", (req, res) => {
res.setHeader('Access-Control-Allow-Origin','*')
res.writeHead(200, {
"Content-Type": "text/event-stream",
Connection: "close",
});
const file = fs.readFileSync("./Test.text", "utf-8");
const data = file.split("");
const total = data.length-1;
let current = 0;
let time = setInterval(() => {
if (current >= total) {
clearInterval(time);
}
res.write(`event: lol\n`);
res.write(`data: ${data[current]}\n\n`);
current++;
}, 300);
});

app.listen(3000, () => {
console.log("Server is running on http://localhost:3000");
});

// 前端
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
div{
width: 400px;
height: 300px;
border: 1px solid black;
margin: 50px auto;
}
</style>
</head>
<body>
<div>

</div>
</body>
<script>
var div = document.querySelector('div');
document.body.addEventListener('keydown',(e)=>{
if(e.keyCode == 13){
const see = new EventSource('http://localhost:3000/api')
see.addEventListener('lol',(e)=>{
div.innerHTML += e.data
})
}
})
</script>
</html>

09 - webSocket全套

WebSocket是一种在单个 TCP 连接上进行全双工通信的网络协议。它是 HTML5 中的一种新特性,能够实现 Web 应用程序和服务器之间的实时通信,比如在线聊天、游戏、数据可视化等。

场景

  1. 实时性要求较高的应用,比如在线聊天、游戏、数据可视化等;
  2. 需要频繁交换数据的应用,比如在线编辑器、文件管理器等;
  3. 需要推送服务的应用,比如实时数据监控、通知系统等;
  4. 跨平台的应用,比如桌面应用程序、移动应用程序等。

demo 简易聊天 后端nodejs

// 后端
import ws from 'ws'

const wss = new ws.Server({ port: 8080 })

const state = {
HEART:1,
MESSAGE:2
}
// 和客户端进行连接
wss.on('connection', (ws: ws) => {
// 收到客户端消息
ws.on('message',(message)=>{
// 判断当前在线用户进行群发
// console.log(bufferParse(message));

wss.clients.forEach((client) => {
client.send(JSON.stringify({"state":state.MESSAGE,"message":message.toString('utf-8')}))
})
})
let hraetInreval:any = null;
// 心跳包(每过5秒判断当前连接是否活跃)
const sendHeart = ()=>{
if(ws.readyState === ws.OPEN){
ws.send(JSON.stringify({"state":state.HEART,message:'心跳'}))
}else{
clearInterval(hraetInreval)
}
}
hraetInreval = setInterval(sendHeart,5000)
})


<!--前端-->
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<meta name="viewport" content="width='device-width', initial-scale=1.0">
<title>Document</title>
</head>

<body>
<ul id="list"></ul>
<input type="text" name="data" id="ipt">
<button id="btn">提交</button>
</body>
<script>
const list = document.getElementById('list');
const ipt = document.getElementById('ipt');
const btn = document.getElementById('btn');
const ws = new WebSocket('ws://localhost:8080');
btn.addEventListener('click', () => {
if (ipt.value) {
ws.send(ipt.value);
ipt.value = '';
}
})
ws.addEventListener('message', (res) => {
let data = JSON.parse(res.data);
if (data.state === 2) {
list.innerHTML += `<li>${data.message}</li>`;
}
})
</script>

</html>

10 - navigator.sendBeacon全套

使用 navigator.sendBeacon 实现高效的数据上报

在 web 开发中,我们经常需要将用户行为或性能数据上报到服务器。为了不影响用户体验,开发者通常会在页面卸载时进行数据上报。然而,传统的数据上报方式,如 XMLHttpRequestFetch API,容易受到页面卸载过程中的阻塞,导致数据丢失。为了解决这个问题,navigator.sendBeacon API 被引入,它可以在页面卸载时安全、可靠地发送数据。

navigator.sendBeacon 对比 Ajax fetch

优点

  1. 不受页面卸载过程的影响,确保数据可靠发送。
  2. 异步执行,不阻塞页面关闭或跳转。
  3. 能够发送跨域请求。

缺点

  1. fetch 和 ajax 都可以发送任意请求 而 sendBeacon 只能发送POST
  2. fetch 和 ajax 可以传输任意字节数据 而 sendBeacon 只能传送少量数据(64KB 以内)
  3. fetch 和 ajax 可以定义任意请求头 而 sendBeacon 无法自定义请求头
  4. sendBeacon 只能传输 ArrayBufferArrayBufferViewBlobDOMStringFormDataURLSearchParams 类型的数据
  5. 如果处于危险的网络环境,或者开启了广告屏蔽插件 此请求将无效

navigator.sendBeacon 应用场景

  1. 发送心跳包:可以使用 navigator.sendBeacon 发送心跳包,以保持与服务器的长连接,避免因为长时间没有网络请求而导致连接被关闭。
  2. 埋点:可以使用 navigator.sendBeacon 在页面关闭或卸载时记录用户在线时间,pv uv,以及错误日志上报 按钮点击次数。
  3. 发送用户反馈:可以使用 navigator.sendBeacon 发送用户反馈信息,如用户意见、bug 报告等,以便进行产品优化和改进

其他注意事项 type

ping请求 是html5 新增的 并且是sendBeacon 特有的 ping 请求 只能携带少量数据,并且不需要等待服务端响应,因此非常适合做埋点统计,以及日志统计相关功能。

注意:使用navigator.sendBeacon时,会向后端发送预检请求,当后端返回'Access-Control-Allow-Origin':'*'时,就会而且请求的 credentials 模式设置为 include,就会导致这个错误。

<!--前端-->

<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<meta name="viewport" content="width='device-width', initial-scale=1.0">
<title>Document</title>
</head>

<body>
<button id="btn">发送</button>
</body>
<script>
const btn = document.getElementById('btn');
var fd = new FormData();
fd.set("user", "a123123");

btn.addEventListener('click', () => {
let data = JSON.stringify({ name: '张三'})
const blob = new Blob([data], { type: 'application/json' });
const res = navigator.sendBeacon('http://localhost:3000/api/beacon', blob);

})
</script>

</html>
// 后端
import express from 'express'
import cors from 'cors'
// 设置跨域源
const corsOptions = {
origin: 'http://127.0.0.1:5500',
// 允许携带cookie等用户信息
credentials: true
};
const app = express()
app.use(cors(corsOptions))
app.post("/api/beacon",(req,res)=>{
let body = req.body
res.send('ok')
})

11 - SSL、TLS、HTTPS

HTTPS

HTTPS,全称为 Hypertext Transfer Protocol Secure,是一种通过加密通道传输数据的安全协议。它是 HTTP 协议的安全版本,用于在 Web 浏览器和 Web 服务器之间进行安全的数据传输。HTTPS 在传输过程中使用了 SSL(Secure Sockets Layer)或 TLS(Transport Layer Security)协议来加密数据,确保敏感信息在传输过程中不会被窃取或篡改。

http 缺点

通信使用明文(不加密),内容可能会被盗用

不验证通信方的身份,因此有可能遭遇伪装

无法证明报文的完整性,所以有可能已遭篡改

https

信息加密

完整性校验

身份验证

HTTPS = http + TLS/SSL

TLS SSL

TLS(Transport Layer Security)和 SSL(Secure Sockets Layer)是用于保护网络通信的安全协议。它们都提供了加密和认证机制,用于确保数据传输的机密性和完整性。

SSL 是最早的安全协议,而 TLS 是在 SSL 的基础上发展起来的。目前广泛使用的版本是 TLS 1.2 和 TLS 1.3。TLS 1.3 是最新的协议版本,在安全性、性能和功能方面有一些改进。

TLS 和 SSL 主要用于以下两个方面:

  1. 加密通信:TLS/SSL 使用加密算法来对数据进行加密,防止第三方截获和窃听通信内容。它可以确保数据在传输过程中的隐私性。
  2. 身份认证:TLS/SSL 还提供了身份验证机制,用于确认通信双方的身份,并确保数据只发送到正确的接收方。这可以防止恶意用户冒充其他用户或服务器。

SSL 是最早的用来做https TLS 是SSL升级版 提高了安全性 并解决了SSL存在的一些安全性问题

SSl/TLS 工作原理类似的

HTTP TLS/SSL 安全层 TCP

加密

  1. 对称加密

常见的算法有 AES DES 加密

举例 麒麟->星月发消息 但是他们的消息不想被别人知道,采用了对称加密,于是他们两个协商了一段密钥,今生永相随

麒麟:AES算法 + 密钥(今生永相随)+明文(吃面) = XMZSXMZS==

星月:使用AES + 密钥(今生永相随)+密文( XMZSXMZS==) = 吃面

  1. 非对称加密

常见算法有RSA DSA 加密

举例 麒麟->星月发消息,这次使用的是非对称加密,生成了公钥和私钥,公钥可以对外公开,私钥必须只能麒麟知道不能泄露。

星月:RSA + 公钥 + 明文(吃面) = XMZS==

麒麟:RSA + 私钥 + 密文(XMZS==) = 吃面

openSSL 生成私钥

openSSL 安装

Mac电脑自带了

windows www.openssl.org/source/

在 SSL/TLS 加密通信中,一般需要使用三个文件来完成证书相关操作,即:

  1. 私钥文件(例如 “private-key.pem”),用于对加密数据进行解密操作。
  2. 证书签名请求文件(例如 “certificate.csr”),用于向 CA 申请 SSL/TLS 证书签名。
  3. SSL/TLS 证书文件(例如 “certificate.pem”),用于对客户端发送的请求进行验证,以确保通信安全可靠。

私钥文件用于对数据进行解密操作,保证了通信的机密性;证书签名请求文件包含了请求者的身份信息和公钥等信息,需要被发送给 CA 进行签名,从而获取有效的 SSL/TLS 证书;SSL/TLS 证书文件则包含了签名后的证书信息,被用于客户端和服务器之间的身份验证,以确保通信的安全性和可靠性。

通过使用这三个文件进行密钥交换和身份验证,SSL/TLS 可以实现加密通信以及抵御可能的中间人攻击,提高了通信的安全性和保密性。

openssl genpkey -algorithm RSA -out private-key.pem -aes256

  1. openssl: OpenSSL 命令行工具的名称。
  2. genpkey: 生成私钥的命令。
  3. -algorithm RSA: 指定生成 RSA 私钥。
  4. -out private-key.pem: 将生成的私钥保存为 private-key.pem 文件。
  5. -aes256: 为私钥添加 AES 256 位加密,以保护私钥文件不被未经授权的人访问。
  6. Enter PEM pass phrase qwe123 密码短语生成pem文件的时候需要生成pem 证书文件

openssl req -new -key private-key.pem -out certificate.csr

  1. “req”: 表示使用 X.509 证书请求管理器 (Certificate Request Management) 功能模块。
  2. “-new”: 表示生成新的证书签名请求。
  3. “-key private-key.pem”: 表示使用指定的私钥文件 “private-key.pem” 来加密证书签名请求中的密钥对。
  4. “-out certificate.csr”: 表示输出生成的证书签名请求到文件 “certificate.csr” 中。该文件中包含了申请者提供的一些证书请求信息,例如公钥、授权主体的身份信息等。

openssl x509 -req -in certificate.csr -signkey private-key.pem -out certificate.pem

  1. “x509”: 表示使用 X.509 证书管理器功能模块。
  2. “-req”: 表示从输入文件(这里为 “certificate.csr”)中读取证书签名请求数据。
  3. “-in certificate.csr”: 指定要读取的证书签名请求文件名。
  4. “-signkey private-key.pem”: 指定使用指定的私钥文件 “private-key.pem” 来进行签名操作。一般情况下,签名证书的私钥应该是和之前生成 CSR 的私钥对应的。
  5. “-out certificate.pem”: 表示将签名后的证书输出到文件 “certificate.pem” 中。该文件中包含了签名后的证书信息,包括签名算法、有效期、公钥、授权主体的身份信息等。
  6. Enter pass phrase for private-key.pem: 密码短语

nodejs接口测试https

引入生成好的两个文件 certificate.pem private-key.pem

import https from 'node:https'
import fs from 'node:fs'
https.createServer({
key: fs.readFileSync('private-key.pem'),
cert: fs.readFileSync('certificate.pem'),
//密码短语
passphrase: 'qwe123'
}, (req, res) => {
res.writeHead(200)
res.end('success')
}).listen(443,()=>{
console.log('server is running')
})

nginx配置https

server {
listen 443 ssl;
server_name localhost;

ssl_certificate nginx.crt;
ssl_certificate_key nginx.key;

ssl_session_cache shared:SSL:1m;
ssl_session_timeout 5m;

# ssl_ciphers HIGH:!aNULL:!MD5;
# ssl_prefer_server_ciphers on;

location / {
root html;
index index.html index.htm;
}
}

12 - JWT(登录鉴权)

jwt(json web token)

主要是做鉴权用的登录之后存储用户信息

JWT是三部分组成的

  • 头部(Header):头部通常由两部分组成:令牌的类型(即 “JWT”)和所使用的签名算法。头部通常采用 JSON 对象表示,并进行 Base64 URL 编码。
{
"alg": "HS256",
"typ": "JWT"
}

alg:代表所使用的签名算法,例如 HMAC SHA256(HS256)或 RSA 等。 typ:代表令牌的类型,一般为 “JWT”。

  • 负载(Payload):负载包含所要传输的信息,例如用户的身份、权限等。负载也是一个 JSON 对象,同样进行 Base64 URL 编码。
{
"iss": "example.com",
"exp": 1624645200,
"sub": "1234567890",
"username": "johndoe"
}

iss:令牌颁发者(Issuer),代表该 JWT 的签发者。 exp:过期时间(Expiration Time),代表该 JWT 的过期时间,以 Unix 时间戳表示。 sub:主题(Subject),代表该 JWT 所面向的用户(一般是用户的唯一标识)。 自定义声明:可以添加除了预定义声明之外的任意其他声明。

  • 签名(Signature):签名是使用私钥对头部和负载进行加密的结果。它用于验证令牌的完整性和真实性。
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secretKey
)

express JWT demo

// 后端
import express from 'express';
import jwt from 'jsonwebtoken';
import cors from 'cors';
const app = express();
const secretKey = 'xmzs' //加盐

app.use(express.urlencoded({ extended: false }));
app.use(express.json());
app.use(cors())

let user = { name: 'admin', password: '123456', id: 1 } //模拟用户信息

app.post('/api/login', (req, res) => {
console.log(req.body)
if (req.body.name == user.name && req.body.password == user.password) {
res.json({
message: '登录成功',
code: 200,
token: jwt.sign({ id: user.id }, secretKey, { expiresIn: 60 * 60 * 24 }) //生成token
})
} else {
res.json({
message: '登录失败',
code: 400
})
}
})


app.get('/api/list', (req, res) => {
console.log(req.headers.authorization)
jwt.verify(req.headers.authorization as string, secretKey, (err, data) => { //验证token
if (err) {
res.json({
message: 'token失效',
code: 403
})
} else {
res.json({
message: '获取列表成功',
code: 200,
data: [
{ name: '张三', age: 18 },
{ name: '李四', age: 20 },
]
})
}
})
})




app.listen(3000, () => {
console.log('server is running 3000');
})
//前端login页面
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>

<body>

<div>
<div>
<span>账号</span> <input id="name" type="text">
</div>
<div>
<span>密码</span> <input id="password" type="password">
</div>
<button id="btn">登录</button>
</div>

<script>
const btn = document.querySelector('#btn')
const name = document.querySelector('#name')
const password = document.querySelector('#password')

btn.onclick = () => {
fetch('http://localhost:3000/api/login', {
body: JSON.stringify({
name: name.value,
password: password.value
}),
headers: {
'Content-Type': 'application/json'
},
method: 'POST',
}).then(res => res.json()).then(res => {
localStorage.setItem('token', res.token)
location.href = './list.html'
})
}
</script>
</body>

</html>
// 前端 list.html页面
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>List</title>
</head>

<body>
<script>
console.log(localStorage.getItem('token'))
fetch('http://localhost:3000/api/list', {
headers: {
'Authorization':`Bearer ${localStorage.getItem('token')}`
}
}).then(res => res.json()).then(res => {
console.log(res)
})
</script>
</body>

</html>

13 - 网络状态和强网弱网环境

前端如何获取在线和离线状态

onlineoffline 事件

onlineoffline 事件是浏览器自带的两个事件,可以通过添加事件监听器来检测当前网络连接状态。当浏览器的网络连接发生变化,比如从在线状态切换到离线状态,或者从离线状态切换到在线状态时,这两个事件就会被触发。以下是示例代码:

window.addEventListener('online', () => {
console.log('Online');
});

window.addEventListener('offline', () => {
console.log('Offline');
});

navigator.onLine

除了使用事件监听器之外,JavaScript 还提供了另一种方式来检测浏览器的网络连接状态,即使用 navigator.onLine 属性。该属性返回一个布尔值,表示浏览器是否处于联网状态。以下是示例代码:

if (navigator.onLine) {
console.log('Online');
} else {
console.log('Offline');
}

在上述代码中,我们使用了 navigator.onLine 属性来检测当前的网络连接状态,并根据返回的布尔值输出相应信息到控制台。需要注意的是,navigator.onLine 属性只能检测当前的网络连接状态,而不能监听网络连接状态的变化。 如何通过前端获取更多的网络信息 navigator.connection

navigator.connection

navigator.connection 是 Web API 中提供的一种获取网络连接相关信息的接口。该接口返回的是一个 NetworkInformation 对象,包含了多个关于用户设备网络连接状况的属性,如网络类型、带宽、往返时间等。

通过 navigator.connection API 能够获取的主要网络连接属性如下:

  • downlink: 当前网络连接的估计下行速度(单位为 Mbps)
  • downlinkMax: 设备网络连接最大可能下行速度(单位为 Mbps)
  • effectiveType: 当前网络连接的估计速度类型(如 slow-2g、2g、3g、4g 等)
  • rtt: 当前网络连接的估计往返时间(单位为毫秒)
  • saveData: 是否处于数据节省模式