网络模型
OSI七层模型
网络协议指的是计算机网络中互相通信的对等实体之间交换信息时所必须遵守的规则的集合。
为了增强通用性和兼容性,计算机网络都被设计成 层次机构,每一层都遵守一定的规则
- 物理层:通过网线、光缆等这种物理方式将电脑连接起来。传递的数据是 比特流
- 数据链路层:将 比特流 封装成 数据帧 的格式,对0、1进行分组。数据都经过 网卡 来传输,网卡定义了唯一的MAC地址。再通过广播的形式向局域网内所有电脑发送数据 (根据数据中 MAC地址和自身对比 判断是否是发给自己的)
- 网络层:由于广播的形式太低效,为了区分哪些MAC地址属于 同一个子网, 网络层定义了IP和子网掩码,通过对IP和子网掩码进行 与运算 就知道是否是同一个子网,再通过 路由器和交换机 进行传输
- 传输层:有了网络层的 MAC + IP 地址之后,为了确定数据包是从哪个进程发送过来的,就需要端口号,通过端口来建立通信
- 会话层:负责建立和断开连接
- 表示层:为了使得数据能够被其他的计算机理解,再次将数据转换成另外一种格式,比如文字、视频、图片等
- 应用层:最高层,面对用户,提供计算机网络与最终呈现给用户的界面
协议
- 物理层:WIFI协议、蓝牙连接协议、USB接口协议
- 数据链路层:点对点协议(PPP)、广播协议(CSMA/CD)、以太网协议
- 网络层:IP、ICMP、ARP
- 传输层:TCP、UDP
- 会话层:SSL、TLS传输安全协议、RPC
- 表示层:ASCII、PNG、JPEG
- 应用层:HTTP、FTP、DNS、SMTP、TELNET
TCP/IP 四层模型
- 数据链路层,也有称作网络访问层、网络接口层。他包含了OSI模型的物理层和数据链路层,把电脑连接起来
- 网络层,也叫做IP层,处理IP数据包的传输、路由,建立主机间的通信
- 传输层,就是为两台主机设备提供端到端的通信
- 应用层,包含OSI的会话层、表示层和应用层,提供了一些常用的协议规范,比如FTP、SMPT、HTTP等
TCP/UDP
什么是TCP、为什么需要TCP
TCP 是面向连接的、可靠的、基于字节流的传输层通信协议
- 面向连接 :一定是**【一对一】**才能连接,不能像 UDP 协议可以同时向多个主机发送消息
- 可靠的 : 无论网络链路中出现了怎样的变化,TCP都可以保证一个报文一定能够到达接收端
- 字节流 : 消息是**【没有边界】的,所以无论消息有多大都可以进行传输。消息是【有序的】,当【前一个】消息没有收到的时候,即使它先收到了后面的字节,那么也不能扔给应用层去处理,同时对【重复】**的报文回自动丢弃
为什么需要TCP
- IP 层是**【不可靠】**的,它不保证数据包的按序交付和完完整性,即需要 传输层 的 TCP 协议来负责
- TCP 是一个工作在 传输层 的 可靠数据传输的服务, 它能确保接收端接收的网络包是 按序、完整、无间隔
TCP 头格式
序列号 : 在建立连接时由计算机生成的 随机数 作为初始值,通过 SYN 包传给接收端主机,每发送一次数据,就 【累加】**【数据字节数】**的大小。 用来解决网络包乱序问题
确认应答号 : 指下一次【期望】收到的 数据的序列号 ,发送端 收到 这个 确认应答 以后可以认为在这个序号以前的数据都已经被正常接收。用来解决不丢包的问题
控制位 :
-
SYN :该位为 1 时,表示希望建议连接,并在 系列号 的字段进行初始化
-
ACK : 该位为 1 时,【确认应答】的字段变为有效,TCP规定除了最初建立连接时的 SYN 包之外该位必须设置为 1
-
RST : 该位为 1 时, 表示TCP连接中出现 异常 必须 强制断开连接
-
FIN :该位为 1 时,希望断开连接。当通信结束希望断开连接时,通信双方主机之间就可以相互交换 FIN 位为 1 的TCP段
唯一确定一个TCP连接
TCP四元组可以唯一的确定一个连接
-
源端口和目标端口的字段 (16位) 是在 TCP头部中,作用是告诉 TCP协议把报文发给哪个进程
-
源地址和目的地址的字段 (32位) 是在 IP头部中,作用是通过 IP协议发送报文给对方主机
TCP如何保证可靠性传输
校验和
- 通过检验和的方式,接收端可以检测出来数据是否有差错和异常,假如有差错就会直接丢弃TCP段,重新发送
序列号 / 确认应答号
- 序列号 : 在建立连接时由计算机生成的 随机数 作为初始值,通过 SYN 包传给接收端主机,每发送一次数据,就 【累加】**【数据字节数】**的大小。 用来解决网络包乱序问题
- 确认应答号 : 指下一次【期望】收到的 数据的序列号 ,发送端 收到 这个 确认应答 以后可以认为在这个序号以前的数据都已经被正常接收。用来解决不丢包的问题
流量控制
- TCP 连接的每一方都有固定大小的缓冲空间,TCP的接收端只允许发送端发送接收端缓冲区能接纳的数据。当接收方来不及处理发送方的数据,能提示发送方降低发送的速率,防止包丢失。TCP 使用的流量控制协议是可变大小的滑动窗口协议。 (TCP 利用滑动窗口实现流量控制)
拥塞控制
- 当网络拥塞时,减少数据的发送
ARQ协议
- 每发完一个分组就停止发送,等待对方确认。在收到确认后再发下一个分组
超时重传
- 当 TCP 发出一个段后,它启动一个定时器,等待目的端确认收到这个报文段。如果不能及时收到一个确认,将重发这个报文段
TCP和UDP区别、场景
UDP 不提供复杂的控制机制,利用 IP 提供面向**【无连接】**的通信服务
UDP 头部只有 8 个字节(64位)
- 源端口和目标端口 :主要告诉UDP协议应该把报文发给哪个 进程
- 包长度 :字段保存了 UDP 首部的长度和数据的长度之和
- 校验和 :校验和为了提供可靠的UDP首部和数据而设计
UDP头部没有【首部长度】字段,TCP有
TCP有 可变长的【选项】字段,而UDP头部长度则是 不会变化的,无需多一个字段去记录UDP的首部长度
UDP头部有【包长度】字段,而TCP没有
为了网络设备硬件设计和处理方便,首部长度需要是 4 字节的整数倍
TCP 和 UDP 区别
1. 连接
- TCP 是面向连接的传输层协议,传输数据前要建立连接
- UDP是不需要连接,即刻传输数据
2. 服务对象
- TCP 是一对一的两点服务
- UDP 支持一对一、一对多、多对多的交互通信
3. 可靠性
- TCP 是可靠交付数据的,数据可以 不重复、不丢失、按需到达
- UDP不保证可靠交付数据
4. 拥塞控制、流量控制
- TCP 有拥塞控制和流量控制机制,保证数据传输的安全性
- UDP则没有,及时网络非常拥堵了,也不会影响 UDP的发送速率
5. 首部开销
- TCP首部长度较长,会有一定的开销,首部在没有使用【选项】字段时是 20 个字节,如果使用了【选项】字段则会变长
- UDP首部只有 8 个字节,并且是固定不变的,开销较小
6. 传输方式
- TCP是流式传输,没有边界,但保证顺序和可靠
- UDP是一个包一个包的发送,是有边界的,但可能会丢包和乱序
7. 分片不同
- TCP 的数据大小如果大于 MSS 大小,则会在传输层进行分片,目标主机收到后,也同样在传输层组装TCP数据包,如果中途丢失了一个分片,只需要传输丢失的这个分片
- UDP 的数据大小如果大于 MTU 大小,则会在IP层进行分片,目标主机收到后,在IP层组装完数据,接着再传给传输层,但是如果中途丢了一个分片,在实现可靠传输的UDP时则需要重传所有的数据包,这样传输效率非常差,所以通常 UDP的报文应该小于 MTU
应用场景
TCP是 面向连接,能保证数据的可靠性交付
- FTP 文件传输
- HTTP / HTTPS
UDP是 面向无连接,可以随时发送数据,再加上UDP本身的处理即简单又高效
- 包总量较少的通信,如 DNS、SNMP 等
- 视频、音频等多媒体通信
- 广播通信
Keep Alive(Tcp和HTTP)
- http keep-alive是为了让tcp活得更久一点,以便在同一个连接上传送多个http,提高socket的效率
- tcp keep-alive是TCP的一种检测TCP连接状况的机制
三次挥手和四次握手
三次握手
TCP 是面向连接的协议,所以使用TCP前必须先建立连接,而建立连接是通过三次握手来进行的
-
客户端和服务端都处于 关闭 状态。 先是 服务端 主动监听某个端口,处于 监听 状态
-
1.客户端会随机初始化序号(client_isn),将其置于TCP首部的**【序列号】字段中,同时把 SYN 标志位 置为 1。 接着把第一个SYN报文**发送给服务端,表示向服务端发起连接,该报文 不包含应用层数据,之后客户端处于 SYN-SENT 状态
-
2. 服务端收到客户端的 SYN 报文后,也随机初始化自己的序号(server_isn),将此序号填入TCP首部的**【序列号】字段中,其次把TCP首部的【确认应答号】**字段填入 client_isn + 1,接着把 SYN 和 ACK 标志位置为 1。最后把报文发给客户端,该报文也不包含应用层数据,之后服务端处于 SYN-RCVD状态
-
3.客户端收到服务端报文后,还要向服务端回应最后一个应答报文,首先该应答报文TCP首部 ACK 标志位置为 1,其次**【确认应答号】字段填入 server_isn + 1,最后把报文发送给服务端,这次报文可以携带客户端到服务器的数据**,之后客户端处于 ESTABLISHED 状态
-
服务器收到客户端的应答报文后,也进入 ESTABLISHED 状态
一旦完成三次握手,双方都处于 ESTABLISHED 状态,此时连接就已建立完成,客户端和服务端就可以相互发送数据了 (第三次握手是可以携带数据的,前两次握手是不可以携带数据的)
为什么是三次握手?不是两次、四次
-
【两次握手】: 无法防止历史连接的建立,也无法可靠的同步双方序列号,会造成双方资源的浪费,
-
【四次握手】 : 三次握手就已经理论上最少可靠连接建立,所以不需要使用更多的通信次数
为什么三次握手才可以初始化Socket、序列号和窗口大小并建立TCP连接
序列号能够保证数据包不重复、不丢弃和按序传输
原因一:避免历史连接
在 网络拥堵 情况下,客户端连续发送多次 SYN 建立连接的报文,一个【旧SYN报文】比【最新的SYN】报文早到达了服务端
- 如果是两次握手连接,就不能判断当前连接是否是历史连接,三次握手则可以在客户端准备发送第三次报文时,客户端因有足够的上下文来判断当前连接是否是历史连接
- 如果是历史连接(序列号过期或超时),则第三次握手发送的报文时 RST 报文,中止历史连接
- 如果不是历史连接,则第三次发送的报文时 ACK 报文,通信双方就会成功建立连接
原因二 :同步双方初始序列号
TCP协议的通信双方,都必须维护一个**【序列号】,序列号是可靠传输**的一个关键因素
- 接收方可以去除重复的数据
- 接收方可以根据数据包的序列号按序接受
- 可以标识发送出去的数据包中,哪些是已经被对方收到的
当客户端发送携带**【初始序列号】的 SYN 报文的时候,需要服务端回一个 ACK 应答报文,表示客户端的SYN报文已被服务端成功接收,那当服务端发送【初始序列号】**给客户端的时候,依然也要得到客户端的应答回应。这样才能确保双方的初始序列号能被可靠的同步
- 两次握手只保证了一方的初始序列号能被对方成功接收,没办法保证双方的初始序列号都能被确认接收
原因三 :避免资源浪费
只有**【两次握手】**,当客户端的 SYN 请求连接在网络中阻塞,客户端没有接收到 ACK 报文,就会重新发送 SYN, 由于没有第三次握手, 服务器不清楚客户端是否收到了自己发送的建立连接的 ACK 确认信号,所以每收到一个 SYN 就只能先主动建立一个连接,这会造成什么情况呢?
如果客户端的 SYN 阻塞了,重复发送多次 SYN 报文,那么服务器在收到请求会 建立多个冗余的无效链接,造成不必要的资源浪费
四次挥手
-
1. 客户端打算关闭连接,会向服务端发送一个 TCP 首部 FIN 标志位 被置为 1 的报文,之后进入 FIN_WAIT_1 状态
-
**2.**服务端收到该报文后,就向客户端发送 ACK 应答报文,接着服务端进入 CLOSED_WAIT 状态
-
客户端收到服务端的 ACK 应答报文后,之后进入 FIN_WAIT_2 状态
-
3.等待服务端处理完数据后,也向客户端发送 FIN 报文,之后服务端进入 LAST_ACK 状态
-
**4.**客户端收到服务端的 FIN 报文后,回一个 ACK 应答报文,之后进入 TIME_WAIT 状态
-
服务器收到了 ACK 应答报文后,就进入了 CLOSED 状态,至此服务端已经完成连接的关闭
-
客户端在经过 2MSL 一段时间后,自动进入 CLOSED 状态,至此客户端也完成连接的关闭
-
每个方向都需要 一个FIN和一个ACK,因为通常被称为 四次挥手
主动关闭连接的,才有 TIME_WAIT 状态
为什么挥手需要四次?(三次会怎样)
- 关闭连接时,客户端向服务端发送 FIN 时,仅仅表示 客户端不再发送数据了但是还能接收数据
- 服务端收到客户端的 FIN 报文时,先回一个 ACK 应答报文,而服务端可能还有数据需要处理和发送,等服务端不再发送数据时,才发送 FIN 报文给客户端来表示同意现在关闭连接
服务端通常需要等待完成数据的发送和处理,所以服务端的 ACK 和 FIN 一般都会分开发送,从而比三次握手导致多了一次
如果最后一次挥手出现丢包,客户端最后回复的ACK丢了
- 客户端在回复**「ACK」后,会进入 TIME-WAIT 状态,开始长达 2MSL 的等待,服务端因为没有收到「ACK」**的回复,会重试一段时间,直到服务端重试超时后主动断开
- 或者等待新的客户端接入后,收到服务端重试的**「FIN」消息后,回复「RST」消息,在收到「RST」**消息后,复位服务端的状态。
TIME_WAIT
为什么需要TIME_WAIT状态
主动发起关闭连接的一方,才会有 TIME_WAIT 状态
- 防止具有相同**【四元组】**(源IP地址,源端口,目标IP地址,目标端口) 的 旧数据包被收到
- 等待足够长的时间 即保证最后的 ACK 能让 被动关闭方 接收,从而 帮助其正常关闭
TIME_WAIT等待足够长的情况能够遇到两种情况:
- 服务端 正常 收到四次挥手的最后一个 ACK 报文,服务端正常关闭连接
- 服务端 没有 收到四次挥手的最后一个 ACK 报文,会重发 FIN 关闭连接报文并等待新的 ACK 报文
客户端在 TIME_WAIT 状态等待 2MSL 时间后,就可以 保证双方的连接都可以正常的关闭
为什么TIME_WAIT等待的时间是 2MSL?
MSL是什么
MSL 是 报文最大生存时间,它是任何报文在网络上存在的最长时间,超过这个时间报文将被丢弃。因为 TCP 报文基于是 IP协议 的,而 IP头中有一个 TTL 字段,是IP数据报可以经过的最大路由数,每经过一个处理它的路由器此值就 减 1,当此值为 0 则数据报将被丢弃,同时发送 ICMP 报文通知 源主机
MSL 与 TTL 的区别
-
MSL 的单位是时间,而 TTL 是经过路由跳数
-
MSL 应该要大于等于 TTL 消耗为0的时间,以确保报文已被自然消亡
TIME_WAIT 等待2倍的 MSL
-
网络中可能存在来自发送方的数据包,当这些数据包被接受方处理后又会向对方发送响应,所以 一来一回需要等待 2 倍的时间
-
2MSL 的时间是从 客户端接受到FIN后发送ACK 开始计时的。如果在 TIME-WAIT 时间内,因为客户端的 ACK 没有传输到服务端,客户端又接受到了服务端重发的 FIN 报文,那么 2MSL 时间将重新计时
在Linux系统里 2MSL 默认是 60 秒,那么一个 MSL 也就是 30 秒。 Linux系统停留在 TIME_WAIT的时间为固定的 60 秒
如果要修改 TIME_WAIT 的时间长度,只能修改 Linux 内核代码里 TCP_TIMEWAIT_LEN的值,并重新编译 Linux 内核
TIME_WAIT过多有什么危害?
过多的 TIME-WAIT 状态主要的危害有两种:
- 内存资源占用
- 端口资源资源的占用,一个TCP连接至少消耗一个本地端口
如果发起连接一方的 TIME_WAIT 状态过多,占满了所有端口资源,则会导致无法创建新连接
客户端受端口资源限制:
- 客户端 TIME_WAIT 过多,就会导致端口资源被占用,因为端口就 65536个,被占满就会导致无法创建新的连接
服务端受系统资源限制:
- 由于一个四元组表示 TCP 连接,理论上服务端可以建立很多连接,服务端确实只监听了一个端口,但是会把连接扔给处理线程,所以理论上监听的端口可以继续监听。但是线程池处理不了那么多一直不断的连接。所以当服务端出现大量 TIME_WAIT时,系统资源被占满时,会导致处理不过来新的连接
如何优化 TIME_WAIT?
net.ipv4.tcp_tw_reuse 和 tcp_timestamps
- Linux内核参数开启后,可以 复用处于 TIME_WAIT 的 socket 为新的连接所用
为什么客户端和服务端的初始序列号 ISN 是不相同?
如果一个已经失效的连接被重用了,但是该旧连接的历史报文还残留在网络中
- 如果序列号相同,那么就无法分辨出该报文是不是历史报文,如果历史报文被新的连接接收了,则会产生数据错乱
- 每次建立连接前重新初始化一个序列号主要是为了 通信双方能够根据序列号将不属于本连接的报文段丢弃,另一方面为了安全性,防止黑客伪造的相同序列号的 TCP 报文被对方接收
滑动窗口
-
TCP每发送一个数据,都要进行一次确认应答。当上一个数据包收到且应答了,再发送下一个。这样的传输方式有一个缺点 : 数据包的往返时间越长,通信的效率就越低
-
TCP引入了 窗口 这个概念,可以指定窗口大小,窗口大小指 无需等待确认应答,就可以继续发送数据的最大值
**实现: **
-
操作系统开辟的一个缓存空间,发送方主机在等到确认应答返回之前,必须在缓冲区中保留已发送的数据。如果按期收到确认应答,此时数据就可以从缓存区清除
-
假设窗口大小为 3 个 TCP段,那么发送方就可以**【连续发送】** 3 个 TCP段,并且中途若有ACK丢失,可以通过**【下一个确认应答进行确认】**
-
-
图中的 ACK 600 确认应答报文丢失,也没关系,因为可以通过下一个确认应答进行确认,只要发送方收到了 ACK 700 确认应答,说明 700 以前的所有数据 【接收方】都收到了。这个模式就叫 累计确认 或者 累计应答
窗口大小
窗口大小由哪一方决定?
-
TCP头里有一个字段叫 Window,也就是窗口大小
-
这个字段是接受方告诉发送方自己还有多少缓冲区可以接收数据。于是发送方就可以根据这个接收方的处理能力来发送数据,而不会导致接收方处理不过来
-
通常窗口的大小是由接收方的窗口大小来决定的
-
发送方发送的数据大小 不能超过 接收方的窗口大小,否则接受方就无法正常接收到数据
接收窗口和发送窗口的大小是相等的吗?
- 并不是完全相等,接受窗口的大小是 约等于 发送窗口的大小的
- 因为滑动窗口并不是一成不变的。比如,当接受方的应用进程读取数据的速度非常快的话,这样接收窗口可以很快空缺出来。那么新的接收窗口的大小,是通过 TCP 报文中的 Windows 字段来告诉发送方。那么这个传输过程是存在时延的,所以接收窗口是和发送窗口的约等于的关系
发送方和接收方的滑动窗口
发送方的滑动窗口
下图为发送方缓存的数据,根据处理的情况分为四个部分,其中深蓝色方框是发送窗口,紫色方框是可用窗口
- #1 是已发送并收到ACK确认的数据:1~31 字节
- #2 是已发送但未收到ACK确认的数据:32~45 字节
- #3 是未发送但总大小在接收方处理范围内 (接收方还有空间) :46~51 字节
- #4 是未发送但总大小超过接收方处理范围 (接受方没有空间) :52字节以后
当发送方把数据【全部】都一下发送出后后,可用窗口大小就为 0 了,表明可用窗口耗尽,在没收到 ACK 确认之前是无法继续发送数据了
当收到之前发送的数据 32~36 字节的 ACK 确认应答后,如果发送窗口的大小没有变化,则滑动窗口往右边移动5个字节,因为有5个字节的数据被应答确认,接下来 52 ~ 56 字节又变成了可用窗口,那么后续也就可以发送 52 ~ 56 这5个字节的数据了
接收方的滑动窗口
- #1 + #2 已成功接收并确认的数据(等待应用进程读取)
- #3 未收到数据但可以接收的数据
- #4 未收到数据并不可以接收的是
其中三个接收部分,使用两个指针进行划分:
- RCV. WND : 表示接收窗口的大小,它会通告给发送方
- RCV.NXT : 是一个指针,它指向期望从发送方发送来的下一个数据字节的序列号,也就是 #3 的第一个字节
- 指向 #4 的第一个字节是个相对指针,它需要 RCV.NXT 指针加上 RCV.WND 大小的偏移量,就可以指向 #4 的第一个字节
程序是如何表示发送方的四个部分的呢?
TCP滑动窗口方案使用三个指针来跟踪在四个传输类别中的每一个类别中的字节。其中两个指针是绝对指针(指特定的序列号),一个是相对指针(需要做偏移)
- SND.WND :表示发送窗口的大小 (大小是由接收方指定的)
- SND.UNA:是一个绝对指针,它指向的是已发送但未收到确认的第一个字节的序列号,也就是 #2 的第一个字节
- SND.NXT : 也是一个绝对指针,它指向未发送但可发送范围的第一个字节的序列号,也就是 #3 的第一个字节
- 指向 #4 的第一个字节是个相对指针,它需要 SND.UNA 指针加上 SND.WND 大小的偏移量,就可以指向 #4 的第一个字节了
可用窗口大小的计算 = SND.WND - (SND.NXT - SND.UNA)
流量控制
-
如果一直发数据给对方,但对方处理不过来,那么就会导致触发重发机制,从而导致网络流量的无端的浪费
-
流量控制 : TCP提供了一种机制可以让【发送方】 根据 【接收方】的 实际接受能力控制发送的数据量
SND.UNA:绝对指针,指向的是已发送但未收到确认的第一个字节的序列号
SND.NXT :绝对指针,指向的是已发送但未收到确认的第一个字节的序列号
RVC.NXT:指针,指向期望从发送方发送来的下一个数据字节的序列号
假设以下场景: 客户端是接收方,服务端是发送方,接受窗口和发送窗口都为 200,假设两个设备在整个传输过程中都保持相同的窗口大小,不受外界影响(流量控制的过程)
- 客户端向服务端发送请求数据报文,服务端收到请求报文后,发送确认报文和 80 字节的数据,可用窗口 Usable 减少为 120 字节,同时 SND.NXT 指针也向右偏移 80 字节后,指向 321,意味着下次发送数据时,序列号是 321
- 客户端收到 80 字节数据后,于是接受窗口往右移动 80 字节,RCV.NXT 也就指向 321,这意味着客户端期望的下一个报文的序列号是 321,接着发送确认报文给服务端
- 服务端再次发送了 120 字节数据,于是可用窗口耗尽为 0 ,服务端无法再继续发送数据
- 客户端收到 120 字节的数据后,于是接受窗口往右移动 120 字节,RCV.NXT 也就指向 321, 于是可用窗口 Usable 增大到80
- 服务端收到对 80 字节数据的确认报文后,SND.UNA 指针往右偏移后指向 321,于是可用窗口 Usable 增大到80
- 服务端收到对 120 字节数据的确认报文后,SND.UNA 指针往右偏移后指向 441,于是可用窗口 Usable 增大到 200
- 服务端可以继续发送了,于是发送了 160 字节的数据后,SND.NXT 指向 601,于是可用窗口 Usable 减少到 40
- 客户端收到 160 字节后,接收窗口往右移动了 160 字节,RCV.NXT 也就是指向了 601,接着发送确认报文给服务端
- 服务端收到对 160 字节数据的确认报文后,发送窗口往右移动了 160 字节,于是 SND.UNA 指针偏移了 160 后指向 601,可用窗口 Usable 也就增大至了 200
操作系统缓冲区与滑动窗口的关系
实际上,发送窗口和接受窗口中所存放的字节数,都是放在操作系统内存缓冲区中的,而操作系统的缓冲区,会被操作系统调整
当应用程度没有及时读取缓存时,发送窗口和接受窗口的变化
- 客户端作为发送方,服务端作为接收方,发送窗口和接受窗口初始大小为 360
- 服务端非常的繁忙,当收到客户端的数据时,应用层不能及时读取数据
说明过程:
- 客户端发送 140 字节数据后,可用窗口变为 220 (360 - 140)
- 服务端收到 140 字节数据,但是服务端非常繁忙,应用进程只读取了 40 个字节,还有 100 字节占用着缓冲区,于是接收窗口收缩到了 260 (360 - 100), 最后发送确认信息时,将窗口大小通告给客户端
- 客户端收到确认和窗口通告报文后,发送窗口减少为 260
- 客户端发送 180 字节数据,此时可用窗口减少为 80
- 服务端收到 180 字节数据,但是应用程序没有读取任何数据,这 180 字节字节就留在了缓冲区,于是接收窗口收缩到了80 (260 - 180),并在发送确认信息时,通过窗口大小给客户端。
- 客户端收到确认和窗口通告报文后,发送窗口减少为 80
- 客户端发送 80 字节数据后,可用窗口耗尽
- 服务端收到 80 字节数据,但是应用程序依然没有读取任何数据,这 80 字节留在了缓冲区,于是接收窗口收缩到了 0,并在发送确认信息时,通过窗口大小给客户端
- 客户端收到确认和窗口通告报文后,发送窗口减少为 0
最后窗口都收缩为0,也就是发生了窗口关闭。当发送方可用窗口变为 0 时,发送方实际上会定时发送窗口探测报文,以便知道接收方的窗口是否发生了改变
当服务端系统资源非常紧张的时候
操作系统可能会直接减少了接受缓冲区大小,这时应用程序又无法及时读取缓存数据,会出现数据包丢失的现象
- 客户端发送 140 字节的数据,于是可用窗口减少到了 220
- 服务端因为现在非常繁忙,操作系统于是把接收缓存减少了 120 字节,当收到 140 字节数据后,又因为应用程序没有读取任何数据,所以 140 字节留在了缓冲区中,于是接收窗口大小从 360 收缩成了 100, 最后发送确认信息时,通告窗口大小给对方
- 此时客户端因为还没有收到服务端的通告窗口报文,所以不知道此时接收窗口收缩成了 100,客户端只会看自己的可用窗口还有 220,所以客户端就发送了 180 字节数据,于是可用窗口减少到了 40
- 服务端收到了 180 字节数据时,发现数据大小超过了接收窗口的大小,于是就把数据包丢失了
- 客户端收到第 2 步 时,服务端发送的确认报文和通告窗口报文,尝试减少发送窗口到 100,把窗口的右端向左收缩了 80,此时可用窗口的大小就会出现 负值
如果发生了先减少缓存,再收缩窗口,就会出现丢包的现象
为了防止这种情况,TCP规定是不允许同时减少缓存又收缩窗口,而是采用先收缩窗口,过段时间再减少缓存,这样可以避免了丢包的情况
窗口关闭
TCP 通过让接收方指明希望从发送方接收的数据大小 (窗口大小) 来进行流量控制
如果窗口大小为 0 时,就会阻止发送方给接收方传递数据,直到窗口变为非 0 为止,这就是窗口关闭
窗口关闭潜在的危险
接收方向发送方通告窗口大小时,是通过 ACK 报文来通告的
- 当发生窗口关闭时,接收方处理完数据后,会向发送方通告一个窗口非0的 ACK 报文,如果这个通告窗口的 ACK 在网络中丢失了
- 会导致发送方一直等到接收方 非 0 窗口通知,接收方也一直等待发送方的数据。这种相互等待的过程,造成了死锁的现象
TCP如何解决窗口关闭时,潜在的死锁现象
TCP 为每个连接设有一个持续定时器,只要 TCP 连接一方收到对方的零窗口通知,就启动持续计时器
如果持续计时器超时,就会发送 窗口探测报文,而对方在确认这个探测报文时,给出自己现在的接收窗口大小
- 如果接收窗口 仍然是 0,那么收到这个报文的一方就会重新启动持续计时器
- 如果接收窗口 不是 0,那么死锁的局面就可以被打破了
窗口探测的次数一般为 3 次,每次大约 30 - 60 秒(不同的实现可能会不一样)。如果 3 次过后接收窗口还是 0 的话,有的 TCP 实现就会发 RST 报文来中断连接
拥塞控制
-
在网络出现拥堵时,如果继续发送大量数据包,可能会导致数据包时延、丢失等,这时TCP就回重传数据,但是一重传就会导致网络的负担更重,于是会导致更大的延迟以及更多的丢包,这个情况就会进入恶性循环被不断地放大
-
当网络发送拥塞时,TCP会自我牺牲,降低发送的数据量,就有了 拥塞控制,控制的目的就是 避免【发送方】的数据填满整个网络
-
为了在【发送方】调节所要发送数据的量,定义了一个叫做 【拥塞窗口】 的概念
怎么判断当前网络出现了拥塞?
只要 【发送方】没有在规定时间内接收到 ACK 应答报文,也就是 发生了超时重传,就会认为网络出现了拥塞
拥塞窗口和发送窗口
拥塞窗口 cwnd 是发送方维护的一个的状态变量,它会根据 网络的拥塞程度动态变化的
发送窗口 swnd 和 接收窗口 rwnd 是约等于的关系,那么由于加入了拥塞窗口的概念后,此时发送窗口的值是 swnd = min(cwnd,rwnd),也就是拥塞窗口和接收窗口中的最小值
拥塞窗口 cwnd 变化的规则:
- 只要网络中没有出现拥塞, cwnd 就会增大
- 但网络中出现了拥塞, cwnd 就减少
拥塞控制算法
- 慢启动
- 拥塞避免
- 拥塞发生
- 快速恢复
慢启动
- TCP在刚建立连接的完成后,首先是有个慢启动的过程(一点一点的提高发送数据包的数量)
- 慢启动算法规则 :当发送方每收到一个 ACK,拥塞窗口 cwnd 的大小就会 加 1
假定拥塞窗口 cwnd 和 发送窗口 swnd 相等
- 连接建议完成后,一开始初始化 cwnd = 1,表示可以传一个 MSS 大小的数据
- 当收到 一个 ACK 确认应答后, cwnd 增加 1,于是一次能够发送 2个
- 当收到 2个 的 ACK 确认应答后, cwnd 增加2,于是就可以比之前多发2个,所以这一次能够发送 4 个
- 当这 4个 的 ACK 确认到来的时候,每个确认 cwnd 增加1,4个确认 cwnd增加4,于是就可以比之前多发 4个,所以这一次能够发送 8个
慢启动涨到什么时候?
有一个叫慢启动门限 ssthresh 状态变量
- 当 cwnd < ssthresh ,慢启动算法
- 当 cwnd >= ssthresh,拥塞避免算法
拥塞避免算法
- 当拥塞窗口 cwnd 【超过】 慢启动门限 ssthresh 就会进入 拥塞避免算法。一般来说 ssthresh 的 大小是 65535 字节
- 那么进入拥塞避免算法后,规则 : 每当收到一个 ACK时, cwnd 增加 1/cwnd
接着慢启动的例子, 假定 ssthresh 为 8
- 当 8 个 ACK 应答确认到来时,每个确认增加 1/8,8个ACK确认 cwnd 一共增加1,于是这一次能够发送 9个 MSS 大小的数据,变成了线性增长
- 拥塞避免算法就是将原本慢启动的算法的指数增长变成了线性增长,还是增长阶段,但是速度缓慢了一些
- 一直增长着后,网络慢慢进入了拥塞状况,就会出现丢包现象,需要对丢失的数据包进行重传。当触发了重传机制,也就进入了**【拥塞发生算法】**
拥塞发生
当网络出现拥塞,也就是会发生数据包重传,重传机制主要有两种:
-
超时重传
-
快速重传
发生超时重传的拥塞发生算法
当发生了【超时重传】,则就会使用拥塞发生算法
这个时候,ssthresh 和 cwnd 的值会发生变化
- ssthresh 设为 cwnd / 2
- cwnd 重置为 1
接着就重新开始 慢启动, 慢启动是会突然减少数据流的。一旦【超时重传】,马上回到解放前。这种方式太激进了,反应也很强烈,会中造成网络卡顿
发生快速重传的拥塞发生算法
【快速重传算法】。 当接收方发现丢了一个中间包的时候,发送三次前一个包的ACK,于是发送端就会快速地重传,不必等待超时再重传
TCP认为这种情况不严重,因为大部分没丢,只丢了一小部分
- cwnd = cwnd / 2,设置为原来的一半
- ssthresh = cwnd
- 进入快速恢复算法
快速恢复
- 快速重传和快速恢复算法一般同时使用
- 快速恢复算法认为,还能收到 3个重复ACK 说明网络也不那么糟糕,所以没有必要像 RTO 超时那么强烈
进入快速恢复之前, cwnd 和 ssthresh 已被更新了:
- cwnd = cwnd / 2,也就是设置为原来的一半
- ssthresh = cwnd
进入快速恢复算法如下:
- 拥塞窗口 cwnd = ssthresh + 3 (3的意思确认有3个数据包被收到了)
- 重传丢失的数据包
- 如果再收到重复的 ACK,那么 cwnd 增加1
- 如果收到新数据的 ACK 后,把 cwnd 设置为第一步中的 ssthresh 的值,原因是该 ACK 确认了新的数据,说明从 duplicated ACK时的数据都已收到,该恢复过程已经结束,可以回到恢复之前的状态了,也即再次进入拥塞避免状态
HTTP
HTTP 是计算机里专门在两点之间传输文字、图片、音频等超文本数据的约定和规范
协议
- HTTP 是一个用在计算机里的协议。它使用计算机能够理解的语言确立了一种计算机之间交流通信的规范,以及相关的各种控制和错误处理方式
传输
- HTTP 是一个 双向传输协议,有两个最基本的参与者 A 和 B,从 A 开始到 B 结束,数据在 A 和 B 之间双向而不是单向流动。通常把先发起传输动作的 A 叫做 请求方,把后接到传输的 B 叫做应答方 或者 响应方
超文本
- HTTP 传输的不是 TCP/UDP 这些底层协议里被切分的杂乱无章的二进制包,而是完整的、有意义的数据,可以被浏览器、服务器这样的上层应用程序处理
URI和URL
- URI 是统一资源标示符,定了URI,HTTP就可以解析出对象
- URL 统一资源定位符,描述了一台特定服务器上某资源的特定位置
- URL是URI的子集
HTTP方法、报文
HTTP方法
- GET : 请求服务器的某个资源
- **POST : ** 向服务器输入数据。包含主体
- PUT : 向服务器写入文档。包含主体
- DELETE : 删除指定的资源
- HEAD : ** 获得报文首部,与GET方法一样但不返回报文主体部分**,用于确定URI有效性及资源更新的日期时间
- OPTIONS: 询问支持的方法,用来查询针对请求URI指定的资源支持的方法
- TRACE : 追踪路径,Web服务器端将之前的请求通信环回给客户端的方法
HTTP报文
HTTP报文分为 请求报文 和 响应报文
HTTP报文包括以下三个部分:
- 起始行:**请求报文中 ** HTTP的方法、请求资源的路径、HTTP的版本号
- 响应报文中 : HTTP的版本号、状态码、原因短语
- 首部字段:有零个或多个首部字段。每个首部字段都包含一个名字和一个值,两者之间用 : 来分隔
- 主体:请求主体中包括了要发送给Web服务器的数据。响应主体中装载了要返回给客户端的数据
HTTP 首部
若HTTP首部字段重复了会如何
- 这种情况在规范内尚未明确,根据浏览器内部处理逻辑的不同,结果可能并不一致
- 有些浏览器会优先处理第一次出现的首部字段,而有些则会优先处理最后出现的首部字段
4中HTTP首部字段类型
- 通用首部字段(General Header Fields):既可以出现在请求报文中,也可以出现在响应报文中
- 请求首部字段(Request Header Fields):提供更多有关请求的信息
- 响应首部字段(Response Header Fields):提供更多有关响应的信息
- 实体首部字段(Entity Header Fields):描述主体的长度和内容,或者资源自身
常见的首部实例
-
Date:Tue, 30ct 2021 12:00:00 GMT 服务器产生响应的日期
-
Accept: image/gif,image/jpeg, text/html 客户端可以接受GIF图片和JPEG图片以及HTML
-
Content-type: image/gif 实体的主体部分的类型
-
Content-length: 15040 实体的主体部分包含了15040字节的数据
通用首部字段
Cache-Control
操作缓存的工作机制
Cache-Control : private, max-age = 0, no-cache
Cache-Control : public 其他用户也可利用缓存
Cache-Control : private 响应只以特定的用户作为对象
Cache-Control : no-cache 防止从缓存中返回过期的资源
客户端发送的请求中包含表示:客户端将不会接收缓存过的响应
服务端返回的响应中包含表示:缓存服务器不能对资源进行缓存
Cache-Control : no-cache=Location 若报文首部字段Cache-Control中对no-cache字段名具体指定参数值,那么客户端在接收到这个被指定参数值的首部字段对应的响应报文后,就不能使用缓存
Cache-Control : no-store 表示请求或响应中包含机密信息,则指令规定缓存不能在本地存储请求或响应的任一部分
Cache-Control : max-age=604800(单位:秒)
当客户端发送的请求中包含该指令时,如果判定 缓存资源的缓存时间数值比指定时间的数值更小,那么客户端就接收缓存的资源。若max-age值为0,那么缓存服务器通常需要将请求转发给源服务器
当服务器返回的响应中包含该指令时,缓存服务器将不对资源的有效性再作确认,而max-age数值代表资源保存为缓存的最长时间
Cache-Control : s-maxage=604800 (单位:秒) s-maxage指令的功能和max-age指令的相同,不同点是同一用户重复返回响应的服务器来说,这个指令没有任何作用
Date
创建HTTP报文的日期和时间
Connection
管理持久连接,控制不再转发给代理的首部字段
Connection : close HTTP/1.1版本的默认连接都是持久连接。客户端会在持久连接上连续发送请求。当服务器端想明确断开连接时,则指定Connection首部字段的值为Close
Connection : Keep-Alive HTTP/1.1之前的HTTP版本的默认连接都是非持久连接。为此,如果想在旧版本的HTTP协议上维持持续连接,则需要指定Connection首部字段的值为Keep-Alive
Upgrade
用于检测HTTP协议及其他协议是否可使用更高的版本进行通信,其参数值可以用来指定一个完全不同的通信协议
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nlmRUBUS-1649846358944)(D:\TyporaImage\计算机网络 \image-20211015145122246.png)]
Upgrade首部字段产生作用的Upgrade对象仅限于客户端和邻接服务器之间。即使用首部字段Upgrade时,还需要额外指定 Connection:Upgrade
对于附有首部字段Upgrade的请求,服务器可用101 Switching Protocols状态码作为响应返回
请求首部字段
host
请求的资源所处的互联网主机名和端口号,Host首部字段在HTTP/1.1规范内是唯一一个必须被包含在请求内的首部字段
Authorization
告知服务器,用户代理的认证信息。想要通过服务器认证的用户代理会在接收到返回的401状态码响应后,把首部字段Authorization加入请求中。共用缓存在接收到含有Authorization首部字段的请求时的操作处理会略有差异
Accept
通知服务器,用户代理能够处理的媒体类型及媒体类型的相对优先级。可使用type/subtype这种形式,一次指定多种媒体类型
Accpet-Charset
通知服务器,用户代理支持的字符集及字符集的相对优先顺序。另外,可一次性指定多种字符集。与首部字段Accept相同的是可用权重q值来表示相对优先级
Accept-Encoding
通知服务器,用户代理支持的内容编码及内容编码的优先级顺序。可一次性指定多种内容编码
Accept-Language
通知服务器,用户代理能够处理的自然语言集(指中文或英文等),以及自然语言集的相对优先级。可一次指定多种自然语言集
响应首部字段
Accept-Ranges: bytes
告知客户端,服务器是否能处理范围请求,以指定获取服务器端某个部分的资源
可指定的字段值有两种,可处理范围请求时指定其为bytes,反之则指定其为none
Age : 600
告知客户端,服务器在多久前创建了响应。字段值的单位为秒
若创建该响应的服务器是缓存服务器,Age值是指缓存后的响应再次发起认证到认证完成的时间值。代理创建响应时必须加上首部字段Age
ETag : "82e22293907ce725faf67773957acd12"
告知客户端实体标识。将资源以字符串形式做唯一性标识的方式。服务器会为每份资源分配对应的ETag值
当资源更新时,ETag值也需要更新。生成ETag值时,并没有统一的算法规则,而仅仅是由服务器来分配
- 强ETag值,不论实体发生多么细微的变化都会改变其值
- 弱ETag值,只用于提示资源是否相同。只有资源发生了根本改变,产生差异时才会改变ETag值。这时,会在字段值最开始处附加W/
Location
将响应接收方引导至某个与请求URI位置不同的资源。基本上,该字段会配合 3xx:Redirection 的响应,提供重定向的URI
几乎所有的浏览器在接收到包含首部字段Location的响应后,都会强制性地尝试对已提示的重定向资源的访问
Proxy-Authenticate
把由代理服务器所要求的认证信息发送给客户端
它与客户端和服务器之间的HTTP访问认证的行为相似,不同之处在于其认证行为是在客户端与代理之间进行的。而客户端与服务器之间进行认证时,首部字段WWW-Authorization有着相同的作用
WWW-Authenticate
用于HTTP访问认证。它会告知客户端适用于访问请求URI所指定资源的认证方案(Basic或是Digest)和带参数提示的质询(challenge)
状态码401 Unauthorized响应中,肯定带有首部字段WWW-Authenticate
实体首部
Content-Type : text/html; charset=UTF-8
实体主体内对象的媒体类型
Content-Encoding : gzip
服务器对实体的主体部分选用的内容编码方式。内容编码是指在不丢失实体信息的前提下所进行的压缩
Content-Length
实体主体部分的大小(单位是字节)。对实体主体进行内容编码传输时,不能再使用Content-Length首部字段
Content-Location : http://www.hackr.jp/index-ja.html
给出与报文主体部分相对应的URI。和首部字段Location不同,Content-Location表示的是报文主体返回资源对应的URI
Allow : GET,HEAD
通知客户端能够支持Request-URI指定资源的所有HTTP方法
当服务器接收到不支持的HTTP方法时,会以状态码405 Method Not Allowed作为响应返回
HTTP常见的状态码
2XX
-
【200 OK】:最常见的成功状态码,表示一切正常。如果是非 HEAD 请求,服务器返回的响应头都会有 body 数据
-
【204 No Content】 :常见的成功状态码,与 200 OK 基本相同,但响应头没有 body 数据
-
206【Partial Content】 :应用于 HTTP 分块下载或断点续传,表示响应返回的 body 数据并不是资源的全部,而是其中的一部分,也是服务器处理成功的状态
3XX
- 【301 Moved Permanently】: 表示永久重定向,说明请求的资源已经不存在了,需改用新的 URL再次访问
- 【302 Found】: 表示临时重定向,说明请求的资源还在,但暂时需要另一个 URL 来访问
- 301 和 302 都会在响应头里使用字段 Location,指明后续要跳转的 URL,浏览器会自动重定向新的 URL
- 【304 Not Modified】 : 不具有跳转的含义,表示资源未修改,重定向已存在的缓冲文件,也称缓存重定向,用于缓存控制
4XX
- 【400 Bad Request】: 表示客户端请求的报文有错误,但只是个笼统的错误
- 【403 Forbidden】 : 表示服务器禁止访问资源,并不是客户端的请求出错
- 【404 Not Found】: 表示请求的资源在服务器上不存在或未找到,所以无法提供给客户端
5XX
- 【500 Internal Server Error】 : 与 400类型,是个笼统通用的错误码,服务器发生了什么错误,并不知道
- 【501 Not Implemented】: 表示客户端请求的功能还不支持
- 【502 Bad Gateway】: 通常是服务器作为网关或代理时返回的错误码,表示服务器自身工作正常,访问后端服务器发生了错误
- 【503 Service Unavailable】: 表示服务器当前很忙,暂时无法响应服务器
GET 和 POST的区别
- 请求参数:GET请求参数是通过URL传递的,多个参数以&连接,POST请求放在request body中
- 安全性:POST比GET安全,GET请求在浏览器回退时是无害的,而POST会再次请求
- 编码方式:GET请求只能进行url编码,而POST支持多种编码方式
- 请求缓存:GET请求会被缓存,而POST请求不会,除非手动设置
- 对参数的数据类型:GET只接受ASCII字符,而POST没有限制
- 收藏为书签:GET请求支持,POST请求不支持
- 历史记录:GET请求参数会被完整保留在浏览历史记录里,而POST中的参数不会被保留
报文上的区别
- 不带参数的区别就仅仅是报文的前几个字符不同
- 带参数时的报文区别,在约定中,GET 方法的参数应该放在 url 中,POST 方法参数应该放在 body 中
POST /uri HTTP/1.1 \r\n
GET /uri HTTP/1.1 \r\n
GET /index.php?name=qiming.c&age=22 HTTP/1.1 Host: localhost POST /index.php HTTP/1.1 Host: localhost Content-Type: application/x-www-form-urlencoded name=qiming.c&age=22
HTTP无状态
- 好处: 服务器不会去记忆 HTTP 的状态,所以不需要额外的资源来记录状态信息,这能减轻服务器的负担,能够把更多的 CPU 和 内存用来对外提供服务
- 坏处: 服务器没有记忆能力,它在完成有关联性的操作时会非常麻烦
Cookie 通过在请求和响应报文中写入 Cookie 信息来控制客户端的状态
http和https的区别?
- HTTP 是超⽂本传输协议,信息是明文传输,存在安全风险的问题。HTTPS 在TCP 和 HTTP 之间加⼊了 SSL/TLS 安全协议,使得报文能够加密传输
- TCP 三次握⼿之后便可进⾏ HTTP 的报⽂传输。 HTTPS 在 TCP 三次握⼿之后,还需进⾏ SSL/TLS 的握⼿过程,才可进入加密报文传输
- HTTP 的端口号是 80,HTTPS 的端⼝号是 443
- HTTPS 协议需要向 CA(证书权威机构)申请数字证书,来保证服务器的身份是可信的
https
https怎么确保安全?
HTTPS 在 HTTP 与 TCP 层之间加⼊了 SSL/TLS 协议
- 混合加密、摘要算法、数字证书
混合加密
HTTPS 采⽤的是非对称加密和 对称加密 结合的「混合加密」⽅式
- 在 通信建⽴前 采⽤ ⾮对称加密 的⽅式交换「会话秘钥」,后续就不再使⽤ 非对称加密
- 在 通信过程中 采⽤ 对称加密 的「会话秘钥」的⽅式加密明文数据
采用 混合加密 的原因 :
-
非对称加密 使⽤两个密钥:公钥和私钥,公钥可以任意分发⽽私钥保密,解决了密钥交换问题但速度慢
-
对称加密 只使⽤⼀个密钥,密钥必须保密,⽆法做到安全的密钥交换但速度快
摘要算法
- 摘要算法用来保证数据完整性,能够为数据生成独一无二的[code],用于校验数据的完整性,解决了篡改的风险
- 客户端在发送明文之前会通过摘要算法算出明文的 [code],发送的时候把「code + 明⽂」⼀同加密后发送给服务器,服务器解密后,⽤相同的摘要算法算出发送过来的明⽂,通过⽐较客户端携带的「code」和当前算出的「code」做⽐较,若「code」相同,说明数据是完整的
数字证书
- 客户端先向服务器端索要公钥,然后⽤公钥加密信息,服务器收到密文后,⽤⾃⼰的私钥解密
- 需要借助CA** (数字证书认证机构),将服务器公钥放在数字证书(由数字证书认证机构颁发)中,只要证书是可信的,公钥就是可信的
HTTPS建立连接和交互的过程
SSL/TLS 的 握手阶段 涉及 四次 通信
1. Client Hello
首先,由客户端向服务器发起加密通信请求。在这一步,客户端主要向服务器发送一下信息:
-
客户端支持的 SSL/TLS 协议版本,如 TLS 1.2 版本
-
客户端生产的随机数(Client Random),后面用于生产 会话秘钥
-
客户端支持的密码套件列表,如 RSA加密算法
2. ServerHello
服务器收到客户端请求后,向客户端发出响应。 服务端回应的内容有如下:
-
确认 SSL/TLS 版本协议,如果浏览器不支持,则关闭加密通信
-
服务器生产的随机数 (Client Random),后面用于生产 会话秘钥
-
确认的密码套件列表,如 RSA加密算法
-
服务器的数字证书
3. 客户端回应
客户端收到服务器的回应之后,首先通过浏览器或者操作系统中的 CA公钥,确认服务器的数字证书的真实性
如果证书没有问题,客户端会从数字证书中取出服务器的公钥,然后使用它加密报文,向服务器发送如下信息:
- 一个随机数(pre-master key)。该随机数会被服务器公钥加密
- 加密通信算法改变通知,表示随后的系信息都将用 会话秘钥 加密通信
- 客户端握手结束通知,表示客户端的握手阶段已经结束。这一项同时把之前所有内容的发生的数据做个摘要,用来供服务端检验
pre-master key 是整个握手阶段的第三个随机数,这样客户端和服务器同时拥有三个随机数,接着就用双方协商的加密算法,各自生成 本次通信的 会话秘钥
4. 服务器的最后回应
服务器收到客户端的第三个随机数 (pre-master key) 之后,通过协商的加密算法,计算出本次通信的 会话秘钥。然后, 向客户端发出最后的信息:
- 加密通信算法改变通知,表示随后的信息都将用 会话秘钥 加密通信
- 服务器握手结束通知,表示服务器的握手阶段已经结束。这一项同时把之前所有内容的发生的数据做个摘要,用来供客户端检验
至此,整个 SSL/TLS 的握手阶段全部结束。接下来,客户端与服务器进入加密通信,就完全是使用普通的 HTTP 协议,只不过用 会话秘钥 加密内容
Cookie 和 Session
Cookie
-
HTTP Cookie 是服务器发送到用户浏览器并保存在本地的一小块数据,它会在浏览器下次向同一服务器再发起请求时被携带并发送到服务器上
-
通常,它用于告知服务端两个请求是否来自同一浏览器,如保持用户的登录状态。Cookie 使基于无状态的 HTTP 协议记录稳定的状态信息成为了可能
Cookie 主要用于以下三个方面:
- 会话状态管理(如用户登录状态、购物车、游戏分数或其它需要记录的信息)
- 个性化设置(如用户自定义设置、主题等)
- 浏览器行为跟踪(如跟踪分析用户行为等)
Session
- Session 代表着服务器和客户端一次会话的过程
- Session 对象存储特定用户会话所需的属性及配置信息
- 这样,当用户在应用程序的 Web 页之间跳转时,存储在 Session 对象中的变量将不会丢失,而是在整个用户会话中一直存在下去。当客户端关闭会话,或者 Session 超时失效时会话结束
Cookie 和 Session 有什么不同?
- 作用范围不同,Cookie 保存在客户端,比较容易遭到不法获取。Session 保存在服务器端,安全性相对 Cookie 要好一些
- 存取方式的不同,Cookie 只能保存 ASCII,Session 可以存任意数据类型,一般情况下我们可以在 Session 中保持一些常用变量信息,比如说 UserId 等
- 有效期不同,Cookie 可设置为长时间保持,比如我们经常使用的默认登录功能,Session 一般失效时间较短,客户端关闭或者 Session 超时都会失效
- 存储大小不同, 单个 Cookie 保存的数据不能超过 4K,Session 可存储数据远高于 Cookie
为什么需要Cookie 和 Session,有什么关联
HTTP 协议无状态,意味着浏览器并不知道是哪个用户在和服务端打交道。需要有一个机制来告诉服务端,本次操作用户是否登录,是哪个用户在执行的操作,那这套机制的实现就需要 Cookie 和 Session 的配合
Cookie 和 Session 是如何配合的
- 用户第一次请求服务器的时候,服务器根据用户提交的相关信息,创建对应的 Session ,请求返回时将此 Session 的唯一标识信息 SessionID 返回给浏览器,浏览器接收到服务器返回的 SessionID 信息后,会将此信息存入到 Cookie 中,同时 Cookie 记录此 SessionID 属于哪个域名
- 当用户第二次访问服务器的时候,请求会自动判断此域名下是否存在 Cookie 信息,如果存在自动将 Cookie 信息也发送给服务端,服务端会从 Cookie 中获取 SessionID,再根据 SessionID 查找对应的 Session 信息,如果没有找到说明用户没有登录或者登录失效,如果找到 Session 证明用户已经登录可执行后面操作
- SessionID 是连接 Cookie 和 Session 的一道桥梁,大部分系统也是根据此原理来验证用户登录状态
浏览器禁止了Cookie,如何保障整个机制的正常运转
第一种方案
- 每次请求中都携带一个 SessionID 的参数,也可以 Post 的方式提交,也可以在请求的地址后面拼接 xxx?SessionID=123456…
第二种方案
- Token 机制
- Token 是服务端生成的一串字符串,作为客户端进行请求的一个标识。当用户第一次登录后,服务器根据提交的用户信息生成一个 Token,响应时将 Token 返回给客户端,以后客户端只需带上这个 Token 前来请求数据即可,无需再次登录验证
分布式 Session 问题
- Nginx ip_hash 策略,服务端使用 Nginx 代理,每个请求按访问 IP 的 hash 分配,这样来自同一 IP 固定访问一个后台服务器,避免了在服务器 A 创建 Session,第二次分发到服务器 B 的现象
- Session 复制,任何一个服务器上的 Session 发生改变(增删改),该节点会把这个 Session 的所有内容序列化,然后广播给所有其它节点
- 共享 Session,服务端无状态话,将用户的 Session 等信息使用缓存中间件来统一管理,保障分发到每一个服务器的响应结果都一致
IP
DNS
通过浏览器解析 URL 并生成 HTTP 消息后,需要委托操作系统将消息发送给 Web 服务器
发送之前需要 查询服务器域名对应的IP地址
DNS服务器专门保存了 Web服务器域名 与 IP 的对应关系
域名的层级关系
- 根DNS服务器
- 顶级域DNS服务器 (com)
- 权威DNS服务器 (server.com)
域名解析的工作流程
- 客户端发出一个 DNS 请求,问 www.server.com 的IP,并发送给 本地 DNS服务器 (客户端的TCP/IP 设置中填写的 DNS服务器地址)
- 本地DNS服务器 收到客户端的请求后,如果缓存里的表格能找到 www.server.com,则直接返回IP地址,如果没有,本地DNS会询问 根DNS
- 根DNS 收到 本地DNS的请求后,发现后置是 .com,会转给对应的顶级域名服务器
- 顶级域名服务器 查询负责 www.server.com 区域的权威DNS服务器
- 本地DNS 转向 权威DNS服务器,权威DNS服务查询后将对应的IP地址告诉给 本地DNS
- 本地DNS将IP地址返回客户端,客户端和目标建立连接
ARP
- 在传输一个 IP 数据报的时候,确定了 源IP地址 和 目标IP地址后,就会通过主机 【路由表】确定IP数据包下一跳。网络层的下一层是数据链路层,即需要知道 【下一跳的】 MAC地址
- 通过 ARP 协议 求得下一跳的 MAC地址
ARP是如何知道对方MAC地址的?
- ARP是借助 ARP请求与ARP响应 两种类型的包确定 MAC地址的
- 主机会通过 广播发送ARP请求,这个包中包含了想要知道的MAC地址的主机IP地址
- 当同个链路中的所有设备收到ARP请求时,会去拆开ARP请求包里的内容,如果ARP请求包中的目标IP地址与自己的IP地址一致,那么这个设备就将自己的 MAC 地址塞入 ARP响应包 返回给主机
- 操作系统通常会把第一次通过ARP获取的 MAC地址缓存起来,以便下次直接从缓存中找到对应IP地址的 MAC地址。不过,MAC地址的缓存是有一定期限的,超过这个期限,缓存的内容将被清楚
输入网址到网页显示,期间发生了什么
- URL 解析,解析 http 协议、端口、资源地址。
- DNS 查询:首先查询本地 host,再访问 DNS 服务器将 域名解析成 ip 地址。
- 建立 TCP 连接。
- 服务器收到请求后处理,并且构造响应返回给客户端。
- 客户端接收 HTTP 报文响应。
- 渲染页面,最后有可能会四次挥手断开连接,也可能不会而是复用连接
HTTP
- 首先浏览器对 URL 进行解析,从而生成发送给 Web 服务器的请求信息
- 对 URL 进行解析之后,浏览器确定了 Web服务器和文件名,接下来就是根据这些信息来生成 HTTP请求信息
URL元素组成
http: + // + Web 服务器 + / + 目录名 + / + … + 文件名
URL开头表示访问的协议 // 后面的字符串表示服务器的名称 表示数据源(文件)的路径
真实地址查询 – DNS
通过浏览器解析 URL 并生成 HTTP 消息后,需要委托操作系统将消息发送给 Web 服务器
发送之前需要 查询服务器域名对应的IP地址
DNS服务器专门保存了 Web服务器域名 与 IP 的对应关系
域名的层级关系
- 根DNS服务器
- 顶级域DNS服务器 (com)
- 权威DNS服务器 (server.com)
域名解析的工作流程
- 客户端发出一个 DNS 请求,问 www.server.com 的IP,并发送给 本地 DNS服务器 (客户端的TCP/IP 设置中填写的 DNS服务器地址)
- 本地DNS服务器 收到客户端的请求后,如果缓存里的表格能找到 www.server.com,则直接返回IP地址,如果没有,本地DNS会询问 根DNS
- 根DNS 收到 本地DNS的请求后,发现后置是 .com,会转给对应的顶级域名服务器
- 顶级域名服务器 查询负责 www.server.com 区域的权威DNS服务器
- 本地DNS 转向 权威DNS服务器,权威DNS服务查询后将对应的IP地址告诉给 本地DNS
- 本地DNS将IP地址返回客户端,客户端和目标建立连接
协议栈
通过DNS获取到 IP 后,可以把 HTTP 的传输工作交给操作系统中的 协议栈
应用程序(浏览器) 通过调用 Socket 库,来委托协议栈工作。协议栈的上半部分有两块,分别是收发数据的TCP 和 UDP 协议,会接受应用层的委托执行收发数据的操作
协议栈的下面一般是用 IP 协议控制网络包收发操作,在互联网上传数据时,数据会被切分成一块块的网络包,而将网络包发送给对方的操作就是由 IP 负责的
IP中还包括 ICMP 协议 和 ARP 协议
- ICMP 用于告知网络包传送过程中产生的错误以及各种控制信息
- ARP 用于根据IP地址查询相应的以太网MAC地址
IP下面的网卡驱动程序负责控制网卡硬件,最下面的网卡则负责完成对网线中的信号执行发送和接受操作
可靠传输 – TCP
- 源端口号 和 目的端口号 是必不可少的,没有这两个端口号,数据就不知道发送给哪个引用
- 序号 :解决包乱序的问题
- 确认号 :确认发出去对方是否有收到。如果没有收到就应该重新发送,直到送达,为了解决不丢包的问题
- 状态位: SYN:(发起一个连接) ACK:(回复) RST:(重新连接) FIN:(结束连接) TCP是面向连接的,双方要维护连接的状态,这些带状态位的包的发送,引起双方的状态变更
- 窗口大小 :TCP要做 流量控制,通信双方各声明一个窗口(缓存大小),标识自己当前能够处理的能力
- 拥塞控制 : 控制发送速度
TCP传输数据之前,要先三次握手建立连接
三次握手目的是 保证双方都有发送和接收的能力
如何查看TCP的连接状态
Linux通过 netstat -napt 命令查看
TCP分割数据
如果HTTP请求消息比较长,超过了 MSS 的长度,这时TCP需要把 HTTP 的数据拆解成一块块的数据发送,而不是一次性发送所有数据
- MTU: 一个网络包的最大长度,以太网中一般为 1500 字节
- MSS: 除去IP 和 TCP头部之后,一个网络包所能容纳的TCP数据的最大长度
数据会被以 MSS 的长度为单位进行拆分,拆分出来的每一块数据都会被进单独的网络包中。也就是在每个被拆分的数据加上TCP头信息,然后交给 IP 模块来发送数据
TCP报文生成
- TCP 协议⾥⾯会有两个端⼝,⼀个是浏览器监听的端⼝(通常是随机⽣成的),⼀个是 Web 服务器监听的端⼝(HTTP默认端⼝号是 80 , HTTPS 默认端⼝号是 443 )
- 在双⽅建⽴了连接后,TCP 报⽂中的数据部分就是存放 HTTP 头部 + 数据,组装好 TCP 报⽂之后,就需交给下⾯的⽹络层处理
远程定位 – IP
TCP 模块在执⾏连接、收发、断开等各阶段操作时,都需要委托 IP 模块将数据封装成网络包发送给通信对象
IP报文头部的格式
在 IP 协议⾥⾯需要有源地址 IP 和 ⽬标地址 IP:
- 源地址IP,即是客户端输出的 IP 地址
- ⽬标地址,即通过 DNS 域名解析得到的 Web 服务器 IP
因为 HTTP 是经过 TCP 传输的,所以在 IP 包头的 协议号,要填写为 06 (⼗六进制),表示协议为 TCP
假设客户端有多个网卡,就回有多个IP地址,那IP头部的源地址应该选择哪个IP呢?
需要根据 路由表 规则,来判断哪一个网卡作为源地址 IP
在Linux操作系统,使用 route -n 命令查看当前系统的路由表
假设 Web 服务器的⽬标地址是 192.168.10.200
- ⾸先先和第⼀条目的子网掩码( Genmask )进⾏ 与运算,得到结果为 192.168.10.0 ,但是第⼀个条⽬的Destination 是 192.168.3.0 ,两者不⼀致所以匹配失败
- 再与第⼆条⽬的⼦⽹掩码进⾏ 与运算,得到的结果为 192.168.10.0 ,与第⼆条⽬的 Destination192.168.10.0 匹配成功,所以将使⽤ eth1 ⽹卡的 IP 地址作为 IP 包头的源地址
- 第三条⽬⽐较特殊,它⽬标地址和⼦⽹掩码都是 0.0.0.0 ,这表示默认⽹关,如果其他所有条⽬都⽆法匹配,就会⾃动匹配这⼀⾏。并且后续就把包发给路由器,Gateway 即是路由器的 IP 地址
IP报文生成
两点传输 – MAC
⽣成了 IP 头部之后,接下来⽹络包还需要在 IP 头部的前⾯加上 MAC 头部
MAC包头格式
MAC 头部是以太⽹使⽤的头部,它包含了接收⽅(48位)和发送⽅的 MAC 地址(48位)和协议类型(16位)
在 MAC 包头⾥需要 发送⽅ MAC 地址 和 接收⽅⽬标 MAC 地址,⽤于两点之间的传输
一般在 TCP/IP 通信里,MAC包头的 协议类型 只使用
- 0800 :IP协议
- 0806 :ARP协议
MAC发送方和接收方如何确认
- 发送方 的 MAC 地址获取⽐较简单,MAC 地址是在⽹卡⽣产时写⼊到 ROM ⾥的,只要将这个值读取出来写⼊到 MAC 头部就可以了
- 接收方的 MAC 地址就有点复杂了,只要告诉以太网对⽅的 MAC 的地址,以太网就会帮我们把包发送过去,那么很显然这⾥应该填写对⽅的 MAC 地址
- 所以先得搞清楚应该把包发给谁,这个只要查⼀下路由表就知道了。在路由表中找到相匹配的条⽬,然后把包发给Gateway 列中的 IP 地址就可以了
如何获取对方的 MAC 地址呢?
- 先查询 ARP 缓存,如果其中已经保存了对方的 MAC地址,就不需要发送 ARP 查询,直接使用 ARP 缓存中的地址
- 当 ARP缓存中不存在对方 MAC 地址时,则发送 ARP 广播查询
查看 ARP 缓存内容
MAC报文生成
出口 – 网卡
网络包只是存放在内存中的⼀串⼆进制数字信息,没有办法直接发送给对⽅。需要将数字信息转换为电信号,才能在网线上传输,负责执行这⼀操作的是⽹卡,要控制网卡还需要靠⽹卡驱动程序
网卡驱动从 IP 模块获取到包之后,会将其 复制 到网卡内的缓存区中,接着会在其 开头加上报头和起始帧分界符,在末尾加上用于检测错误的帧校验序列
送别者 – 交换机
交换机的设计是将网络包原样转发到目的地
交换机工作在 MAC 层,也称为 二层网络设备
交换机的包接收操作
- 首先,电信号到达网线接口,交换机里的模块进行接收,接下来交换机里的模块将电信号转换为数字信号
- 然后通过包末尾的 FCS 校验错误,如果没问题则放到缓冲区。这部分操作基本和计算机的网卡相同,但交换机的工作方式和网卡不同
- 计算机的网卡本身具有 MAC 地址,并通过核对收到的包的接收方 MAC 地址判断是不是发给自己的,如果不是发给自己的则丢弃;相对地,交换机的端口不核对接收方 MAC 地址,而是直接接收所有的包并存放到缓冲区中。因此,和网卡不同,交换机的端口不具有 MAC 地址
- 将包存入缓冲区后,接下来需要查询一下这个包的接收方 MAC 地址 是否已经 在 MAC 地址表中有记录了
交换机的 MAC 地址表主要包含两个信息:
- 一个是设备的 MAC 地址
- 另一个是该设备连接在交换机的哪个端口上
交换机内部有一张 MAC 地址 与网线端口的映射表。当接收到包时,会将相应的端口号和发送MAC地址写入表中,这样就可以根据地址判断出该设备连接在哪个端口上了
交换机根据MAC地址表查找MAC地址,然后将信号发送到相应的端口
当MAC地址表找不到指定的MAC地址会怎样?
- 地址表中找不到指定的 MAC 地址。可能是因为具有该地址的设备还没有向交换机发送过包,或者这个设备一段时间没有工作导致地址被从地址表中删除了
- 这种情况下,交换机无法判断应该把包转发到哪个端口,只能将包转发到除了源端口之外的所有端口上,无论该设备连接在哪个端口上都能收到这个包
- 以太网的设计本来就是将包发送到整个网络,只有相应的接受者才接收包,而其他设备则会忽略这个包
- 此外,如果接收方 MAC 地址是一个 广播地址,那么交换机会将包发送给处源端口之外的所有端口
以下两个属于广播地址:
MAC地址中的:FF:FF:FF:FF:FF:FF
IP地址中的:255.255.255.255
出境大门 – 路由器
路由器与交换机的区别
- 网络包经过交换机之后,现在到达了 路由器,并在此被转发到下一个路由器或目标设备
- 这一步转发个工作原理和交换机类似,也是通过查表判断包转发的目标
具体的操作,路由器和交换机的区别:
- 因为 路由器 是基于 IP 设计的,三层 网络设备,路由器 的各个端口都具有 MAC 地址和 IP 地址
- 交换机 是基于 以太网 设计的,二层 网络设备,交换机 的端口 不具有 MAC 地址
路由器的包接收操作
- 电信号到达网线接口部分,路由器中的模块会将电信号转成数字信号,然后通过包末尾 FCS 进行错误检验
- 如果没问题则检查 MAC 头部中的 接收方MAC地址,看看是不是发给自己的包,如果是就放到接收缓冲区中,否则就丢弃这个包
- 路由器的端口都具有 MAC 地址,只接收与自身地址匹配的包,遇到不匹配的包则直接丢弃
查询路由表确定输出端口
- 完成包接收操作之后,路由器就会 去掉 包开头的 MAC 头部
- MAC 头部的作用就是将包送达路由器,其中的接收方 MAC 地址就是路由器端口的 MAC 地址。因此,当包到达路由器之后,MAC 头部的任务就完成了,于是 MAC 头部就会 被丢弃
- 接下来,路由器会根据 MAC 头部后方的 IP 头部中的内容进⾏包的转发操作
路由器转发操作
- 首先是查询 路由表 判断转发目标
- 假设地址为 10.10.1.101 的计算机要向地址为 192.168.1.100 的服务器发送一个包,这个包先到达路由器
- 判断转发目标的第一步,就是根据包的接收方 IP 地址查询路由表中的目标地址栏,以找到相匹配的记录
- 每个条目的子网掩码和 192.168.1.100 IP 做 & 与运算 后,得到的结果与对应条目的目标地址进行匹配,如果匹配就会作为候选转发目标,如果不匹配就继续与下个条目进行路由匹配
- 如第⼆条目的子网掩码 255.255.255.0 与 192.168.1.100 IP 做 & 与运算 后,得到结果是 192.168.1.0 ,这与第⼆条目的目标地址 192.168.1.0 匹配,该第⼆条目记录就会被作为转发目标
- 实在找不到匹配路由时,就会选择 默认路由,路由表中子网掩码为 0.0.0.0 的记录表示 默认路由
路由器的发送操作
接下来就会进入包的 发送操作
首先,根据 路由表的网关列 判断对方的地址
- 如果网关是⼀个 IP 地址,则这个 IP 地址就是要转发到的目标地址,还未抵达终点,还继续需要路由器转发
- 如果网关为空,则 IP 头部中的接收方 IP 地址就是要转发到的目标地址,也是就终于找到 IP 包头里的目标地址了,说明已抵达终点
知道对方的 IP 地址之后,接下来需要通过 ARP 协议 根据 IP 地址查询 MAC 地址,并将查询的结果作为接收方 MAC 地址
- 路由器也有 ARP 缓存,因此首先会在 ARP 缓存中查询,如果找不到则发送 ARP 查询请求
- 接下来是发送方 MAC 地址字段,这⾥填写输出端口的 MAC 地址。还有⼀个以太类型字段,填写 0800 (十六进制)表示 IP 协议
- 网络包完成后,接下来会将其转换成电信号并通过端口发送出去。这⼀步的⼯作过程和计算机也是相同的
- 发送出去的网络包会通过 交换机 到达下⼀个路由器。由于接收方 MAC地址就是下⼀个路由器的地址,所以交换机会根据这⼀地址将包传输到下⼀个路由器
- 接下来,下⼀个路由器会将包转发给再下⼀个路由器,经过层层转发之后,网络包就到达了最终的目的地
- 在网络包传输的过程中,源 IP 和⽬标 IP 始终是不会变的,⼀直变化的是 MAC 地址,因为需要 MAC 地址在以太网内进行 两个设备 之间的包传输
服务器与客户端
-
-
客户端查看收到的数据包 HTTP 响应报文后,交给浏览器去渲染页面
-
客户端要离开了,向服务器发起了 TCP 四次挥⼿,至此双方的连接就断开了