mirror of
https://github.com/NohamR/knowledge-kit.git
synced 2026-05-25 04:17:17 +00:00
feature: dyld && LD 链接器
This commit is contained in:
@@ -143,6 +143,87 @@ TCP 会利用另一种机制来解决超时重传带来的时间等待问题,
|
||||
|
||||
- 当 RW < CW 时,速度由 RW 决定
|
||||
|
||||
|
||||
|
||||
## HTTP 特点及解决方案
|
||||
|
||||
### 无连接
|
||||
|
||||
因为 HTTP 无连接,客户端和服务端交互的时候,打开一个 TCP 连接,然后交互,然后关闭 TCP 连接。下次需要交互的时候,继续打开一个 TCP 连接,继续交互,最后又关闭 TCP 连接。
|
||||
|
||||
<img src="./../assets/ HTTPContinuesousLink.png" style="zoom:40%" />
|
||||
|
||||
|
||||
|
||||
为了解决该问题,HTTP 推出持久连接方案。
|
||||
|
||||
涉及哪些头部字段?
|
||||
|
||||
- `Connection: keep-alive`:客户端期许使用持久连接
|
||||
- `time: 20`:20s 内不会进行四次挥手关闭。20s 内发送请求会复用之前的 TCP 连接
|
||||
- `max: 10`:最多可以发送多少个请求和响应对。
|
||||
|
||||
怎么样判断一个请求是否结束了?2个方案
|
||||
|
||||
- 服务端响应的时候会在 header 中携带 `Content-length: 1024` 如果接受到的数据字节数大小,等于 `Content-length` 则说明已经全部接受完毕。
|
||||
- `chunked` :使用 post 请求的时候,服务端返回给客户端是通过多个块返回的。每个报文都带有 chunked 这个字段,最后一个报文会带一个空的 chunked。
|
||||
|
||||
|
||||
|
||||
### 无状态 - Cookie/Session
|
||||
|
||||
Session、Cookie 都是针对 HTTP 协议无状态特点的补偿。
|
||||
|
||||
|
||||
|
||||
#### Cookie
|
||||
|
||||
Cookie 主要用来记录用户状态,区分用户;状态保存在客户端。
|
||||
|
||||
<img src="./../assets/CookieLifeCycle.png" style="zoom:30%" />
|
||||
|
||||
客户端请求服务端的时候,服务端生成 Cookie,通过 HTTP 响应报文,Header 部分中 `Set-Cookie` 首部字段设置 Cookie。
|
||||
|
||||
- 客户端发送的 Cookie 在 HTTP 请求报文的 `Cookie` 首部字段中
|
||||
- 服务端设置 HTTP 的响应报文 `Set-Cookie` 首部字段
|
||||
|
||||
|
||||
|
||||
怎么修改 Cookie?
|
||||
|
||||
新 Cookie 覆盖旧 Cookie。
|
||||
|
||||
覆盖规则:name、path、domain 等需要与原 Cookie 一致。
|
||||
|
||||
怎么删除 Cookie?
|
||||
|
||||
- 新 Cookie 覆盖旧 Cookie。覆盖规则:name、path、domain 等需要与原 Cookie 一致。
|
||||
- 设置 Cookie 的过期时间在过去。比如 `expires = 过去的一个时间点`,或者 `maxAge = 0`
|
||||
|
||||
怎么保证 Cookie 的安全?
|
||||
|
||||
- 对 Cookie 进行加密处理
|
||||
- 只在 HTTPS 上携带 Cookie
|
||||
- 设置 Cookie 为 httponly,防止跨站脚本攻击
|
||||
|
||||
|
||||
|
||||
#### Session
|
||||
|
||||
Session 也用来记录用户状态,区分用户。只不过状态保存在服务端。
|
||||
|
||||
Session 需要依赖于 Cookie 机制。
|
||||
|
||||
<img src="./../assets/SessionWorkflow.png" style="zoom:30%" />
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## HTTP 缓存控制
|
||||
|
||||
缓存(Cache)是计算机领域里的一个重要概念,是优化系统性能的利器。
|
||||
@@ -247,7 +328,7 @@ ETag 即 Entity Tag,代表资源的唯一标识。主要用来解决修改时
|
||||
|
||||
代理体现在头信息上就是字段 `Via`,是一个通用字段,请求头或响应头里都可以出现。每当报文经过一个代理节点,代理服务器就会把自身的信息追加到字段的末尾,就像是经手人盖了一个章。如果通信链路中有很多中间代理,就会在 Via 里形成一个链表,这样就可以知道报文究竟走过了多少个环节才到达了目的地。
|
||||
|
||||

|
||||

