§ 第一章.OSI七层网络参考模型
TCP/IP -> OSI参考模型分别是:
1.物理层,2.数据链路层,3.网络层,4.传输层,5.会话层,6.表示层,7.应用层
很多人把七层分为了四层或者五层,分别是:
一层:网络接口层/数据链路层:数据链路层,物理层
二层:网络层:网络层
三层:传输层:传输层
四层:应用层:应用层,表示层,会话层
1.物理层:
物理层是直接和物理介质打交道的,物理层的设备有,网卡,网线,集线器,中继器,调制解调器等,这里面最重要的是物理层信道
物理层信道:
物理层信道,也就是明线,明线是平行架设在电线杆上的架空线路
优点:传输的损耗是比较低的
缺点:暴露在大自然中,容易受天气气候环境影响
目前已经逐渐被电缆所取代
1.对称电缆:
对称电缆是由多对双绞线组成的线缆
2.同轴线缆:
同轴线缆分为四层:中心导体,绝缘层,外层导体(带屏蔽),外皮
同轴线缆的对应范围极其广泛,同轴线缆能以低损耗的方式传输模拟信号和数字信号,适用于各种应用,其中常见的有电视广播系统,长途电话传输系统,计算机系统之间的短距离跳线以及局域网互联等等
3.光纤:
光导纤维是由玻璃或塑料制成的纤维,利用光在这些纤维中以全反射原理传输的光传导工具
4.无线信道:
无线电波,以辐射无线电波为传输的方式,比如:卫星通讯,电台广播,wifi等等
总结:无论是以光,电,无线电波传输的方式来获取对应的传输信号,最终都会转换成01010101的形式,但是数据还没有组织,它们的单位为bit(比特)所以这一层的主要作用就是传输比特流(物理层传输的是比特流)
2.数据链路层:
作用:拿到从物理层转换的010101比特流组装成一个数据帧。
数据帧是由Mac地址组成,Mac地址是每一个网卡的唯一标识,当有了Mac地址之后,会将比特流进行分组,并且会拼装上Mac地址,拼装上了就知道给谁发,谁是接受者了
如何发送:通过“吼”的方式也可以说是广播
如何在电脑中查看物理地址:
windows系统:win+R → 输入cmd回车 → 输入ipconfig/all,就可以看到物理地址
mac系统:ifconfig/all
总结:数据链路层主要是拿到物理层的数据,拼装一个数据帧,并且拼装上网卡Mac地址,通过物理设备 交换机 进行跳点的方式进行交互
3.网络层:
网络层主要做的两件事情是:寻址,路由
1.寻址:
对网络层而言,通过IP地址来唯一标识互联网设备,网络层依靠IP地址作为寻址的媒介,并且进行相互通信
如何寻址:通过路由器的路由来寻址
2.路由:
网络是非常巨大的,通过路由器端对端的数据进行查找,直到找到你要发送的这一个地址为止,这一层协议就叫:IP协议,目前是ipv6版本
4.传输层:
传输层主要就是定义我们的端口号,比如8080,以及控流和校验,传输层拥有两个协议,分别是:TCP,UDP
1.TCP:
TCP是面向连接的协议,也是可靠协议,因为TCP会进行三次握手和四次挥手,所以是可靠的,也因为需要反复的确认,所以这样会降低速度
2.UDP:
UDP具有较好的实时性,也是不可靠协议,但是效率比TCP高,之所以不可靠,是因为UDP没有三次握手和四次挥手,所以不稳定,但是速度快,常用语直播,游戏等等
这一层通常被称为:数据段
5.会话层:
会话层:在发送方和接收方之间,通过会话ID,进行通信时创建,维持,终止,断开会话,比如QQ,微信,和电话通话有点相似
会话层包含了一种称为检查点(checkpoint)的机制来维持可靠通话,检查点定义了一个最接近成功通信的点,并且定义了当发生内容丢失或损坏时需要回滚以便恢复丢失或损坏的数据点
这一层通常被称为:报文
6.表示层:
1.当你的数据发送之前进行加密,在接受者的表示层进行解密
2.表示层还会对图片文件等格式进行解码和编码,比如设备中有图片(JPG,PNG,GIF)MP3音乐,MP4视频,ASCII等等编码,转换成计算机能读懂的编码
这一层通常被称为:报文
7.应用层:
应用层就是我们使用最多的一层,比如ajax,axios调用接口发送http请求,响应数据,请求数据,再比如域名系统DNS,邮件协议SMTP,webSocket长连接,SSH协议等等
这一层通常被称为:报文
§ 第二章.TCP三次握手
面向连接就是数据通讯的识货需要进行三次握手,断开通信的时候进行四次挥手
三次握手:
1.seq(sequence number)序列号通过各种算法随机生成的,也叫做isn随机生成的
2.ack(acknowledgement number)确认号,ack = seq + 1,通过生成的seq加上1判断一下,是否有效,如果有效,会打上大写的ACK标记
3.ACK(acknowledgement)确定序列号有效
4.SYN(synchronous)发起新连接
第一次握手:
客户端和服务端第一次发起连接的时候,通过SYN建立连接,并且客户端向服务端发起seq序列号(因为有很多报文,顺序会乱,通过生成的seq方便整理顺序)
第二次握手:
生成信息之后会发送给服务端,服务端判断当前序列号是否正确,服务端会拿到客户端的seq+1生成ack序列号,如果正确就会打上大写的ACK标记,并且服务端也会自己生成自己对应的seq的序列号,然后再拼接上SYN发送给客户端
第三次握手:
客户端收到之后也会判断是否正确,客户端的seq根据第一次客户端向服务端发送的seq+1,ack是读取服务端的seq+1,如果正确,就打上大写的ACK标记等于1
§ 第三章.TCP四次挥手
1.seq(sequence number)序列号通过各种算法随机生成的,也叫做isn随机生成的
2.ack(acknowledgement number)确认号,ack = seq + 1,通过生成的seq加上1判断一下,是否有效,如果有效,会打上大写的ACK标记
3.ACK(acknowledgement)确定序列号有效
4.SYN(synchronous)发起新连接
5.FIN(FINISH)完成
四次挥手:
客户端和服务端都可以主动发起
第一次挥手:
客户端向服务端发起首先要有结束状态,并且客户端要生成一个seq=u,客户端进入第一阶段等待状态,等待服务端给我们响应,首先客户端就会收到FIN包和seq进行验证,如果验证成功,打上ACK标记,代表我们的序列号是有效的
第二次挥手:
客户端进入wait2阶段,如果有未完成的任务或者别的响应,在第一阶段也会处理完成的,直到把所有的任务都处理完成,服务端才会发起第三次握手
第三次挥手:
第三次挥手也就是最终结束状态,服务端发送FIN请求,并且验证ACK(拿到客户端的seq+1判断,如果成功,生成ACK,并且生成服务端的序列号,seq=w)并且发送给客户端
第四次挥手:
此时客户端会进入超时等待状态,状态会持续1-4分钟时间,这个状态结束之后,客户端会向服务端发送最后一个请求,并且会进入close阶段(请求会验证客户端seq是否正确,seq+1,如果验证成功,就会打上ACK标记,并且生成新的seq序列号)发送给服务端,如果服务端收到,就会close关闭
- 为什么会有1-4分钟时间等待:
在最后一步当客户端向服务端发起的时候,因为某种情况,ACK标记丢掉了,服务端就永远不会断开,就会造成不稳定,为了弥补这种问题的发生,所以就执行1-4分钟的等待,如果丢掉了,那么服务端就会重新发起断开连接的请求,过程再重新执行一遍
§ 第四章.浏览器输入url发生了什么
URL是由三部分组成,例如:www.baidu.com/dir/index.h…
分别是:
1.访问协议(https:)
2.服务器名称:域名(www.baidu.com)
3.请求文件(资源)的路径名(dir/index.html)
1.进入DNS查询:
(DNS是将域名和IP做了映射,通过对应的域名找到服务器对应的IP,然后拿到正确的资源)
DNS遵循四个查找规则:
-
浏览器自身DNS(如果找不到)
-
操作系统DNS(如果找不到)
-
本地hosts文件映射关系(如果找不到)
-
向域名服务器发送请求(如果前三个都找不到,来到最后一步)
DNS向域名服务器发送请求的规则是:
比如以www.baidu.com为例
向客户端发送请求,从本地DNS服务器查找有没有对应的IP,如果找不到
1.根域名服务器(如果找不到)
2.顶级域名服务器 com.(如果找不到)
3.权威域名服务器查找对应的IP baidu.com.
2.三次握手:
查找之后拿到对应的IP,并且发起网络请求,经过OSI七层网络模型中的传输层步骤,并且根据IP建立TCP连接
3.发送HTTP请求:
到应用层步骤,发送HTTP请求,例如HTTP报文里有,响应标头,请求标头,请求方法
请求方法除了GET和POST,还有restful标准中还有delete删除,put更新,patch,options预检请求还有head等
有一种情况是在实际开发中,有两个http请求,第一个是options,第二个才是我们需要的post请求,导致这种情况的原因一般有两种情况
第一种情况是遇到跨域的时候,浏览器会发送options请求做预检,检测我们的请求能否成功
第二种情况是当我们自定义请求头的时候,浏览器也会触发options预检请求
- 需要注意的是:
简单的get,hand,post以及对应的content type为application的formdata格式,上传文件格式以及text plan是不会触发预检请求的
只有POST的时候并且请求头为application JSON的模式,才会触发options预检请求
4.到浏览器缓存:
浏览器缓存有两种缓存,分别是强缓存,协商缓存
1) 强缓存:
强缓存是让浏览器强制缓存服务端提供的资源,一般常用于静态资源,比如css,js,或者一些比较简单的请求
强缓存一般通过后台去配置,一般是有两个字段,分别是
1.Cache-Control和max-age=10 十秒钟
2.Expire存储GMT时间
通过这两个字段设置缓存之后,当第二次发送请求的时候,浏览器就会直接返回,此时就不经过服务端了,之后在第一次的时候才会经过服务端
缓存资源存储在
1.from disk cache,硬盘缓存
2.from memory cache,内存缓存
当浏览器多次刷新的时候,数据会从内存缓存里读取,因为第一次的时候,已经从硬盘缓存中读取过了,第二次就会从内存缓存中读取
2) 协商缓存:
和后端去协商,通过两个字段,分别是Last-Modified和ETag
Last-Modified:存储GMT时间,通过后台请求头去存储相关字段(最后被修改的时间)
If-Modified-Since:这两个字段进行对比,看资源有没有更新,如果更新了,就正常返回200,如果没有更新,服务端返回304(Not Modified)
ETag:什么都可以存储,比如可以自定义存储版本号,说明等等,ETag的值没有规定,一般是文件hash,每次请求都会对比Etag的hash值,如果有变化,同样返回200,否则返回304
5.四次挥手:
断开TCP链接
6.拿到HTML页面进行渲染:
HTML解析器会将超文本和标签解析成DOM树,从上到下进行AST抽象语法树,一行一行的去解析我们的dom,然后绘制成一个抽象语法树
CSS渲染引擎是将CSS进行格式化,转化成标准格式,比如会将em转化为px,颜色值转化为rgb,font-weight:bold转化为font-weight:700等
在浏览器绘制的时候,我们还会遇到回流和重绘
1.回流:
当大小发生改变的时候触发回流事件
会导致回流的操作:
1.页面首次渲染
2.浏览器窗口大小发生改变
3.元素尺寸或位置发生改变
4.元素内容变化(文字数量或图片大小等等)
5.元素字体大小变化
6.天假或者删除可见的DOM元素
7.激活CSS伪类(例如:hover)
8.查询某些属性或调用某些方法
一些常用且会导致回流的属性和方法:
clientWidth,clientHeight,clientTop,clientLeft
offsetWidth,offsetHeight,offsetTop,offsetLeft
scrollWidth,scrollHeight,scrollTop,scrollLeft
scrollIntoView(),scrollIntoViewIfNeeded()
getComputedStyle()
getBoudingClientRect()
scrollTo()
2.重绘:
当页面中元素样式的改变并不影响她在文档流中的位置时(例如color,background-color,visibility等)浏览器会将信阳市赋予给元素并重新绘制时,这个过程就叫重绘
7.V8引擎解析JavaScript:
V8引擎是由C++写的,比如输入一段代码
function foo() { …… } foo()
解析器会将这段JS代码解析成AST抽象语法树,之后通过中间代码也就是字节码,之后通过解释器(解释器在V8用的是JIT,即时编译,边解析边执行)监控机器人,如果发现热点代码就继续解析,之后转化成机器码(100010010101格式),之后运行到CPU
以上就是输入URL执行的所有过程
补充:
计算机组成原理:
一般是通过软件和硬件,硬件是基于冯若依慢架构体系做的实现,首先是输入设备和输出设备,比如鼠标键盘,CPU主要是负责运算以及控制器
运算器:
运算器主要是处理逻辑比如加减乘除,或,并且,运算等等
控制器:
控制器一般是从内存中读取指令,比如翻译指令,分析指令,根据指令相关的部件去发送一个命令
存储器:
1.内部存储器
内部存储器一般都是以二进制的代码进行存储的,都是以字节为单位的,一个存储器占用一个存储单元,并且每个存储单元都有唯一的一个地址号
2.外部存储器
硬盘,移动硬盘,U盘等
§ 第五章.CDN内容分发
CDN(Conrent Delivery Network)内容分发网络,CDN是用来优化网络资源请求时间的
网站上线必须要有一台服务器,这个服务器上存放前端(html css js)资源,当我们访问的时候,通过域名配置DNS域名解析和IP进行绑定,让用户访问的时候,就会经过DNS解析,找到对应的IP并且返回相关资源,但是服务器都会有一个物理地址,比如服务器是在北京,但是网友在广东,从广东到北京是很慢的,如果我们在每一个站点都放置一台服务器,但是源服务器在北京,当我们访问的时候,资源就可以就近返回,这样就节省了加载访问时间。
比如在实际项目开发中,我们可以通过script的src外链CDN地址的模块JS或者资源,可以实现更快更便捷的访问
§ 第六章.跨域的解决方案
为什么会有跨域?
出于浏览器的同源策略的限制,浏览器会拒绝跨域请求
同源策略:
请求的时候拥有相同的协议,域名,端口,只要有一个和主机不同,就属于跨域
解决跨域的方案:
1.前后端协商JSONP
原理是通过script的src不受同源策略的限制,可以跨域请求数据,但是只能发送get请求
缺点:只能发送get请求,不安全,不容易维护
后端返回的是一个函数,但是这个函数是在前端定义的,他会把值注入到这个函数的参数中
前端:(新建一个根目录文件夹,新建一个index.html文件)
<script>
const jsonp = (name) => {
let script = document.createElement('script')
script.src = 'http://localhost:3000/api/jsonp?callback=' + name
document.body.appendChild(script)
return new Promise((resolve) => {
window[name] = (data) => {
resolve(data)
}
})
}
jsonp(`callback${new Date().getTime()}`).then(res => {
console.log(res)
})
</script>
后端:
后端用node实现:(在新建的根目录文件夹中安装)
生成package依赖包:
pnpm init
安装express:
pnpm install express
安装express声明文件:
pnpm install @types/express
安装node声明文件
pnpm install @types/node
根目录新建index.ts
index.ts:
import express from 'express';
const app = express();
app.get('/api/jsonp', (req, res) => {
const { callback } = req.query;
res.send(`${callback}('hello')`)
})
app.listen(3000, () => {
console.log('server is running')
})
启动服务:ts-node-esm index.ts
控制台提示server is running说明已经启动成功
vscode中启动live server,就可以看到
2.前端解决,使用代理dev
后端:index.ts:
import express from 'express';
const app = express();
app.get('/api/data', (req, res) => {
res.json({name:"xiaoming"})
})
app.listen(3000, () => {
console.log('server is running')
})
前端:
<script>
fetch('/api/data').then(res => res.json()).then(res => {
console.log(res)
})
</script>
此时,浏览器控制台会报红色的错误信息:
Access to fetch at 'http://localhost:3000/api/data' from origin 'http://127.0.0.1:5500' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
说明因为端口不一样,已经跨域了
如何解决跨域?这里我们用vite去解决
安装vite:
pnpm install vite -D
根目录新建vite.config.ts:
import { defineConfig } from 'vite'
export default defineConfig({
server: {
proxy: {
'/api': {
target: 'http://localhost:3000',
changeOrigin: true,
// rewrite: (path) => path.replace(/^\/api/, '')
}
}
}
})
配置vite服务
打开package.json:
"scripts": {
"dev": "vite"
},
在vscode中输入:npm run dev
3.后端解决,设置请求头
只需要给后端设置header请求头即可
res.setHeader('Access-Control-Allow-Origin', '*');
后端:index.ts:
import express from 'express';
const app = express();
app.get('/api/json', (req, res) => {
res.setHeader('Access-Control-Allow-Origin', '*');
res.json({ name: "xiaoming" })
})
app.listen(3000, () => {
console.log('server is running')
})
前端:
<script>
fetch('http://localhost:3000/api/json').then(res => res.json()).then(res => {
console.log(res)
})
</script>
此时在network中的请求头会多出来:
Access-Control-Allow-Origin: *
说明是允许任何源都可以跨域,这样是不安全的,一般可以把*指定一个固定的ip,之后指定的ip,才能访问,否则其他的都有跨域
比如:res.setHeader('Access-Control-Allow-Origin', 'http://127.0.0.1:5500/');
4.运维解决,使用ngnix代理
§ 第七章.Ajax全套
正在开发中。。。
§ 第八章.fetch全套
fetch用起来更加简单一些,并且更加直观和更佳语义化,XMLHttpRequest需要写很多代码,fetch写起来比较简单,并且fetch目前一直在维护,XMLHttpRequest已经不再维护
fetch的缺点:
1.无法设置超时时间(timeout)需要自己去实现超时时间
2.取消请求的时候,没有XMLHttpRequest方便,需要基于一个构造函数去完成
1.实现get和post请求:
get请求:
前端:
<body>
<button id="send">send Fetch</button>
<br />
<button>中断请求</button>
<script>
const btn = document.querySelector('#send')
const sendFetch = () => {
// 1.第一个参数url默认请求方式为get,返回是一个Promise对象
fetch('http://localhost:3000/api/txt').then(res => {
console.log(res)
})
}
btn.addEventListener('click',sendFetch)
</script>
</body>
// 启动live server
后端:
import express from 'express';
const app = express();
app.get('/api/txt', (req, res) => {
res.setHeader('Access-Control-Allow-Origin', '*');
res.json({ name: "xiaoming" })
})
app.listen(3000, () => {
console.log('server is running')
})
// 输入 ts-node-esm index.ts
返回的是可以查看response是否请求成功,比如状态status为200和ok为true,还可以获取请求头中的信息,我们还需要从prototype中调用几个方法,比如返回的格式,返回的格式有五种:
1.text():将响应体解析为纯文本字符串并返回
2.json():将响应体解析为JSON格式并返回一个JavaScript对象
3.blob():将响应体解析为二进制数据并返回一个Blob对象
4.arrayBuffer():将响应体解析为二进制数据并返回一个ArrayBuffer对象
5.formData():将响应体解析为FormData对象
例如:
<body>
<button id="send">send Fetch</button>
<br />
<button>中断请求</button>
<script>
const btn = document.querySelector('#send')
const sendFetch = () => {
// 1.第一个参数url默认请求方式为get,返回是一个Promise对象
fetch('http://localhost:3000/api/txt').then(res => {
console.log(res)
// 指定返回的方式
return res.text()
}).then(data => {
console.log(data)
})
}
btn.addEventListener('click',sendFetch)
</script>
</body>
post请求:
安装cors:npm install cors --save
后端:(index.ts)
import express from 'express';
var cors = require('cors');
const app = express();
app.use(cors())
app.use(express.urlencoded({extended:false}))
app.use(express.json())
app.get('/api/txt', (req, res) => {
res.setHeader('Access-Control-Allow-Origin', '*');
res.json({ name: "xiaoming" })
})
app.post('/api/list', (req, res) => {
res.setHeader('Access-Control-Allow-Origin', '*');
res.json({ name: "xiaoming" })
})
app.listen(3000, () => {
console.log('server is running')
})
前端:
<body>
<button id="send">send Fetch</button>
<br />
<button>中断请求</button>
<script>
const btn = document.querySelector('#send')
const sendFetch = () => {
// 1.第一个参数url默认请求方式为get,返回是一个Promise对象
fetch('http://localhost:3000/api/list', {
method: 'post',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({})
}).then(res => {
console.log(res)
// 指定返回的方式
return res.json()
}).then(data => {
console.log(data)
})
}
btn.addEventListener('click', sendFetch)
</script>
</body>
这样我们就可以在实现post请求方式了