OSI七层模型
Open System Interconnection(开放系统互连)
TCP/IP5层模型
- 应用层、传输层、网络层、数据链路层、物理层
Http的几种请求方法是什么?
- GET方法:发送一个请求来取得服务器上的资源
- POST方法:向URL指定的资源提交数据
- PUT方法:跟POST方法很像,也是向服务器提交数据。put方法是幂等方法,在请求时容易造成数据冗余。
- HEAD方法:只请求页面的首部
- DELETE方法:删除服务器上的资源
- OPTIONS方法:它用于获取当前URL所支持的方法。如果请求成功,会有一个Allow的头包含类似“GET,POST”这样的信息
post与get的区别
- GET参数通过URL传递,POST参数放在实体中。
- GET请求在URL中传送的参数是有长度限制的,而POST没有。(GET参数暴露在URL上,不能用来传递敏感信息)
- GET产生一个TCP数据包,header与data一起发。
POST产生两个,浏览器先发送header,等服务器响应后,再发送data
HTTP状态码?
2 开头的表示成功
- 一般就是200
3 开头的表示重定向
- 301永久重定向
- 302临时重定向
4 开头的表示客户端错误
- 403 禁止访问
- 404 请求资源不存在
5 开头的表示服务端错误
- 500 执行请求时发生错误
- 503 服务器处于超负荷状态或停机维护
301:请求浏览器是会缓存的,例如 http:// 永久重定向到 https://302:例如未登陆的用户访问用户中心会重定向到登录页面,redirect标识302。
Cookie和Session的作用以及区别?
Cookie和Session的作用:
客户端和服务器进行交互使用了HTTP协议,但是HTTP协议是无状态的。但是在有些时候是需要保存一些客户端的请求信息,识别客户端的某些状态等。就需要用到Cooike和Session了。
区别
- cookie数据保存在客户端,session数据保存在服务器端。
- cookie放在别人那,安全性不高。为了提升安全性,可以在客户端加密,服务端解密。
- 当访问量增多时,session会占用服务器的性能。
- 单个cookie在客户端的限制是3k,session没有大小限制。
- cookie仅支持ASICC码,中文需要转化才能传输;session支持任意格式
- 因此,登陆信息等重要信息存放为session;其他信息要保留存放为cookie。
分布式session原理?
分布式Session的问题
客户端发送一个请求,经过负载均衡后该请求会被分配到服务器中的其中一个,由于不同服务器有不同的web服务器(例如Tomcat),不同的web服务器中并不能发现之前保存的session信息,就会再次生成一个JSESSIONID,之前的状态就会消失。
解决方式:基于redis存储session方案
- 把项目中的session统一存在redis中,所有的参与集群的项目都在redis中取。
- spring为我们封装好了spring-session,直接引入依赖即可。
长连接和短连接?
- 在HTTP/1.0中默认使用短连接。即 客户端和服务器每进行一次HTTP操作,就建立一次连接,任务结束后就中断连接。
而从HTTP/1.1起,默认使用长连接,会在响应头加入这行代码:
Connection:keep-alive
在使用长连接的情况下,当一个网页打开完成后,客户端和服务器之间用于传输HTTP数据的TCP连接不会关闭,客户端再次访问这个服务器时,会继续使用这一条已经建立的连接。Keep-Alive不会永久保持连接,它有一个保持时间,可设定。
- HTTP协议的长连接和短连接,实质上是TCP协议的长连接和短连接。
HTTP1.0、1.1和2.0的区别?
- Http0.9 只能进行get请求
- Http1.0 添加了POST、PUT、DELETE等请求
- Http1.1 增加了长连接keepAlive、支持断点续传。
- Http2.0 多路复用、新的二进制格式、头部压缩(避免了重复header的传输)
HTTP和HTTPS的区别,https的实现原理?
区别
- http无状态无连接,而且是明文传输,不安全。
- https传输内容加密,身份验证,保证数据完整性。
https实现原理
- 首先客户端向服务器发起一个随机值,以及一个加密算法。
- 服务器收到后返回一个协商好的加密算法,以及另一个随机值
- 服务器再发送一个公钥CA
- 客户端收到以后先验证CA是否有效,如果无效则报错弹窗,有过有效则进行下一步操作。
- 客户端使用之前的两个随机值和一个预主密钥组成一个会话密钥,再通过服务器传来的公钥加密把会话密钥发送给服务器
- 服务器收到后使用私钥解密,得到两个随机值和预主密钥,然后组装成会话密钥
- 客户端在向服务器发起一条信息,这条信息使用会话秘钥加密,用来验证服务器可以收到加密的信息。
- 服务器收到信息后返回一个会话秘钥加密的信息。
- 都收到以后SSL层连接建立成功
对称加密、非对称加密优缺点?
对称加密:对称加密算法又称传统加密算法。 加密和解密使用同一个密钥。
- 优点:计算量小,加密速度快,加密效率高
- 缺点:安全性得不到保证
非对称加密: 非对称加密算法需要两个密钥:公开密钥(publickey) 和私有密钥(privatekey),公开密钥和私有密钥是一对。如果用公开密钥对数据进行加密,只有用对应的私有密钥才能解密,反之也是这样。
- 优点:算法强度复杂,安全性高
- 缺点:加密解密速度慢,只适合对少量数据进行加密
RSA应用场景:通常数据本身的加密解密使用对称加密算法,而用RSA算法加密并传输对称算法所需要的密钥。
CA证书是什么?
CA(Certification Authority),证书颁发机构,防止服务器伪造,内含公钥和私钥,使用 CA 公钥进行验证真伪。
DNS域名解析的流程?
通过UDP或者TCP传输,一般采用UDP
- 先浏览器缓存,再到host
- 本地域名服务
- 根域名服务器“.”
- 顶级域名服务器“edu, gov, com, net”
- 二级域名服务器“baidu, google”
点对点和端对端的区别?
- 点到点是主机到主机之间的通信。端到端是进程到进程之间的通信。
- 一台计算机可以与很多台计算机进行通信,使用IP对不同的计算机进行区分(点到点)。
- 一台计算机上的一个程序(例如QQ)和很多计算机上的程序通信,需要使用IP+端口才能唯一的表示一个会话。
Cookie和Session的区别?
- 主要区别是,cookie数据保存在客户端,session数据保存在服务器端
- cookie放在别人那不是很安全。
- 当访问量增多时,session会占用服务器的性能。如果要减轻服务器压力,应用cookie
- 单个cookie在客户端的限制是3k
- 所以,登陆信息等重要信息存放为session;其他信息要保留存放为cookie。
TCP和UDP的区别?
连接:
- TCP面向连接
- UDP无连接。
服务对象:
- TCP是点对点的两点间服务。
- UDP支持一对一,一对多,多对一,多对多的交互通信。
可靠性:
- TCP是可靠的。无差错,不丢失,不重复,按序到达。
- UDP不保证可靠。
首部开销:
- TCP首部开销大,首部20个字节。
- UDP首部开销小,8字节。
为什么有了TCP还需要UDP?各自适用场景?
因为UDP有简单、传输快的优势,在越来越多场景下取代了TCP,如实时游戏。现代网速的提升使得UDP的丢包率降低,如果使用应用层重传,还能够确保传输的可靠性。
如果采用TCP,一旦发生丢包,TCP会将后续的包缓存起来,接收重传的包后再继续发送,延时会越来越大。
- TCP适用场景:适用于要求可靠运输的应用,例如文件传输
- UPD适用场景:适用于实时应用,例如IP电话、视频会议、直播 等
UDP实现可靠传输的设计思路?
在应用层模仿传输层TCP的可靠性传输。
- 添加seq/ack机制,确保数据发送到对端
- 添加发送和接收缓冲区。
- 添加超时重传机制。
UPD首部字段和长度?
深入理解 TCP 协议:从原理到实战
TCP概述
一句话描述TCP协议:TCP是一个可靠的、面向连接的、基于字节流的、全双工的协议。
面向连接
面向连接的协议要求正式发送数据之前需要通过握手建立一个逻辑连接,结束通信时也是通过有序的四次挥手来断开连接
可靠的
IP是一种无连接、不可靠的协议,TCP想要在IP基础上构建可靠的传输层协议,必须有一个复杂的机制来保障。主要有:
- 对每个包提供校验和
- 包的序列号解决了接收数据的乱序、重复问题
- 超时重传
- 流量控制、拥塞控制
校验和(checksum)每个TCP包的首部中都有两字节用来表示校验和,防止在传输过程中有损坏。如果收到一个校验和有差错的保温,TCP不会发送任何确定直接丢弃它,等待发送端重传。
包的序列号保证了接收数据的乱序和重复问题 假设我们往 TCP 套接字里写 3000 字节的数据导致 TCP发送了3 个数据包,每个数据包大小为 1000 字节。
假如因为网络原因导致第二个、第三个包先到接收端,第一个包最后才到。TCP会根据他们的序号进行重新的排列然后把结果传递给上层应用程序。
如果TCP接收到重复的数据,可能的原因是超时重传了两次但这个包并没有丢失,TCP也能够根据包序号丢弃重复的数据。
超时重传、流量控制、拥塞控制 这部分内容较复杂,后面专门讲述。
是面向字节流的
TCP是一种字节流(byte-stream)协议,流的含义是没有固定的报文边界。
假设你调用2次write函数往socket里一次写500字节、800字节。write函数只是把字节拷贝到内核缓冲区,最终会以多少条报文发送出去是不确定的,如下图所示
上面出现的情况取决于诸多因素:路径最大传输单元MTU、发送窗口大小、拥塞窗口大小等。
是全双工的
在TCP中发送端和接收端可以是客户端/服务端,也可以是服务端/客户端,通信的双方在任意时刻既可接收数据也可以是发送数据。
小结
剖析TCP首部字段
TCP头部是支撑 TCP 复杂功能的基石。完整的TCP头部如下图所示:
源端口号、目标端口号
TCP报文头部里没有源ip和目标ip地址,只有源端口号和目标端口号。因为那是IP层协议的事,TCP层只有源端口和目标端口。
源IP、源端口、目标IP、目标端口构成了TCP连接的四元组。一个四元组可以唯一标识一个连接。
序列号(Sequence number)
TCP是面向字节流的协议,通过TCP传输的每个字节都分配了序列号,序列号指的是本报文段 第一个字节的序列号。
序列号加上报文的长度,就可以确定传输的是哪一段数据。
因为网络层(IP层)不保证包的顺序,TCP协议利用序列号来解决网络包乱序、重复的问题,以保证数据表以正确的顺序组装传递给上层应用。
初始序列号(Initial Sequence Number, ISN)
在建立连接之初,通信双方都会各自选择一个序列号,称之为初始序列号。在建立连接时,通信双方通过SYN报文交换彼此的ISN。
其中第2步和第3步可以合并在一起,这就是三次握手的过程。
确认号(Acknowledgment number)
TCP使用确认号ACK 来告知对方下一个期望接收的序列号,小于此确认号的所有字节都已经收到。
关于ACK的几个注意点:
- 不是所有的包都需要确认
- 不是收到了数据包就要立马确认,可以延迟一会再确认
- ACK包本身不需要被确认,否则就会无限循环了
- 确认号永远是表示小于此确认号的字节都已经收到
TCP Flags
TCP有很多种标记,有些用来发起连接同步初始序列号,有些用来确认数据包,还有些用来结束连接。
TCP定义了一个8位的字段用来表示Flags,大部分只用到了后6个,如下图所示
我们通常所说的 SYN、ACK、FIN、RST 其实只是把 flags 对应的 bit 位置为 1 而已,这些标记可以组合使用,比如 SYN+ACK,FIN+ACK 等。
最常见的有下面几个:
- SYN(synchronized):用于发起连接数据表同步双方的初始序列号
- ACK(Acknowledge):确认数据包
- RST(Reset):这个标记用来强制断开连接
- FIN(Finish):通知对方我发完了所有数据,准备断开连接,后面就不会发数据包给你了。
- PSH(Push):告知对方这些数据包收到以后应该马上交给上层应用,不能缓存下来。
窗口大小
用于表示窗口大小的“Window Size” 只有16位,也就是最大窗口是2^16^=65535字节(64KB)。
这也太小了,因此TCP协议引入了【TCP窗口缩放】选项作为窗口缩放的比例因子,比例因子的范围是0~14,其中最小值0表示不缩放。比例因子可以将窗口扩大到原来的2的n次方。
例如:窗口大小缩放前为1050,缩放因子为7,则真正的窗口大小为1050*2^7^=134400。
可选项
可选项的格式入下所示
例如MSS,kind=2,length=4,value=1460
常用的选项有以下几个:
- MSS:TCP允许的从对方接收的最大报文段
- SACK:选择确定选项
- Window Scale:窗口缩放选项
MTU与MSS
数据传输经过的过程:应用层—>表示层—>会话层—>传输层—>网络层—>链路层—>物理层
- MSS(Maximum Segment Size)最大报文段长度,工作在网络层。
MTU(Maximum Transmission Unit)最大传输单元,工作在链路层。
- IP数据包长度在超过链路的MTU时,发送之前需要分片。而TCP层为了IP层不用分片主动将包分割为MSS大小。
- MTU具有木桶效应。
三次握手
一次经典的三次握手的过程如下图所示:
- 三次握手的最重要的是交换彼此的 ISN(初始序列号)
SYN报文不携带数据,但是却占用一个序号,下一次发送时数据序列号要加一。
除了交换彼此的ISN,三次握手的另一个作用是交换一些辅助信息,比如MSS、窗口大小、窗口缩放因子、是否支持选择确认等。
为什么需要三次握手,两次不行吗?
需要三次握手才能确认双方的接收、发送都是正常的。
三次握手的状态变化
对于客户端而言:
- 初始的状态是处于
CLOSED
状态。closed并不是一个真实的状态,而是一个假想的起点和终点。 - 客户端调用connect以后会发送SYN同步报文给服务端,进入
SYN-SENT
阶段,客户端将保持这个阶段知道收到了服务端的确定包 - 收到确定包后,它将发送确认服务端SYN报文的ACK包,同时进入
ESTABLISHED
状态,表明自己已经准备好发送数据。
对于服务端而言:
- 在执行bind、listen 调用以后进入
LISTEN
状态,等待客户端连接。 - 收到客户端的SYN同步报文之后,回复确认并发送自己的SYN同步报文,这时服务端进入
SYN-RCVD
阶段等待客户端的确认 - 当收到客户端的确认报文后,进入
ESTABLISHED
状态。这时双方可以互相发数据了。
四次挥手
客户端调用close方法,主动关闭,会发送一个FIN报文给服务端,从这以后客户端不能再发送数据了,客户端进入
FIN-WAIT-1
状态。(FIN报文就是将FIN标志位设置为1)- FIN段是可以携带数据的,比如客户端可以在它最后要发送的数据块中带上 FIN段。不管FIN段是否携带数据,都需要消耗一个序列号。
- 客户端发送FIN包以后不能再发送数据,但是还可以接受服务端发送的数据,这个状态为【半关闭】
主动发起关闭的一方称为主动关闭方,另一方称为被动关闭方
- 服务端收到FIN报文后回复确认ACK报文给客户端,服务端进入
CLOSE_WAIT
,客户端收到ACK后进入FIN-WAIT-2
状态。 - 服务端也没有数据要发送了,也发一个FIN报文给客户端,然后进入
LAST-ACK
状态,等待客户端的确认ACK。(同前面一样如果FIN段没有携带数据,也需要消耗一个序列号) - 客户端收到服务端的FIN报文以后,也回复确认ACK报文,进入
TIME_WAIT
状态,等待 2 个MSL以后进入CLOSED
状态。服务端收到ACK后进入
CLOSED
状态。
为什么FIN和SYN要消耗一个序列号呢?
因为FIN和SYN信号都是需要ACK的,也就是必须回复这个信号,如果它不占用一个字节的话,是判断不出这个ACK是回复这个信号 还是回复这个信号之前的数据包的。
例如:如果FIN信号不占用一个字节,回复FIN的ACK包可能被认为是 之前发送的数据包被重新发了一次,第二次挥手无法完成,连接也就无法正常关闭了。
为什么挥手要四次,三次可以吗?
首先,因为有延迟确认的存在,是可以把第二部的ACK跟随第三步的FIN包一起发送的。
发送FIN包后,会进入半关闭状态,表示自己不会再给对方发数据了。在这种情况下,如果服务端不先立刻发送ACK表示收到。那可能客户端会因为等待服务端发数据的过程中,重发FIN包。
TIME_WAIT 存在的原因?
只有主动断开的那一方才会进入 TIME_WAIT 状态,且会在那个状态持续 2 个 MSL(Max Segment Lifetime)。
原因:
- 允许老的重复分节在网络中消逝。数据报文可能会在发送途中延迟,因此要等“迷路”的重复报文段在网络中过期失效。否则用相同源端口和目标端口创建新连接时收到姗姗来迟的数据包,造成数据错乱。
- 确保可靠实现TCP全双工 终止连接。四次挥手中,最终的ACK由主动关闭方发出,如果这个ACK丢失了,而且不维持 TIME_WAIT 直接进入 CLOSED 状态,则无法重传ACK,被动关闭方因此不能及时可靠的释放。
大量TIME_WAIT造成的影响,怎么优化?
- 在高并发短连接的TCP服务器上,当服务器处理完请求后立刻主动正常关闭连接。这个场景下会出现大量socket处于TIME_WAIT状态。
- 影响:如果客户端的并发量持续很高,此时部分客户端就会显示连接不上。
优化:
- 调整内核参数解决,打开系统的 TIMEWAIT重用和 快速回收。
- 调整短链接为长链接,可以明显改善。
为什么是2MSL?
- 1个 MSL 确保四次挥手中主动关闭方最后的ACK报文最终能到达对端
- 1个 MSL 确保对端没有收到ACK,重传的FIN报文可以到达
TCP的全连接队列和半连接队列
- 半连接队列,又称 SYN 队列
当服务端调用listen函数时,TCP的状态从Close 变为Listen,同时内核创建了这两个队列。
当客户端发起SYN到服务器时,服务端收到后发送ACK+SYN
,状态变为SYN_RCVD
,此时会将这个连接信息放入 [半连接队列] 。
一旦收到客户端的ACK,服务端就开始尝试把它加入另外一个全连接队列。
- 全连接队列,又称 Accept 队列
[全连接队列] 包含了服务端所有完成了三次握手,但是还未被应用调用 accept 取走的连接队列。此时的 socket 处于 ESTALISHED 状态。每次应用调用 accept() 会移除队列头的连接。
如果队列为空,accpet()通常会阻塞。如果全连接队列满,内核会丢掉客户端发过来的ACK,连接就无法建立。
这个过程有点像生产者消费者模式。内核是一个负责三次握手的生产者,握完手的连接会放入一个队列。我们的应用程序是消费者,取走队列中的连接进行下一步处理。
SYN Flood 攻击
SYN泛洪攻击是一种 DoS(拒绝服务攻击)。
想象一个场景:客户端大量伪造 IP 发送 SYN 包,服务端回复的 ACK+SYN 去到了一个未知的 IP 地址。会造成服务端大量的连接处于 SYN_RCVD 状态,而服务器的半连接队列大小也是有限制的,如果半连接队列满,也会出现无法处理正常请求的情况。
如何应对 SYN Flood 攻击?
- 增加SYN连接数
- 减少发送 SYN+ACK 的重试次数
SYN Cookie机制
- 现在服务器上的tcp_syncookies默认等于1,表示连接队列满时启用。0表示禁用、2表示始终启用。
- 原理就是:在三次握手的最后阶段才分配连接资源。
重传机制
- 永远记住 ACK 是表示这之前的包都已经全部收到
如果发送5000个字节的数据包,因为MSS的限制每次传输1000个字节,分5段传输。
数据包 1 发送的数据正常到达接收端,接收端回复 ACK 1001。如果数据包 2 因为某些原因未能到达服务器,其他包正常到达,这时接收端也不能ack 3 4 5数据包,因为数据包 2 还没收到,接收端只能回复 ACK1001。
快速重传机制与 SACK
快速重传的含义是:当接收端收到一个不按序到达的数据段时,TCP 立刻发送 1 个重复 ACK,当发送端收到 3 个或以上重复 ACK,就意识到之前发的包可能丢了,于是马上进行重传,不用等到重传定时器超时再重传。
这里有一个问题,发送 3、4、5 包收到的全部是 ACK=1001,快速重传解决了一个问题: 需要重传。因为除了 2 号包,3、4、5包也可能丢失,那到底是只重传 2 号包还是重传 2,3,4,5 所有包呢?
聪明的网络协议设计者,想到了一个好办法:
- 收到 3 号包的时候在 ACK 包中告诉发送端。我目前收到的最大连续的包序号是1000(ACK=1001),[1:1001]、[2001:3001]区间的包我也收到了
- 收到 4 号包的时候在 ACK 包中告诉发送端。我目前收到的最大连续的包序号是1000(ACK=1001),[1:1001]、[2001:4001]区间的包我也收到了
- 收到 5 号包的时候在 ACK 包中告诉发送端。我目前收到的最大连续的包序号是1000(ACK=1001),[1:1001]、[2001:5001]区间的包我也收到了
这样发送端就清楚知道只用重传 2 号数据包就可以了,数据包3、4、5已经确认无误的被对端收到了。这种方式被称为 SACK(Selective Acknowledgment)
TCP流量控制(滑动窗口)
TCP 会把要发送的数据放入发送缓冲区(Send Buffer),接收到的数据放入接收缓冲区(Receive Buffer),应用程序会不停的读取接收缓冲区的内容进行处理。
流量控制做的事情就是,如果接受缓冲区已满,发送端应该停止发送数据。
为了控制发送端的速率,接收端会告知客户端自己的接收窗口(rwnd),也就是接受缓冲区中空闲的部分。
TCP在收到数据包回复的 ACK 包里会带上自己接收窗口的大小,发送端会根据这个值调整发送策略。
发送窗口和接收窗口
当对方的 ACK 包中表明自己的接收窗口大小后,发送端会把自己的 [发送窗口] 限制在这个大小以内。
如果处理能力有限,导致接收缓冲区满,接收窗口大小为 0,发送端应该停止发送数据。
TCP包状态分类
从 TCP 角度而言,数据包的状态可以分为四种:
- 粉色部分#1:表示已发送并且已经收到 确认ACK 的数据包
- 蓝色部分#2:表示已发送但是还没收到 确认ACK 的数据包。如果在一段时间内没有收到 ACK,发送端需要重传这部分数据包。
- 绿色部分#3:表示未发送,但接收端有空间可以接收的数据包
- 黄色部分#4:表示未发送,而且这部分接收端没有空间接收的数据包
发送窗口(send window)和可用窗口(usable window)
发送窗口表示在某个时刻,发送端被允许发送的最大数据包大小,包括还没收到确认ACK的数据包(#2、#3区域)
可用窗口是发送端还能发送的最大数据包大小(#3区域)
如果上图中的可用窗口的46~51发送出去,可用窗口区间减小到 0,这个时候只有收到接收端的 ACK 数据,否则发送端将不能发送数据。
因此,流量控制实际上是对【发送方数据流量】的控制。
拥塞控制
流量控制这种机制确实可以防止发送端向接收端过多的发送数据,但是它只关注了发送端和接收端自身的状况,而没有考虑整个网络的通信状况。于是出现了我们今天要讲的拥塞处理。
拥塞处理涉及下面这几个算法:
- 慢启动(Slow Start)
- 拥塞避免(Congestion Avoidance)
- 快速重传(Fast Retransmit)和 快速恢复(Fast Recovery)
为了实现上面算法,TCP的每条连接都有两个核心值:
- 拥塞窗口(Congestion Window,cwnd)
- 慢启动阙值(Slow Start Threshold,ssthresh)
拥塞窗口
拥塞窗口(cwnd)和接收窗口(rwnd)有什么区别呢?
- 接收窗口是接收端的限制,是接收端还能接收的数据量大小
- 拥塞窗口是发送端的限制,是发送端在还未收到对端 ACK 之前还能发送的数据量大小
我们在TCP头部的windows字段讲的是 接收窗口大小。
拥塞窗口初始值等于操作系统的一个变量 initcwnd,最新的 linux 系统 initcwnd 默认值等于 10。
- 真正的发送窗口大小 = 【接收端窗口大小】 与 【发送端自己的拥塞窗口大小】两者的最小值。
如果接收窗口比拥塞窗口小,表示接收端处理能力不足。如果拥塞窗口小于接收窗口,表示接收端有处理能力,但网络拥塞。
因为发送端能发送多少数据,取决于两个因素:
- 对方能接收多少(接收窗口)
- 自己为了避免网络拥塞主动减少数据量(拥塞窗口)
发送端和接收端不会交换 拥塞窗口cwnd 这个值,这个值是维护在发送端本地内存中的一个值。
拥塞控制的算法本质是控制拥塞窗口(cwnd)的变化。
拥塞处理算法之:慢启动
拥塞控制是从整个网络的大局观来控制的,如果有足够的带宽,你可以选择用最快的速度传输数据。但是如果是一个缓慢的移动网络,发送数据过多,只是造成更大的网络延迟。
每个 TCP 连接都有一个拥塞窗口的限制,最初这个值很小,随着时间的推移,每次发送的数据量如果在不丢包的情况下,“慢慢”的递增,这种机制被称为【慢启动】
算法过程:
- 第一步,三次握手以后,双方通过 ACK 告诉对方自己接收窗口(rwnd)大小,准备就绪。
- 第二步,通信双方各自初始化自己的 [拥塞窗口](cwnd)大小
- 第三步,cwnd初始值较小时,每收到一个ACK,cwnd+1;每经过一个 RTT(往返时延),cwnd变为之前的两倍。
慢启动阙值(Slow Start Threshold,ssthresh)
慢启动算法的拥塞窗口(cwnd)肯定不能永无止境的指数级增长下去,它的阙值称为【慢启动阙值】。
- 当 cwnd < ssthresh 时,拥塞窗口按指数级增长(慢启动)
- 当 cwnd > ssthresh 时,拥塞窗口按线性增长(拥塞避免)
拥塞处理算法之:拥塞避免(Congestion Avoidance)
当 cwnd > ssthresh 时,拥塞窗口进入「拥塞避免」阶段。
与慢启动的区别在于:每经过一个 RTT 才将拥塞窗口加 1,不管期间收到多少个 ACK。
拥塞处理算法之:快速恢复
前面介绍的慢启动和拥塞避免是 1988 年提出的拥塞控制方案,在 1990 年又出现了两种新的拥塞控制方案:「快速重传」和「快速恢复」。快速重传我们之前了解过了,现在讲下快速恢复~
当收到三次重复 ACK 时,进入快速恢复阶段。解释为网络轻度拥塞。
- 拥塞阙值 ssthresh 降为 cwnd 的一半
- 拥塞窗口 cwnd 设置为 拥塞阙值
- 拥塞窗口进入线性增加阶段
从浏览器输入url后都经历了什么
- 先进行DNS域名解析。先查看本地hosts文件,查看有没有当前域名对应的ip地址,若有直接发起请求,没有的话会在本地域名服务器去查找,该查找属于递归查找,如果本地域名服务器没查找到,会从根域名服务器查找,该过程属于迭代查找,根域名会告诉你从哪个与服务器查找,最后查找到对应的ip地址后把对应规则保存到本地的hosts文件中。
如果想加速以上及之后的http请求过程的话可以使用缓存服务器CDN,CDN过程如下:
- 用户输入url地址后,本地DNS会解析url地址,不过会把最终解析权交给CNAME指向的CDN的DNS服务器
- CDN的DNS服务器会返回给浏览器一个全局负载均衡IP
- 用户会根据全局负载均衡IP去请求全局负载均衡服务器
- 全局负载均衡服务器会根据用户的IP地址,url地址,会告诉用户一个区域负载均衡设备,让用户去请求它。
- 区域负载均衡服务器会为用户选择一个离用户较近的最优的缓存服务器,并把ip地址给到用户
- 用户想缓存服务器发送请求,如果请求不到想要的资源的话,会一层层向上一级查找,知道查找到为止。
- 进行http请求,三次握手四次挥手建立断开连接
服务器处理,可能返回304也可能返回200
- 返回304说明客户端缓存可用,直接使用客户端缓存即可,该过程属于协商缓存
- 返回200的话会同时返回对应的数据
客户端自上而下执行代码
- 其中遇到CSS加载的时候,CSS不会阻塞DOM树的解析,但是会阻塞DOM树的渲染,并且CSS会阻塞下面的JS的执行
然后是JS加载,JS加载会影响DOM的解析,之所以会影响,是因为JS可能会删除添加节点,如果先解析后加载的话,DOM树还得重新解析,性能比较差。如果不想阻塞DOM树的解析的话,可以给script添加一个defer或者async的标签。
- defer:不会阻塞DOM解析,等DOM解析完之后在运行,在DOMContentloaed之前
- async: 不会阻塞DOM解析,等该资源下载完成之后立刻运行
进行DOM渲染和Render树渲染
- 获取html并解析为Dom树
- 解析css并形成一个cssom(css树)
- 将cssom和dom合并成渲染树(render树)
- 进行布局(layout)
- 进行绘制(painting)
回流重绘
- 回流必将引起重绘,重绘不一定引起回流