|
||||
|
||||
`X-Forwarded-For` 的字面意思是“为谁而转发”,形式上和“Via”差不多,也是每经过一个代理节点就会在字段里追加一个信息。但“Via”追加的是代理主机名(或者域名),而“X-Forwarded-For”追加的是请求方的 IP 地址。所以,在字段里最左边的 IP 地址就是客户端的地址
|
||||
|
||||
@@ -284,7 +365,7 @@ Host: www.xxx.com\r\n
|
||||
|
||||
HTTP 传输链路上,不只是客户端有缓存,服务器上的缓存也是非常有价值的,可以让请求不必走完整个后续处理流程,"就近"获得响应结果。特别是对于那些“读多写少”的数据,例如突发热点新闻、爆款商品的详情页,一秒钟内可能有成千上万次的请求。即使仅仅缓存数秒钟,也能够把巨大的访问流量挡在外面,让 RPS(request per second)降低好几个数量级,减轻应用服务器的并发压力,对性能的改善是非常显著的。
|
||||
|
||||

|
||||

|
||||
|
||||
代理服务器没有缓存的时候:代理服务器每次直接转发来自客户端的报文给服务端,转发服务端的报文给客户端,中间不会存储任何数据,只有基础的中转功能。
|
||||
|
||||
@@ -316,7 +397,7 @@ HTTP 传输链路上,不只是客户端有缓存,服务器上的缓存也是
|
||||
|
||||
下图是完整的服务器端缓存控制策略,可以同时控制客户端和代理
|
||||
|
||||

|
||||

