文章目录
OSI七层模型与TCP/IP 五层模型?
- 应用层:允许访问OSI环境的手段 OSI:开放系统互连
- 表示层:对数据进行翻译,加密和压缩
- 会话层:建立,管理,终止会话
- 传输层:提供端到端的可靠报文传输和错误恢复
- 网络层:负责数据包从源到宿的传递和网际互连
- 数据链路层:比特组装成帧,实现点到点的传输
- 物理层:传输比特
各层常见协议与硬件?
- 应用层:HTTP、FTP、DNS、SMTP
- 传输层:TCP 、UDP
- 网络层:IP、ICMP、路由器、防火墙
- 数据链路层:网卡、网桥、交换机
- 物理层:中继器、集线器
常见协议的概念?
http是一个简单的请求-响应协议,它通常运行在TCP之上。它指定了客户端可能发送给服务器什么样的消息以及得到什么样的响应。请求和响应消息的头以ASCII码形式给出;而消息内容则具有一个类似MIME的格式。
TCP:传输控制协议(TCP,Transmission Control Protocol)是一种面向连接的、可靠的、基于字节流的传输层通信协议
IP是Internet Protocol(网际互连协议)的缩写,是TCP/IP体系中的网络层协议。设计IP的目的是提高网络的可扩展性:一是解决互联网问题,实现大规模、异构网络的互联互通;二是分割顶层网络应用和底层网络技术之间的耦合关系,以利于两者的独立发展。根据端到端的设计原则,IP只为主机提供一种无连接、不可靠的、尽力而为的数据包传输服务。
IP是整个TCP/IP协议族的核心,也是构成互联网的基础。IP位于TCP/IP模型的网络层(相当于OSI模型的网络层),对上可载送传输层各种协议的信息,例如TCP、UDP等;对下可将IP信息包放到链路层,通过以太网、令牌环网络等各种技术来传送。
UDP:Internet 协议集支持一个无连接的传输协议,该协议称为用户数据报协议(UDP,User Datagram Protocol)。UDP 为应用程序提供了一种无需建立连接就可以发送封装的 IP 数据包的方法。Internet 的传输层有两个主要协议,互为补充。无连接的是 UDP,它除了给应用程序发送数据包功能并允许它们在所需的层次上架构自己的协议之外,几乎没有做什么特别的事情。面向连接的是 TCP,该协议几乎做了所有的事情。
FTP(File Transfer Protocol,文件传输协议) 是 TCP/IP 协议组中的协议之一。FTP协议包括两个组成部分,其一为FTP服务器,其二为FTP客户端。其中FTP服务器用来存储文件,用户可以使用FTP客户端通过FTP协议访问位于FTP服务器上的资源。在开发网站的时候,通常利用FTP协议把网页或程序传到Web服务器上。此外,由于FTP传输效率非常高,在网络上传输大的文件时,一般也采用该协议。默认情况下FTP协议使用TCP端口中的 20和21这两个端口,其中20用于传输数据,21用于传输控制信息。但是,是否使用20作为传输数据的端口与FTP使用的传输模式有关,如果采用主动模式,那么数据传输端口就是20;如果采用被动模式,则具体最终使用哪个端口要服务器端和客户端协商决定。
DNS:域名系统(英文:Domain Name System,缩写:DNS)是互联网的一项服务。它作为将域名和IP地址相互映射的一个分布式数据库,能够使人更方便地访问互联网。DNS使用TCP和UDP端口53。当前,对于每一级域名长度的限制是63个字符,域名总长度则不能超过253个字符。
SMTP是一种提供可靠且有效的电子邮件传输的协议。SMTP是建立在FTP文件传输服务上的一种邮件服务,主要用于系统之间的邮件信息传递,并提供有关来信的通知。
ICMP(Internet Control Message Protocol)Internet控制报文协议。它是TCP/IP协议簇的一个子协议,用于在IP主机、路由器之间传递控制消息。控制消息是指网络通不通、主机是否可达、路由是否可用等网络本身的消息。这些控制消息虽然并不传输用户数据,但是对于用户数据的传递起着重要的作用。
TCP,UDP区别?
TCP是一种面向连接的、可靠的、基于字节流的传输层通信协议,TCP传输效率慢,所需资源多,主要应用于文件传输,邮件传输等,首部字节20-60
UDP是一种无连接、不可靠、基于数据报文段的通信协议,传输效率快,所需资源少,主要应用于语音、视频、直播等即时通信,首部8个字节
TCP如何保障可靠传输?
影响TCP可靠传输主要有两点:1.传输信道产生差错,数据丢失。2.接收方来不及处理收到的数据。
应用数据被分割成TCP认为最适合发送的数据块。
TCP给发送的每一个包进行编号,接收方对数据包进行排序,把有序数据传送给应用层。
TCP校验和是一个端到端的校验和,由发送端计算,然后由接收端验证。其目的是为了发现TCP首部和数据在发送端到接收端之间发生的任何改动。如果接收方检测到校验和有差错,则TCP段会被直接丢弃。
停止等待协议:“停止等待”就是每发送完一个分组就停止发送,等待对方的确认。在收到确认再发送下一个分组。如果传输过程出现差错,发送端将无法收到接收端的确认,此时就需要超时重传
超时重传:当TCP发出一个段后,就启动定时器,等待目的端确认收到这个报文段,如果不能及时收到一个确认,发送端将重发这个报文段。对于发送端的确认丢失和确认迟到,都会重传报文段与确认,重复接收的报文段和确认会被丢弃。但停止等待协议一个显著的特点是效率太低,信道利用率太低,可以使用连续ARQ(自动重传请求Automatic Repeat reQuest)协议。
连续ARQ协议规定,发送方每收到一个确认,就把发送窗口向前滑动一个分组的位置。如果原来已经发送了前5个分组,那么现在就可以发送窗口内的第6个分组了。接收方一般都是采用累积确认的方式,也就是说,接收方不必对收到的分组逐个发送确认,而是在收到几个分组后,对按序到达的最后一个分组发送确认,这就表示:到这个分组分组为止的所有分组都已正确收到了。
累计确认优点:容易实现,即使确认丢失也不必重传。缺点:不能向发送方反映出接收方已经正确收到所有的分组的信息。
利用滑动窗口实现流量控制,TCP的接收端只允许发送端发送接收端缓冲区能接纳的数据。每次接收数据后都将返回给发送端当前接收窗口的值,提示发送端降低发送的数据量,让接收端先处理数据。
拥塞控制就是为了防止过多的数据注入到网络中,这样就可以使网络中的路由器或链路不致过载。拥塞控制是一个全局性的过程,涉及到所有的主机,所有的路由器,以及与降低网络传输性能有关的所有因素。TCP发送方要维持一个 拥塞窗口(cwnd) 的状态变量。拥塞控制窗口的大小取决于网络的拥塞程度,并且动态变化。发送方让自己的发送窗口取为拥塞窗口和接收方的接受窗口中较小的一个。TCP进行拥塞控制的方法有四种,即慢开始(slow-start)、拥塞避免(congestion-avoidance)、快重传(fast retransmit)和快恢复(fast recover)
- 慢开始:由小到大逐渐增大拥塞窗口数值。cwnd初始值为1,每经过一个传播轮次,cwnd加倍。
- 拥塞避免:拥塞避免算法的思路是让拥塞窗口cwnd缓慢增大,即每经过一个往返时间RTT就把发送方的cwnd加1.
- 在TCP/IP中,快速重传和恢复(fast retransmit and recovery,FRR)是一种拥塞控制算法,它能快速恢复丢失的数据包。如果使用超时重传,TCP会用定时器来等待接收方的确认信息。而采用FRR快速重传与恢复,当接收端收到了一个不按顺序的数据段,就会理机给发送端发送一个重复确认,如果发送端接收到三个重复确认,他就会假定确认标记指出的数据段丢失了,并立即重传这些丢失的数据段。有了快速重传和恢复就可以避免超时重传浪费等待的时间。
TCP粘包现象原因和解决方法?
TCP粘包是指:发送方发送的若干包数据到接收方接收时粘成一包
发送方原因:
TCP默认使用Nagle算法(主要作用:减少网络中报文段的数量),而Nagle算法主要做两件事:
-
只有上一个分组得到确认,才会发送下一个分组
-
收集多个小分组,在一个确认到来时一起发送
Nagle算法造成了发送方可能会出现粘包问题
接收方原因:
TCP接收到数据包时,并不会马上交到应用层进行处理,或者说应用层并不会立即处理。实际上,TCP将接收到的数据包保存在接收缓存里,然后应用程序主动从缓存读取收到的分组。这样一来,如果TCP接收数据包到缓存的速度大于应用程序从缓存中读取数据包的速度,多个包就会被缓存,应用程序就有可能读取到多个首尾相接粘到一起的包。
解决粘包问题:
最本质原因在与接收方无法分辨消息与消息之间的边界在哪,通过使用某种方案给出边界,例如:
- 发送定长包。如果每个消息的大小都是一样的,那么在接收方只要累计接收数据,直到数据等于一个定长的数值就将它作为一个消息。
- 包尾加上\r\n标记。FTP协议正是这么做的。但问题在于如果数据正文中也含有\r\n,则会误判为消息的边界。
- 包头加上包体长度。包头是定长的4个字节,说明了包体的长度。接收方先接收包体长度,依据包体长度来接收包体。
TCP三次握手?
第一次握手:客户端发送一个带 SYN(建立联机)=1,Seq(顺序号码)=X 的数据包到服务器端口 客户端进入syn_sent状态
第二次握手:服务器返回一个带 SYN=1,ACK(确认)=1, ack(确认号码)=X+1, Seq=Y 的响应包以示传达确认信息 服务端进入syn_rcvd状态
第三次握手:客户端再回传一个带 ACK=1, ack=Y+1, Seq=X+1 的数据包,代表“握手结束” 连接并进入Established状态
![img](https://tva1.sinaimg.cn/large/007S8ZIlly1gek0vnrigmj30p21000vs.jpg)
为什么三次?为什么两次不行?
如果采用两次握手,会出现某些请求因网络等原因延迟发送,导致已失效的链接请求又传到服务器上,致使一端认为失效,一端认为连接成功,造成资源的浪费。
采用三次握手可以使通信双方相互告知序列号起始值,并确认对方已经收到了正确的序列号起始值。如果采用两次握手,则最多只有连接发起方的序列号能得到确认,另一方的序列号无法得到确认。
TCP四次挥手?
连接双方在完成数据传输之后就需要断开连接。由于TCP连接是属于全双工的,即连接双方可以在一条TCP连接上互相传输数据,因此在断开时存在一个半关闭状态,即有有一方失去发送数据的能力,却还能接收数据。因此,断开连接需要分为四次。
- 主机A向主机B发起断开连接请求,之后主机A进入FIN-WAIT-1状态;
- 主机B收到主机A的请求后,向主机A发回确认,然后进入CLOSE-WAIT状态;
- 主机A收到B的确认之后,进入FIN-WAIT-2状态,此时便是半关闭状态,即主机A失去发送能力,但是主机B却还能向A发送数据,并且A可以接收数据。此时主机B占主导位置了,如果需要继续关闭则需要主机B来操作了;
- 主机B向A发出断开连接请求,然后进入LAST-ACK状态;
- 主机A接收到请求后发送确认,进入TIME-WAIT状态,等待2MSL之后进入CLOSED状态,而主机B则在接受到确认后进入CLOSED状态;
-
TIME_WAIT
主动要求关闭的机器表示收到了对方的FIN报文,并发出了ACK报文,进入TIME_WAIT状态,等2MSL后即可进入CLOSED状态。如果在FIM_WAIT_1状态下,同时收到带有FIN和ACK标志的报文时,可直接进入TIME_WAIT状态。 -
CLOSE-WAIT
被动要求关闭的机器收到对方请求关闭连接的FIN报文,在第一次ACK应答后,进入CLOSE-WAIT状态。这个状态表示等待关闭,并通知应用程序发送剩余数据,处理现场信息,关闭相关资源。 -
如何查看TIME-WAIT状态的链接数量?
netstat -ant|awk '/^tcp/ {++state[$NF]} END {for(key in state) print (key,state[key])}' 结果为: LAST_ACK 14 SYN_RECV 348 ESTABLISHED 70 FIN_WAIT1 229 FIN_WAIT2 30 CLOSING 33 TIME_WAIT 18122 状态说明: CLOSED:无连接是活动的或正在进行 LISTEN:服务器在等待进入呼叫 SYN_RECV:一个连接请求已经到达,等待确认 SYN_SENT:应用已经开始,打开一个连接 ESTABLISHED:正常数据传输状态 FIN_WAIT1:应用说它已经完成 FIN_WAIT2:另一边已同意释放 ITMED_WAIT:等待所有分组死掉 CLOSING:两边同时尝试关闭 TIME_WAIT:另一边已初始化一个释放 LAST_ACK:等待所有分组死掉 二、命令解释 重点看看awk部分: /^tcp/ 滤出tcp开头的记录,屏蔽udp, socket等无关记录。 state[]相当于定义了一个名叫state的数组 NF 表示记录的字段数,如上所示的记录,NF等于6 $NF 表示某个字段的值,如上所示的记录,$NF也就是$6,表示第6个字段的值,也就是TIME_WAIT state[$NF]表示数组元素的值,如上所示的记录,就是state[TIME_WAIT]状态的连接数 ++state[$NF]表示把某个数加一,如上所示的记录,就是把state[TIME_WAIT]状态的连接数加一 END 表示在最后阶段要执行的命令 for(key in state) 遍历数组
为什么会TIME-WAIT过多?解决方法是怎样的?
对于访问量大的web服务器,主动关闭对服务器端的连接时会存在大量的time_wait状态。
time_wait问题可以通过调整内核参数和适当的设置web服务器的keep-Alive值来解决。因为time_wait是自己可控的,要么就是对方连接的异常,要么就是自己没有快速的回收资源,总之不是由于自己程序错误引起的。
为何主机A在发送了最后的确认后没有进入CLOSED状态,反而进入了一个等待2MSL的TIME-WAIT?
msl(Maximum Segment Lifetime),报文最大生存时间,网络中的报文超过这个时间报文将被丢弃。
第一,确保主机B能够顺利进入ClOSED状态。如果处于LAST-ACK状态的主机B一直收不到来自主机A的确认,它会重传断开连接请求,然后主机A就可以有足够的时间去再次发送确认。但是这也只能尽最大力量来确保能够正常断开,如果主机A的确认总是在网络中滞留失效,从而超过了2MSL,最后也无法正常断开;
第二,防止失效请求。如果主机A在发送了确认之后立即进入CLOSED状态。假设之后主机A再次向主机B发送一条连接请求,而这条连接请求比之前的确认报文更早地到达主机B,则会使得主机B以为这条连接请求是在旧的连接中A发出的报文,并不看成是一条新的连接请求了,即使得这个连接请求失效了,增加2MSL的时间可以使得这个失效的连接请求报文作废,这样才不影响下次新的连接请求中出现失效的连接请求。
客户端浏览器一次http完整请求过程?
①:DNS解析域名得到IP地址
②:客户端与服务器建立连接(TCP三次握手)
③:客户端发送HTTP请求
④:服务器接收到请求根据端口号.路径等找到对应资源文件,通过HTTP响应源代码给客户端
⑤:客户端拿到请求到的数据(html页面的源代码),开始解析页面以及请求资源
⑥:客户端渲染页面
⑦:web服务器断开连接(四次挥手)
HTTP与HTTPS之间的区别?
HTTP | HTTPS |
---|---|
默认端口80 | HTTPS默认使用端口443 |
明文传输、数据未加密、安全性差 | 传输过程ssl加密、安全性较好 |
响应速度快、消耗资源少 | 响应速度较慢、消耗资源多、需要用到CA证书 |
HTTPS链接建立的过程:
1.首先客户端先给服务器发送一个请求
2.服务器发送一个SSL证书给客户端,内容包括:证书的发布机构、有效期、所有者、签名以及公钥
3.客户端对发来的公钥进行真伪校验,校验为真则使用公钥对对称加密算法以及对称密钥进行加密
4.服务器端使用私钥进行解密并使用对称密钥加密确认信息并发送给客户端
5.随后客户端和服务端就使用对称密钥进行信息传输
对称加密算法:
双方持有相同的密钥,且加密速度快,典型对称加密算法:DES、AES
非对称加密算法:
密钥成对出现(私钥、公钥),私钥只有自己知道,不在网络中传输;而公钥可以公开。相比对称加密速度较慢,典型的非对称加密算法有:RSA、DSA
GET和POST区别?
Get和Post都是向服务器发送的一种请求,只是发送机制不同。
-
GET请求会将参数跟在URL后进行传递,而POST请求则是作为HTTP消息的实体内容发送给WEB服务器。在Ajax请求中,这种区别对用户是不可见的。
-
首先是"GET方式提交的数据最多只能是1024字节",因为GET是通过URL提交数据,那么GET可提交的数据量就跟URL的长度有直接关系了。而实际上,URL不存在参数上限的问题,HTTP协议规范没有对URL长度进行限制。这个限制是特定的浏览器及服务器对它的限制。IE对URL长度的限制是2083字节(2K+35)。对于其他浏览器,如Netscape、FireFox等,理论上没有长度限制,其限制取决于操作系统的支持。注意这是限制是整个URL长度,而不仅仅是你的参数值数据长度;Post传输的数据量大,可以达到2M。
-
GET方式请求的数据会被浏览器缓存起来,因此其他人就可以从浏览器的历史记录中读取到这些数据,例如账号和密码等。在某种情况下,GET方式会带来严重的安全问题。而POST方式相对来说就可以避免这些问题。
-
在客户端使用get请求时,服务器端使用@RequestParam来获取参数,而客户端使用post请求时,服务器端使用@RequestBody来获取参数。使用@RequestBody时必须用post请求。
HTTP标准包含这两种方法是为了达到不同的目的。POST用于创建资源,资源的内容会被编入HTTP请示的内容中。当请求无副作用时(如进行搜索),便可使用GET方法;当请求有副作用时(如添加数据行),则用POST方法。
-
若符合下列任一情况,则用POST方法:
- 请求的结果有持续性的副作用,例如,数据库内添加新的数据行。
- 若使用GET方法,则表单上收集的数据可能让URL过长。
- 要传送的数据不是采用7位的ASCII编码。
-
若符合下列任一情况,则用GET方法:
- 请求是为了查找资源,HTML表单数据仅用来帮助搜索。
- 请求结果无持续性的副作用。
- 收集的数据及HTML表单内的输入字段名称的总长不超过1024个字符。
HTTP常见响应状态码?
100:Continue — 继续。客户端应继续其请求。
200:OK — 请求成功。一般用于GET与POST请求。
301:Moved Permanently — 永久重定向。
302:Found — 暂时重定向。
400:Bad Request — 客户端请求的语法错误,服务器无法理解。
403:Forbideen — 服务器理解请求客户端的请求,但是拒绝执行此请求。
404:Not Found — 服务器无法根据客户端的请求找到资源(网页)。
500:Internal Server Error — 服务器内部错误,无法完成请求。
502:Bad Gateway — 作为网关或者代理服务器尝试执行请求时,从远程服务器接收到了无效的响应。
重定向和转发区别
重定向:redirect:
地址栏发生变化
重定向可以访问其他站点(服务器)的资源
重定向是两次请求。不能使用request对象来共享数据
转发:forward:
转发地址栏路径不变
转发只能访问当前服务器下的资源
转发是一次请求,可以使用request对象共享数据
COOKIE和SESSION有什么区别?
Session(会话)是在服务端保存的一种数据结构,用来标记用户、跟踪用户的状态,这个数据可以保存在集群,数据库,文件中。
Cookie(小甜点:记录账号,给用户甜头)是客户端保存用户信息的一种机制,用来记录用户的一些信息,也是实现Session的一种方式,创建Session时需要在Cookie里面记录一个Session ID。
1. 由于HTTP协议是无状态的协议,所以服务端需要记录用户的状态时,就需要用某种机制来识具体的用户,这个机制就是Session.典型的场景比如购物车,当你点击下单按钮时,由于HTTP协议无状态,所以并不知道是哪个用户操作的,所以服务端要为特定的用户创建了特定的Session,用用于标识这个用户,并且跟踪用户,这样才知道购物车里面有几本书。这个Session是保存在服务端的,有一个唯一标识。在服务端保存Session的方法很多,内存、数据库、文件都有。集群的时候也要考虑Session的转移,在大型的网站,一般会有专门的Session服务器集群,用来保存用户会话,这个时候 Session 信息都是放在内存的,使用一些缓存服务比如Memcached之类的来放 Session。
2. 思考一下服务端如何识别特定的客户?这个时候Cookie就登场了。每次HTTP请求的时候,客户端都会发送相应的Cookie信息到服务端。实际上大多数的应用都是用 Cookie 来实现Session跟踪的,第一次创建Session的时候,服务端会在HTTP协议中告诉客户端,需要在 Cookie 里面记录一个Session ID,以后每次请求把这个会话ID发送到服务器,我就知道你是谁了。有人问,如果客户端的浏览器禁用了 Cookie 怎么办?一般这种情况下,会使用一种叫做URL重写的技术来进行会话跟踪,即每次HTTP交互,URL后面都会被附加上一个诸如 sid=xxxxx 这样的参数,服务端据此来识别用户。
3. Cookie其实还可以用在一些方便用户的场景下,设想你某次登陆过一个网站,下次登录的时候不想再次输入账号了,怎么办?这个信息可以写到Cookie里面,访问网站的时候,网站页面的脚本可以读取这个信息,就自动帮你把用户名给填了,能够方便一下用户。这也是Cookie名称的由来,给用户的一点甜头。
所以,总结一下:Session是在服务端保存的一个数据结构,用来跟踪用户的状态,这个数据可以保存在集群、数据库、文件中;Cookie是客户端保存用户信息的一种机制,用来记录用户的一些信息,也是实现Session的一种方式。
怎么获取Cookie/Session?
获取cookie:
1.document.cookie
2.Network—Name—Resquest Headers—Cookie
获取session:
1.request.getSession().getAttribute(“xxx”).toString();
HTTP头部字段?
Request Headers:
Accept:text/html, application/xhtml+xml, application/xml;q=0.9, image/webp, image/apng, /; q=0.8
- 作用:向服务器申明客户端(浏览器)可以接受的媒体类型(MIME)的资源
- 解释:浏览器可以接受 text/html、application/xhtml+xml、application/xml类型,通配符*/* 表示任意类型的数据。并且浏览器按照该顺序进行接收。( text/html —> application/xhtml+xml —> application/xml)
Accept-encoding: gzip, deflate, br
- 作用:向服务器申明客户端(浏览器)接收的编码方法,通常为压缩方法
- 解释:浏览器支持采用经过 gzip,deflate 或 br 压缩过的资源
Accept-Language: en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7
- 作用:向服务器申明客户端(浏览器)接收的语言
- 解释:浏览器能够接受 en-US, en 和 zh-CN 三种语言,其中 en-US 的权重最高 ( q 最高为1,最低为 0),服务器优先返回 en-US 语言
- 延伸:语言与字符集的区别:zh-CN 为汉语,汉语中有许多的编码:gbk2312 等
Cache-control: max-age=0
- 作用:控制浏览器的缓存,常见值为 private、no-cache、max-age、alidate,默认为 private,根据浏览器查看页面不同的方式来进行区别
- 解释:浏览器在访问了该页面后,不再会访问服务器
Cookie:
- 作用:告诉服务器关于 Session 的信息,存储让服务器辨识用户身份的信息。
Refer:https://www.baidu.com/xxxxxxxxxx
- 作用:告诉服务器该页面从哪个页面链接的
- 解释:该页面从 https://www.baidu.com 中的搜索结果中点击过来的
Upgrade-insecure-requests:1
- 作用:申明浏览器支持从 http 请求自动升级为 https 请求,并且在以后发送请求的时候都使用 https
- 解释:当页面中包含大量的 http 资源的时候(图片、iframe),如果服务器发现一旦存在上述的响应头的时候,会在加载 http 资源的时候自动替换为 https 请求
User-agent:Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36
- 作用:向服务器发送浏览器的版本、系统、应用程序的信息。
- 解释:Chrome 浏览器的版本信息为 63.0.3239.132,并将自己伪装成 Safari,使用的是 WebKit 引擎,WebKit伪装成 KHTML,KHTML伪装成Gecko(伪装是为了接收那些为Mozilla、safari、gecko编写的界面)
- 延伸:可以随便填(但不应该随便填)不过一般用于统计。
X-Chrome-UMA-Enabled、X-Client-Data :与 Chrome 浏览器相关的数据
参考
《图解HTTP》
《计算机网络第七版》
https://blog.csdn.net/java_3y/article/details/98868229
https://zhuanlan.zhihu.com/p/60305452?utm_source=qq
https://blog.csdn.net/qq_32998153/article/details/79678565
https://blog.csdn.net/Geek_Alex/article/details/105989166
https://blog.csdn.net/qq_40959677/article/details/94873075