|
||||
|
||||
### 客户端的缓存控制
|
||||
|
||||
@@ -374,6 +455,124 @@ Range:bytes=20-39 //:从第20个字节到第39个字节之间的数据
|
||||
- 先发送一个 HEAD 方法的请求,知道总文件大小(Content-Length 就是总字节大小)
|
||||
- 多线程下载(线程1:Range:bytes=0-100,线程2:Range:bytes=100-200,...)
|
||||
|
||||
|
||||
|
||||
## Charles 抓包原理
|
||||
|
||||
抓包原理其实就是:HTTP 中间人攻击。
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## DNS 解析
|
||||
|
||||
域名到 IP 地址的映射,DNS 解析请求采用 UDP 数据报,且明文传输。
|
||||
|
||||
|
||||
|
||||
### DNS 解析方式
|
||||
|
||||
DNS 解析查询方式:
|
||||
|
||||
- 递归查询,核心就是“我去给你问一下”
|
||||
|
||||
<img src="./../assets/DNSRecursiveQuery.png" style="zoom:30%" />
|
||||
|
||||
客户端根据网址去请求服务器之前,会先获取 IP 地址信息。
|
||||
|
||||
- 先去本地 DNS 服务器,本地 DNS 服务器可以处理结果(根据域名对应到 IP 数据)则直接返回给客户端
|
||||
- 如果不能解析,则会去请求根域 DNS 服务器,根域 DNS 告诉本地 DNS“你先等一下,我去问问顶级 DNS”
|
||||
|
||||
- 迭代查询,核心就是“我告诉你谁可能知道”
|
||||
|
||||
<img src="./../assets/DNSIteratorQuery.png" style="zoom:30%" />
|
||||
|
||||
- 客户端发送请求的时候问一下本地 DNS 服务器,该域名对应的 IP 地址是什么,本地 DNS 服务器说“我不知道,你去问问根域名服务器,它可能知道”
|
||||
|
||||
- 然后客户端去问根域 DNS 服务器,根域 DNS 服务器说“我也不知道,你去问问顶级 DNS 服务器,它可能知道”
|
||||
|
||||
- 然后客户端去问顶级 DNS 服务器,顶级 DNS 服务器说“我也不知道,你去问问权限 DNS 服务器,它可能知道”
|
||||
|
||||
- 然后权限 DNS 服务器把域名对应的 IP 告诉客户端,客户端拿到 IP 后去请求
|
||||
|
||||
|
||||
|
||||
#### DNS 解析存在哪些常见问题
|
||||
|
||||
最容易遇到:DNS 劫持问题、DNS 解析转发问题
|
||||
|
||||
#### DNS 劫持问题
|
||||
|
||||
##### 什么是 DNS 劫持
|
||||
|
||||
<img src="./../assets/DNSHacker.png" style="zoom:30%" />
|
||||
|
||||
|
||||
|
||||
由于 DNS 解析是采用 UDP 数据包、明文传输,所以很容易遇到中间人攻击,也就是 DNS 劫持。
|
||||
|
||||
|
||||
|
||||
QA:DNS 劫持和 HTTP 的关系是什么?
|
||||
|
||||
**DNS 劫持和 HTTP 是没有关系的**。
|
||||
|
||||
- DNS 解析是发生在 HTTP 连接建立前
|
||||
- DNS 解析请求采用 UDP 数据报,端口为53
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
##### 如何解决 DNS 劫持问题
|
||||
|
||||
- httpDNS
|
||||
|
||||
从“使用 DNS 协议向 DNS 服务器的53端口请求” 变成“使用 HTTP 协议向 HTTP 服务器的80端口请求”
|
||||
|
||||
<img src="./../assets/HTTPDNSServerProcess.png" style="zoom:25%" />
|
||||
|
||||
客户端通过 IP 直连的方式,向 DNS 服务器,通过 HTTP Get 请求的方式,携带域名参数,然后响应一个具体的 IP 地址值给客户端。剩余流程就是拿着请求后的 IP 地址去完成其他逻辑。
|
||||
|
||||
- 长连接
|
||||
|
||||
<img src="./../assets/LongConnectionAvoidDNSHacker.png" style="zoom:30%" />
|
||||
|
||||
在客户端和业务服务器之间,建立一个长连接 Server,可以理解成代理服务器。
|
||||
|
||||
客户端和长连接 Server 建立一个长连接通道。客户端可以发送一个 HTTP 请求,通过长连通道将 HTTP 请求发送给长连 Server。
|
||||
|
||||
长连 Server 可以通过内网专线的方式,进行 HTTP 的请求和响应。也就是说 DNS 解析这步还存在,只不过是发生在内网专线阶段,避免了在外部公网阶段,DNS 劫持的问题。
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
#### DNS 解析转发
|
||||
|
||||
<img src="./../assets/DNSParseRedirect.png" style="zoom:20%" />
|
||||
|
||||
比如某移动 App 发起网络请求,移动 DNS 服务器为了节省资源,将请求转发到某电信 DNS 服务器,用于帮助移动 DNS 服务器,解析域名,获取对应的 IP 地址。
|
||||
|
||||
这个电信 DNS 服务器,会向权威 DNS 服务器去请求解析域名对应的 IP 地址。
|
||||
|
||||
权限 DNS 会根据不同运营商请求情况(网络请求)的流量调度分发:
|
||||
|
||||
移动返回是2.2.2.2,电信返回是3,3,3,3,这种情况下,客户端在通过电信网络请求移动 DNS 服务器,进而转发到电信 DNS 服务器之后,权威 DNS 会返回3.3.3.3,也就是客户端在移动环境,由于 DNS 解析转发,而返回的 DNS 是3.3.3.3的电信环境,造成了跨网访问的效率问题
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## HTTPS 安全
|
||||
|
||||
HTTP 的缺点之一就是:明文 + 不安全。为此诞生了 HTTPS 协议。
|
||||
@@ -1115,4 +1314,4 @@ HTTP/3 没有指定默认的端口号,也就是说不一定非要在 UDP 的 8
|
||||
|
||||
## 参考资料
|
||||
|
||||
- [HTTP/2 协议-HPACK(HTTP2 头部压缩算法)原理介绍_爱因诗贤的博客-CSDN博客_hpack算法](https://blog.csdn.net/qq_38937634/article/details/111410191)
|
||||
- [HTTP/2 协议-HPACK(HTTP2 头部压缩算法)原理介绍_爱因诗贤的博客-CSDN博客_hpack算法](https://blog.csdn.net/qq_38937634/article/details/111410191)
|
||||
@@ -1,6 +1,12 @@
|
||||
# TCP、UDP 的比较
|
||||
|
||||
> 网络协议是每个工程师都需要了解和掌握的知识。 TCP/IP 中有2个最具代表性的传输层协议:TCP、UDP。
|
||||
>
|
||||
> 记住这句话:TCP 是面向字节流协议、UDP 是面向报文协议。主要是因为发送方的发送消息的机制不同。
|
||||
>
|
||||
> 下面的内容会探索这些。
|
||||
|
||||
|
||||
|
||||
|
||||
## 一、TCP/IP 网络模型
|
||||
@@ -52,7 +58,13 @@ UDP 不止支持一对一的传输方式,同样支持一对多,多对多,
|
||||
|
||||
### 3.UDP是面向报文的
|
||||
|
||||
发送方的UDP对应用程序交下来的报文,在添加首部后就向下交付IP层。UDP对应用层交下来的报文,既不合并,也不拆分,而是保留这些报文的边界。因此,应用程序必须选择合适大小的报文
|
||||
发送方的 UDP 对应用程序交下来的报文,在添加首部后就向下交付 IP 层。UDP 对应用层交下来的报文,既不合并,也不拆分,而是保留这些报文的边界。因此,应用程序必须选择合适大小的报文
|
||||
|
||||
<img src="./../assets/UDPOrientedPackage.png" style="zoom:40%" />
|
||||
|
||||
|
||||
|
||||
UDP 在发送消息时,在传输层直接就将一个消息打包成一个完整的包,组装好 UDP 头部,不进行切割,就转发给网络层。也就是每一个UDP 报文就是一个消息。服务端在接收到 UDP 报文时,会将它放到一个队列中,一个元素就是一个 UDP 报文。每次读取时,读取一个元素
|
||||
|
||||
### 4.不可靠性
|
||||
|
||||
@@ -75,6 +87,31 @@ UDP 不止支持一对一的传输方式,同样支持一对多,多对多,
|
||||
因此 UDP 的头部开销小,只有八字节,相比 TCP 的至少二十字节要少得多,在传输数据报文时是很高效的
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
### 功能
|
||||
|
||||
#### 复用、分用
|
||||
|
||||
复用:UDP 多端口复用指的是在 UDP 通信中,可以通过一个 UDP 端口同时处理多个不同的应用程序或服务的通信。这种技术允许多个应用程序共享同一个 UDP 端口进行通信,而不需要为每个应用程序分配独立的端口。通过在接收数据时区分不同的目标端口,可以实现对多个应用程序的数据传输和处理。这种方式可以提高网络资源的利用率和简化网络配置,但需要确保在接收端能够正确解析和处理来自不同端口的数据。
|
||||
|
||||
<img src="./../assets/UDPMultiplePortCommonUse.png" style="zoom:30%" />
|
||||
|
||||
|
||||
|
||||
分用:UDP 多端口分用是指在 UDP 通信中,可以通过一个应用程序或服务同时监听和处理多个不同的 UDP 端口。这种技术允许一个应用程序在同一时间接收来自多个不同 UDP 端口的数据包,并根据端口信息来区分和处理这些数据。通过 UDP 多端口分用,一个应用程序可以灵活地处理多个 UDP 端口上的数据,实现更高效的网络通信和数据处理
|
||||
|
||||
<img src="./../assets/UDPMultiplePortDivideUse.png" style="zoom:30%" />
|
||||
|
||||
#### 差错检测
|
||||
|
||||
以16位字位一个单元,按二进制反码计算出这些16位字的和。将和的二进制反码写入到检验和位。
|
||||
|
||||
IM 参考 UDP 差错检测,判断消息有没有被篡改。
|
||||
|
||||
|
||||
|
||||
## 三、TCP
|
||||
|
||||
当一台计算机想要与另一台计算机通讯时,两台计算机之间的通信需要畅通且可靠,这样才能保证正确收发数据。例如,当你想查看网页或查看电子邮件时,希望完整且按顺序查看网页,而不丢失任何内容。当你下载文件时,希望获得的是完整的文件,而不仅仅是文件的一部分,因为如果数据丢失或乱序,都不是你希望得到的结果,于是就用到了TCP。
|
||||
@@ -97,30 +134,277 @@ HTTP/1.0 为每次 HTTP 请求/相应都建立一条新的 TCP 链接。因此
|
||||
- 第三次握手:客户端收到服务端 ACK+SYN 包,需要向服务端确认。确认报文段的 ACK 置为1(建立连接,2端的ACK都为1),确认号 **ack=y+1**,而自己的序号 **seq=x+1**。客户端和服务端进入 **ESTABLISHED** (TCP 连接成功)状态,完成三次握手。
|
||||
|
||||
|
||||
|
||||
#### 为什么需要3次握手?
|
||||
|
||||
<img src="./../assets/TCPThressTimesBuild.png" style="zoom:30%" />
|
||||
|
||||
|
||||
|
||||
假设只有2次握手流程。客户端先给服务端发送了1个 SYN 报文,不巧的是,此时网络不稳定,该报文没有到达服务端,由于超过了超时时间,启用重传策略,重新发送一个 SYN 报文。
|
||||
|
||||
当服务端收到一个 SYN 报文后,回复给客户端一个 ACK 同步确认报文。如果只有2步握手,此时说明 TCP 连接已经建立了。假设前面超时的 SYN 报文,在服务端收到来自客户端的 SYN 报文后再发送了一个 ACK 确认报文时间之后到达服务端,对于服务端来说,会认为客户端又要建立一个 TCP 连接,也就是存在2个 TCP 连接。但实际上第一个 SYN 超时后,启动重传机制发送了第二个 SYN 报文,客户端实际上只希望建立1个 TCP 连接。
|
||||
|
||||
有了3次握手就可以解决该问题。
|
||||
|
||||
上面背景没有变。当客户端收到来自服务端的 ACK 之后,客户端再发送给服务端一个 ACK 确认报文。之后由于超时才到达服务端的 ACK 报文到达了服务端,服务端收到 SYN,会立马发送一个 ACK 、SYN 报文,一段时间过后,发现客户端并没有给服务端发送一个 ACK 报文。则服务端可以认为是后来的这个 SYN 是由于超时机制发送的报文,客户端并没真正想建立连接。
|
||||
|
||||
所以需要3次握手,用来规避同步请求报文超时而建立重复(无用)连接的场景。
|
||||
|
||||
可能导致客户端重复发起连接请求,从而浪费网络资源和服务器端的资源。同时,也可能造成服务器误认为客户端请求建立连接,而客户端并没有真正发起连接请求,从而造成服务器端资源浪费。因此,三次握手能够更好地确保TCP连接的可靠性。
|
||||
|
||||
|
||||
|
||||
### 四次挥手
|
||||
|
||||

|
||||
|
||||
- 第一次挥手:客户端应用进程先向其 TCP 发出连接释放报文段,并停止发送数据,主动关闭 TCP 连接(此时关闭的是自己与客户端的连接)。FIN 置为1,序号 seq=u,它等于前面已传送过的数据的最后一个字节的序号加1。这时客户端进入 **FIN_WAIT_1** (终止等待1)状态,等待服务端的确认。TCP 规定,FIN 报文段即使不携带数据段,它也会消耗掉一个序号。
|
||||
- 第二次挥手:服务端收到连接释放请求后,会告诉应用层要释放 TCP 链接。发出确认,确认号 ack=u+1,序号为 seq=v,等于服务端前面已经传输过的数据的最后一个字节的序号加1。然后服务端就进入了 CLOSE_WAIT(关闭等待)状态。TCP 服务器进程通知高层应用数据,因而从客户端到服务端这个芳香的连接就释放了。此时 TCP 处于半关闭状态。即客户端已经没数据要发送了,服务端还在发送数据,但是客户端仍需接收数据。也就是说从服务端到客户端的连接尚未关闭
|
||||
- 第二次挥手:服务端收到连接释放请求后,会告诉应用层要释放 TCP 链接。发出确认,确认号 ack=u+1,序号为 seq=v,等于服务端前面已经传输过的数据的最后一个字节的序号加1。然后服务端就进入了 CLOSE_WAIT(关闭等待)状态。TCP 服务器进程通知高层应用数据,因而从客户端到服务端这个方向的连接就释放了。此时 TCP 处于**半关闭状态**。即客户端已经没数据要发送了,服务端还在发送数据,但是客户端仍需接收数据。也就是说从服务端到客户端的连接尚未关闭
|
||||
- 第三次挥手:客户端收到来自服务端的消息后进入了 FIN_WAIT_2 (终止等待2)状态。等待服务端发出的连接释放报文段。若服务端已经没有要向客户端发送数据,其应用进程就通知 TCP 释放连接。这时候服务端发出的报文段必须 FIN=1。假设服务端序号为 W(在在关闭状态下服务端可能又发送了一些数据,因此与 V 有一定距离)。服务端还必须重复上次发送过的确认号 ack=u+1。这时服务端就进入 LAST_ACK(最后确认)状态,等待客户端的确认
|
||||
- 第四次挥手:客户端收到服务端发送的连接释放报文段后,必须对此发出确认。在确认报文段中把 ACK 置1,确认号为 ack=w+1,而自己的序列号 seq=u+1(根据 TCP 标准,前面发送过的 FIN 报文段要消耗一个序号)。然后进入到 TIME_WAIT(时间等待)状态。现在 TCP 还没有释放掉。必须经过时间等待计时器设置的时间 2MSL 后,客户端才可以进入到 CLOSED 状态。时间 MSL 叫做最长报文段寿命。
|
||||
|
||||
|
||||
|
||||
#### 为什么需要4次挥手
|
||||
|
||||
<img src="./../assets/TCPFourTimesUnBuild.png" style="zoom:30%" />
|
||||
|
||||
|
||||
|
||||
首先客户端给服务端发送一个终止 FIN 报文,服务端收到后,同时也发送一个 ACK 确认报文。此时处于半关闭状态。客户端已经不能给服务端发送数据了,但服务端可以给客户端发送数据。
|
||||
|
||||
过了一段时间(服务端数据发送完了),服务端主动发送一个只终止确认报文来断开服务端到客户端方向的连接。客户端收到后,发送一个 ACK 确认报文给服务端。
|
||||
|
||||
有2个方向的连接断开,是因为 TCP 是全双工的。同样一条通道,客户端可以给服务端发送,服务端也可以给客户端发送。
|
||||
|
||||
|
||||
|
||||
### TCP 协议的特点
|
||||
|
||||
- 面向连接
|
||||
面向连接,是指发送数据之前必须在两端建立连接。建立连接的方法是三次握手,这样能建立可靠的连接。建立连接是为数据的可靠传输打下了基础。
|
||||
- 仅支持单向传输
|
||||
每条 TCP 传输连接只能有2个端点,只能进行点对点的数据传输,不支持多播和广播传输方式
|
||||
- 面向字节流
|
||||
TCP 不像 UDP 那样一个个报文独立传输,而是在不保留报文边界的情况下以字节流方式进行传输
|
||||
- 可靠传输
|
||||
对于可靠传输,判断丢包,误码靠的是 TCP 的段编号以及确认号。TCP 为保证报文传输的可靠,给每个包一个序号,同时序号也保证了传输到接收端实体的包的按序接收。然后接收端实体对已成功收到的字节发回一个相应的确认 ACK;如果发送端实体在合理的往返时延(RTT)内未收到确认,那么对应的数据将被重传。
|
||||
- 提供拥塞控制
|
||||
当网络出现拥塞时,TCP 能够减小向网络注入数据的速率和数量,缓解拥塞
|
||||
- TCP 提供全双工通信
|
||||
TCP 允许通信双方的应用程序在任何都能发送数据,因为 TCP 连接的两端都设有缓存,用来临时存放双向通信的数据。当然,TCP 可以发送一个数据段,也可以缓存一段时间一边一次发送更多的数据段(最大的数据段大小取决于 MSS)
|
||||
#### 面向连接
|
||||
|
||||
面向连接,是指发送数据之前必须在两端建立连接。建立连接的方法是三次握手,这样能建立可靠的连接。建立连接是为数据的可靠传输打下了基础。
|
||||
|
||||
数据传输结束后,需要释放连接,需经过4次挥手。
|
||||
|
||||
#### 可靠传输
|
||||
|
||||
对于可靠传输,判断丢包,误码靠的是 TCP 的段编号以及确认号。TCP 为保证报文传输的可靠,给每个包一个序号,同时序号也保证了传输到接收端实体的包的按序接收。然后接收端实体对已成功收到的字节发回一个相应的确认 ACK;如果发送端实体在合理的往返时延(RTT)内未收到确认,那么对应的数据将被重传。
|
||||
|
||||
- 无差错
|
||||
- 不丢失
|
||||
- 不重复
|
||||
- 按序到达
|
||||
|
||||
这些特点在 IM 系统上可以借鉴。
|
||||
|
||||
可靠传输在 TCP 层面是依靠“停止等待协议”实现的。
|
||||
|
||||
|
||||
|
||||
##### 停止等待协议
|
||||
|
||||
> **停止等待协议**:**通信系统中,规定发送方每发送一帧后就要停下来等待接收方的确认返回,仅当接收方确认正确接收后再继续发送下一帧**。
|
||||
|
||||
下面分4个不同情况看看 TCP 是如何保证消息的可靠传输的。
|
||||
|
||||
- 无差错情况
|
||||
|
||||
<img src="./../assets/TCPStopAndWaitProtocolNormal.png" style="zoom:30%" />
|
||||
|
||||
这张图是正常传输的情况。没有发生任何错误
|
||||
|
||||
- 超时重传
|
||||
|
||||
<img src="./../assets/TCPStopAndWaitProtocolResendWhenTimeout.png" style="zoom:30%" />
|
||||
|
||||
这张图是一个超时重传的情况。客户端给服务端发送了 M1 分组报文,由于网络环境比较差,丢失或者滞留,或者被劫持了篡改了,当劫持篡改的情况下服务端接收到 M1 后,会判断篡改后丢弃该报文。在期许的时间内,客户端没有收到分组报文 M1 的确认,认为发生了超时。触发重传机制。然后启动一个分组报文 M1 的重传,此时网络正常,服务端收到 M1,并发送确认给客户端。客户端收到确认报文后,再发送 M2...
|
||||
|
||||
针对超时的情况,可以在客户端设置一个超时定时器,如果没有在规定时间范围内收到 M1 分组报文的确认报文,则开启重传机制。该机制保证分组报文不丢失,服务端的差错检测,可以保证无差错。
|
||||
|
||||
这套流程可以保证:**差错校验、不丢失**
|
||||
|
||||
- 确认丢失
|
||||
|
||||
<img src="./../assets/TCPStopAndWaitProtocolAckMiss.png" style="zoom:30%" />
|
||||
|
||||
客户端发送一个分组报文 M1,服务端收到后发送一个确认报文,但这个确认报文丢失了。此时客户端依旧通过超时定时器,判断在期许的时间内没有收到来自服务端的确认报文,触发超时重传策略。重新发送分组报文 M1,服务端收到 M1 后,由于服务端已经接收过分组报文 M1 了,此时服务端做2件事:丢失重传的 M1 报文;重传确认 M1 报文。客户端收到 M1 确认报文,然后继续发送 M2...
|
||||
|
||||
- 确认迟到
|
||||
|
||||
<img src="./../assets/TCPStopAndWaitProtocolAckLater.png" style="zoom:30%" />
|
||||
|
||||
客户端发送 M1 报文,服务端收到 M1 后发送确认报文,但是由于网络情况不好,传输较慢,客户端在期许时间范围内没有收到确认报文。客户端在超时定时器的作用下,判断超时,触发重传策略。重新发送报文 M1,服务端收到重传的 M1 后,由于服务端之前已经接受过 M1 报文,所以做2件事情:丢弃重传的 M1 报文;重传 M1 确认报文。
|
||||
|
||||
客户端收到 M1 确认报文后,继续发送 M2...
|
||||
|
||||
随后的某个时间,来自服务端的 M1 确认报文到达了客户端,客户端收下后不做任何处理。
|
||||
|
||||
|
||||
|
||||
#### 面向字节流
|
||||
TCP 不像 UDP 那样一个个报文独立传输,而是在不保留报文边界的情况下以字节流方式进行传输。
|
||||
|
||||
<img src="./../assets/TCPOrientedByteBuffer.png" style="zoom:30%" />
|
||||
|
||||
发送方在进行数据发送的时候,在 TCP 层面会有一个发送缓存。接收方也有一个接收缓存。
|
||||
|
||||
之后发送的每一个数据都会经过 TCP 连接的逻辑通道传输给接收方。假设发送方要发送13、14,在放到发送缓存之后,至于每次要发送多少个字节是由 TCP 自己控制的。
|
||||
|
||||
5、6可能是发送方2次要传送的数据,放到发送缓存区后,TCP 将5、6拼接成一个 TCP 报文一次性发送给接收方。
|
||||
|
||||
不管发送方一次性提交给 TCP 缓存多大的数据,TCP 都会根据实际情况来进行划分,比如一次性可以传输几个字节。可能会将发送方的一个数据拆分为多个包进行传输。
|
||||
|
||||
|
||||
|
||||
当 TCP 在传输层发送消息时,一个消息可能会被分割成多个 TCP 报文进行转发给网络层。我们不能认为一个 TCP 报文就是一个消息,所以说 TCP 是面向字节流协议。
|
||||
|
||||
由于一个消息对应的不是一个 TCP 报文,如果接收方不知道一个消息的长度或者分割的边界在哪里,就会无法组装成一个消息,这就引入了**粘包问题**
|
||||
|
||||
|
||||
|
||||
#### 流量控制(滑动窗口)
|
||||
|
||||
流量控制是通过滑动窗口协议来实现的。
|
||||
|
||||
将数据划分为4种状态:
|
||||
|
||||
- 发送并被确认
|
||||
- 发送未被确认
|
||||
- 需要尽快发送的
|
||||
- 等待发送的
|
||||
|
||||
其中,发送未被确认的、需要尽快发送的,组成了 TCP 的窗口(因为这些部分的数据,需要核心管理和处理)。发送窗口要比发送缓存要小。
|
||||
|
||||
从左到右,字节编号,序号逐渐增大。
|
||||
|
||||
<img src="./../assets/TCPWindowElement.png" style="zoom:30%" />
|
||||
|
||||
- 可用窗口,主要是尽快要发送的部分
|
||||
- 重传队列,主要是发送但未被确认的部分
|
||||
|
||||
当发送但未被确认的部分,收到确认之后,窗口将进行合拢。假设 7、8、9 被发送后,变成下面的状态
|
||||
|
||||
<img src="./../assets/TCPWindowMove.png" style="zoom:30%" />
|
||||
|
||||
7、8、9变成了发送未被确认的状态,10、11、12变成了需要尽快发送的状态,窗口右移。
|
||||
|
||||
|
||||
|
||||
假设发送方处于 5G 网络,接收方处于 3G 网络,如果发送方发送太快会导致接收方接收缓存产生大量积累数据,所以需要接收方动态调整发送方发送窗口来决定发送速率。
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
在接收方侧:
|
||||
|
||||
<img src="./../assets/TCPWindowProcess.png" syle="zoom:30%" />
|
||||
|
||||
|
||||
|
||||
- 接受侧提交数据,必须是按序到达的部分。而未按序到达的字节,需要等全部按照顺序接收完毕,保证有序后才可以提交
|
||||
- 按序到达可以通过字节序号进行控制
|
||||
|
||||
|
||||
|
||||
#### 提供拥塞控制
|
||||
|
||||
当网络出现拥塞时,TCP 能够减小向网络注入数据的速率和数量,缓解拥塞
|
||||
|
||||
- 慢启动:慢开始、拥塞避免。
|
||||
- 快恢复、快重传
|
||||
|
||||
|
||||
|
||||
##### 慢开始、拥塞避免
|
||||
|
||||
<img src="./../assets/TCPCongestionControl.png" style="zoom:30%" />
|
||||
|
||||
|
||||
|
||||
工作流程:
|
||||
|
||||
- 一开始先发送1个报文,发现没有拥塞,就发送2个报文,发现依旧没有拥塞,继续翻倍,1、2、4、8、16。这个过程就是指数规律增长的过程,称为慢开始算法。
|
||||
- 当增长到窗口门限值16的时候,会采用拥塞避免策略,来进行发送报文数量的增长。比如发送17后发现没有拥塞、则增长为18,依次线性增长。
|
||||
- 当增长到24的时候,可能会发生网络拥塞。界定网络拥塞比较负责,我们可以简单的视为当连续3个报文的 ACK 没有收到的时候则认为发生了拥塞。拥塞后采用“乘法减小”的策略,来恢复到只发送1个报文的情况,来减少网络层压力。然后重新慢开始。同时将网络拥塞窗口值减少为之前的一半(比如之前的24,现在为12)
|
||||
- 调整后继续按照慢开始算法,比如:一开始先发送1个报文,发现没有拥塞,就发送2个报文,发现依旧没有拥塞,继续翻倍,1、2、4、8、16。这个过程就是指数规律增长的过程,称为慢开始算法。
|
||||
- 当达到门限值16之后,采用拥塞避免,加法增大的机制。比如:16、17、18...
|
||||
|
||||
|
||||
|
||||
##### 快重传
|
||||
|
||||
TCP 实现可靠传输依赖的是 **超时重传** 机制。TCP 在发送完数据后,会启动一个定时器。如果在定时器超时前没收到接收方发来的 ACK 确认,就重传数据。
|
||||
|
||||
这个是默认的情况,但是传输效率还是有点低,所以 TCP 为了更高的效率,采取了快重传机制。什么是快重传?
|
||||
|
||||
<img src="./../assets/TCPQuickResendSample.png" style="zoom:30%" />
|
||||
|
||||
- 左边是发送方,右边是接收方
|
||||
- 发送了序号为1的报文后,接收方收到,回复了一个 ACK2 的报文,ACK2 的意思就是“我收到报文1了,接下来我希望收到序号为2的报文”
|
||||
- 发送方发送序号为2的报文后,可能网络不好,也可能丢包了,接收方还没收到
|
||||
- 发送方发送序号为3的报文后,接收方收到了,但此时序号为2的报文还未收到,只能回复一个 ACK2,表示我接下来还是希望收到一个需要为2的报文
|
||||
- 发送方发送序号为4的报文后,可能网络不好,也可能丢包了,接收方还没收到
|
||||
- 发送方发送序号为5的报文后,接收方收到了,但此时序号为2的报文还未收到,只能回复一个 ACK2,表示我接下来还是希望收到一个需要为2的报文
|
||||
- 发送方发送序号为6的报文后,接收方收到了,但此时序号为2的报文还未收到,只能回复一个 ACK2,表示我接下来还是希望收到一个需要为2的报文
|
||||
|
||||
目前为止,除了第一个正常接受的报文1收到后,接收方回复了一个 ACK2 外,发送方已经收到3个连续的 ACK2,发送方会立即快速重传报文2。请注意,如果没有收到3个连续的 ACK2,发送会等到超时重传触发后(发送报文2的时候,也会启动一个超时计时器,等到 RTO 到了,会超时重传报文2),也会发送报文2。
|
||||
|
||||
快速重传,着重在“**快速**” ,相较于超时重传,它更快,更迅速,更能提高 TCP 传输效率。因为发送方收到3次 ACK2 触发快速重传的时候,可能超时重传策略还没触发。
|
||||
|
||||
|
||||
|
||||
- 快重传是为了解决在没有触发超时重传的时候,就触发发送方传递了数据
|
||||
- 快重传本质上是为了提高传输效率的
|
||||
- 快速重传不是以时间驱动,而是以数据驱动。发送方收到三次同样的确认报文后,会立即重传丢失的报文
|
||||
|
||||
|
||||
|
||||
##### 快恢复
|
||||
|
||||
<img src="./../assets/TCPSlipWindowProcess.png" style="zoom:40%"/>
|
||||
|
||||
|
||||
|
||||
当发生网络拥塞的时候,如果直接变为1,后续会再次经过慢开始(1、2、4、8、16)的过程,大多数情况下,网络可能没那么糟糕,所以不直接变为1,采用一个快速恢复的策略。
|
||||
|
||||
把新的拥塞窗口大小设置为新的门限值,12。从12开始不走慢开始(指数增长),而直接走快恢复(加法增大、线性增大)的流程。跳过了从1走指数增长到新的门限值的过程。
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
QA:上面4种策略是互斥的吗?怎么样工作的
|
||||
|
||||
慢开始、拥塞避免、快恢复、快重传是同时工作的。
|
||||
|
||||
- 一开始传输的时候,走的是慢开始算法,此时还没有到达拥塞避免阶段。此时的网络状态很好。假设此时某个报文丢失了,此时的重传是依靠超时定时器的,也就是说慢开始阶段走的是超时重传逻辑。
|
||||
- 等达到门限值之后,走的是拥塞避免算法,线性增大。
|
||||
|
||||
|
||||
|
||||
发送窗口大小 = Min(接收窗口 rwnd, 拥塞窗口 cwnd)
|
||||
|
||||
|
||||
|
||||
**收到重复的确认,说明发出去的数据发生丢包,意味着网络可能发生拥塞。TCP 同样会将门限阈值(ssthresh) 设为当前拥塞窗口 cwnd 的一半,并重新执行慢启动算法加以应对。**
|
||||
|
||||
|
||||
|
||||
#### 仅支持单向传输
|
||||
每条 TCP 传输连接只能有2个端点,只能进行点对点的数据传输,不支持多播和广播传输方式
|
||||
|
||||
|
||||
|
||||
#### TCP 提供全双工通信
|
||||
|
||||
TCP 允许通信双方的应用程序在任何都能发送数据,因为 TCP 连接的两端都设有缓存,用来临时存放双向通信的数据。当然,TCP 可以发送一个数据段,也可以缓存一段时间一边一次发送更多的数据段(最大的数据段大小取决于 MSS)
|
||||
|
||||
|
||||
|
||||
## TCP、UDP 对比
|
||||
|
||||
|
||||
Reference in New Issue
Block a user