参考文章
35 张图解:被问千百遍的 TCP 三次握手和四次挥手面试题
通俗易懂讲解TCP流量控制机制,了解一下
TCP的拥塞控制(详解)
什么是 TCP
TCP 是面向连接的、可靠的、有序的、基于字节流的传输层通信协议。
- 面向连接:一定是「一对一」才能连接,不能像 UDP 协议可以一个主机同时向多个主机发送消息,也就是一对多是无法做到的;
- 可靠的:无论的网络链路中出现了怎样的链路变化,TCP 都可以保证一个报文一定能够到达接收端;
- 字节流:消息是「没有边界」的,所以无论我们消息有多大都可以进行传输。并且消息是「有序的」,当「前一个」消息没有收到的时候,即使它先收到了后面的字节,那么也不能扔给应用层去处理,同时对「重复」的报文会自动丢弃。
为什么需要 TCP 协议? TCP 工作在哪一层?
网络层的 IP 协议「不可靠」的,它不保证数据的完整性。如果需要保障网络数据包的可靠性,那么就需要由上层(传输层)的 TCP 协议来负责。
TCP 基本认识
TCP 为应用层提供全双工服务,这意味数据能在两个方向上独立地进行传输。
TCP 在传输之前会进行三次沟通,一般称为“三次握手”,传完数据断开的时候要进行四次沟通,一般称为“四次挥手”
-
源端口号( 16 位):它(连同源主机 IP 地址)标识源主机的一个应用进程
-
目的端口号( 16 位):它(连同目的主机 IP 地址)标识目的主机的一个应用进程
-
顺序号 seq( 32 位):在建立连接时由计算机生成的随机数作为其初始值,通过 SYN 包传给接收端主机,每发送一次数据,就「累加」一次该「数据字节数」的大小。用来解决网络包乱序问题。
-
确认号 ack( 32 位):指下一次「期望」收到的数据的序列号。因此,确认序号应当是上次已成功收到数据字节顺序号加 1 。只有 ACK 标志为 1 时确认序号字段才有效。 发送端收到这个确认应答以后可以认为在这个序号以前的数据都已经被正常接收。用来解决不丢包的问题。
-
首部长度( 4 位):任选 字段的长度可变,所以需要这个值指明首部的整体长度,方便取数据(数据从哪里开始)。这个字段占 4bit ,因此 TCP 最多有 60 字节的首部。然而,没有任选字段,正常的长度是 20 字节。
-
保留位( 6 位):保留给将来使用,目前必须置为
-
控制位:
SYN:同步序号,为 1 表示连接请求,用于建立连接和使顺序号同步( synchronize )
ACK:为 1 表示确认号有效,为 0 表示报文中不包含确认信息,忽略确认号字段 。
FIN:用于释放连接,为 1 表示发送方已经没有数据发送了,即关闭本方数据流。 -
窗口大小( 16 位):数据字节数
-
校验和( 16 位):此校验和是对整个的 TCP 报文段,包括 TCP 头部和 TCP 数据,以 16 位字进行计算所得。用于接收端进行数据安全验证。
-
数据: 具体的数据,是可选的。在一个连接建立和一个连接终止时,双方交换的报文段仅有 TCP 首部。如果一方没有数据要发送,也使用没有任何数据的首部来确认收到的数据。在处理超时的许多情况中,也会发送不带任何数据的报文段
如何唯一确定一个 TCP 连接呢
源地址 + 源端口 和 目的地址 + 目的端口 可以唯一的确定一个连接
源地址 和 目的地址 的字段(32位)是在 IP 头部中,作用是通过 IP 协议发送报文给对方主机
源端口和目的端口的字段(16位)是在 TCP 头部中,作用是告诉 TCP 协议应该把报文发给哪个进程
TCP 三次握手
- 第一次握手:主机 A 发送位码为 syn=1,随机产生 seq number=1234567 的数据包到服务器,主机 B 由 SYN=1
知道,A 要求建立联机; - 第二次握手:主机 B 收到请求后要确认联机信息,向 A 发 送 ack number=( 主 机 A 的seq+1),syn=1,ack=1,随机产生 seq=7654321 的包
- 第三次握手:主机 A 收到后检查 ack number 是否正确,即第一次发送的 seq number+1,以及位码 ack 是否为 1,若正确,主机 A 会再发送 ack number=(主机 B 的 seq+1),ack=1,主机 B 收到后确认 seq 值与 ack=1 则连接建立成功。
为什么客户端和服务端的初始序列号 ISN 是不相同的?
- 如果一个已经失效的连接被重用了,但是该旧连接的历史报文还残留在网络中,如果序列号相同,那么就无法分辨出该报文是不是历史报文,如果历史报文被新的连接接收了,则会产生数据错乱。所以,每次建立连接前重新初始化一个序列号主要是为了通信双方能够根据序号将不属于本连接的报文段丢弃。
- 另一方面是为了安全性,防止黑客伪造的相同序列号的 TCP 报文被对方接收。
TCP为什么是三次握手?不是两次、四次
原因一:三次握手才能保证双方具有接收和发送的能力。
TCP 为应用层提供全双工服务,这意味数据能在两个方向上独立地进行传输。双方都需要确认对方有接收和发送的能力。
原因二: 避免历史连接
网络环境是错综复杂的,往往并不是如我们期望的一样,先发送的数据包,就先到达目标主机,反而它很乱,可能会由于网络拥堵等乱七八糟的原因,会使得旧的数据包,先到达目标主机,那么这种情况下 TCP 三次握手是如何避免的呢?
如果是两次握手连接,就不能判断当前连接是否是历史连接,三次握手则可以在客户端(发送方)准备发送第三次报文时,客户端因有足够的上下文来判断当前连接是否是历史连接:
- 如果是历史连接(序列号过期或超时),则第三次握手发送的报文是 RST 报文,以此中止历史连接;
- 如果不是历史连接,则第三次发送的报文是 ACK 报文,通信双方就会成功建立连接;
原因三:同步双方初始序列号
TCP 协议的通信双方, 都必须维护一个「序列号」, 序列号是可靠传输的一个关键因素,它的作用:
- 接收方可以去除重复的数据;
- 接收方可以根据数据包的序列号按序接收;
- 可以标识发送出去的数据包中, 哪些是已经被对方收到的;
可见,序列号在 TCP 连接中占据着非常重要的作用,所以当客户端发送携带「初始序列号」的 SYN 报文的时候,需要服务端回一个 ACK 应答报文,表示客户端的 SYN 报文已被服务端成功接收,那当服务端发送「初始序列号」给客户端的时候,依然也要得到客户端的应答回应,这样一来一回,才能确保双方的初始序列号能被可靠的同步。
四次握手其实也能够可靠的同步双方的初始化序号,但由于第二步和第三步可以优化成一步,所以就成了「三次握手」。
而两次握手只保证了一方的初始序列号能被对方成功接收,没办法保证双方的初始序列号都能被确认接收。
小结
TCP 建立连接时,通过三次握手能确保双方都具有接收和发送的能力,防止历史连接的建立,能帮助双方同步初始化序列号。
序列号能够保证数据包不重复、不丢弃和按序传输。
不使用「两次握手」的原因:
- 无法确保双方都具有接收和发送的能力,无法防止历史连接的建立,也无法可靠的同步双方序列号;
不使用「四次握手」的原因:
- 三次握手就已经理论上最少可靠连接建立,所以不需要使用更多的通信次数。
UDP 基本认识
UDP 不提供复杂的控制机制,利用 IP 提供面向「无连接」的通信服务。
UDP 协议真的非常简,头部只有 8 个字节( 64 位),UDP 的头部格式如下:
- 目标和源端口:主要是告诉 UDP 协议应该把报文发给哪个进程。
- 包长度:该字段保存了 UDP 首部的长度跟数据的长度之和。
- 校验和:校验和是为了提供可靠的 UDP 首部和数据而设计。
TCP 和 UDP 区别
-
连接
TCP 是面向连接的传输层协议,传输数据前先要建立连接。
UDP 是不需要连接,即刻传输数据。 -
服务对象
TCP 是一对一的两点服务,即一条连接只有两个端点。
UDP 支持一对一、一对多、多对多的交互通信 -
可靠性
TCP 是可靠交付数据的,数据可以无差错、不丢失、不重复、按序到达。
UDP 是尽最大努力交付,不保证可靠交付数据。 -
拥塞控制、流量控制
TCP 有拥塞控制和流量控制机制,保证数据传输的安全性。
UDP 则没有,即使网络非常拥堵了,也不会影响 UDP 的发送速率。 -
首部开销
TCP 首部长度较长,会有一定的开销,首部在没有使用「选项」字段时是 20 个字节,如果使用了「选项」字段则会变长的。
UDP 首部只有 8 个字节,并且是固定不变的,开销较小。 -
传输方式
TCP 是流式传输,没有边界,但保证顺序和可靠。
UDP 是一个包一个包的发送,是有边界的,但可能会丢包和乱序。 -
分片不同 (不是很懂)
TCP 的数据大小如果大于 MSS 大小,则会在传输层进行分片,目标主机收到后,也同样在传输层组装 TCP 数据包,如果中途丢失了一个分片,只需要传输丢失的这个分片。
UDP 的数据大小如果大于 MTU 大小,则会在 IP 层进行分片,目标主机收到后,在 IP 层组装完数据,接着再传给传输层,但是如果中途丢了一个分片,则就需要重传所有的数据包,这样传输效率非常差,所以通常 UDP 的报文应该小于 MTU。
为什么 UDP 头部没有「首部长度」字段,而 TCP 头部有「首部长度」字段呢
原因是 TCP 有可变长的「选项」字段,而 UDP 头部长度则是不会变化的,无需多一个字段去记录 UDP 的首部长度。
TCP 连接断开
TCP 断开连接要进行四次。这是由于 TCP 的半关闭造成的。因为 TCP 连接是全双工的(即数据可在两个方向上同时传递)所以进行关闭时每个方向上都要单独进行关闭。这个单方向的关闭就叫半关闭。当一方完成它的数据发送任务,就发送一个 FIN 来向另一方通告将要终止这个
方向的连接。 首先进行关闭的一方将执行主动关闭,而另一方执行被动关闭
- 关闭客户端到服务器的连接:首先客户端 A 发送一个 FIN,用来关闭客户到服务器的数据传送, 然后等待服务器的确认。其中终止标志位 FIN=1,序列号 seq=u
- 服务器收到这个 FIN,它发回一个 ACK,确认号 ack 为收到的序号加 1。客户端A收到ack验证是否正确(u+1),正确关闭连接。
- 关闭服务器到客户端的连接:也是发送一个 FIN 给客户端。
- 客户段收到 FIN 后,并发回一个 ACK 报文确认,并将确认序号 seq 设置为收到序号加 1。
主机 A 发送 FIN 后,进入终止等待状态, 服务器 B 收到主机 A 连接释放报文段后,就立即给主机 A 发送确认,然后服务器 B 就进入 close-wait 状态,此时 TCP 服务器进程就通知高层应用进程,因而从 A 到 B 的连接就释放了。此时是“半关闭”状态。即 A 不可以发送给
B,但是 B 可以发送给 A。
此时,若 B 没有数据报要发送给 A 了,其应用进程就通知 TCP 释放连接,然后发送给 A 连接释放报文段,并等待确认。A 发送确认后,进入 time-wait,注意,此时 TCP 连接还没有释放掉,然后经过时间等待计时器设置的 2MSL 后,A 才进入到close 状态。
TCP为什么挥手需要四次
从上面过程可知,服务端通常需要等待完成数据的发送和处理,所以服务端的 ACK 和 FIN 一般都会分开发送,从而比三次握手导致多了一次。
为什么 TIME_WAIT 等待的时间是 2MSL
MSL 是 Maximum Segment Lifetime,报文最大生存时间 >=发送一趟数据的传播时间。
如果被动关闭方没有收到断开连接的最后的 ACK 报文,就会触发超时重发 Fin 报文,另一方接收到 FIN 后,会重发 ACK 给被动关闭方。
一来一去正好 2 个 MSL。
2MSL 的时间是从客户端接收到 FIN 后发送 ACK 开始计时的。如果在 TIME-WAIT 时间内,因为客户端的 ACK 没有传输到服务端,客户端又接收到了服务端重发的 FIN 报文,那么 2MSL 时间将重新计时。
TCP 控制流程
现象:发生方发数据的速率大于接收方处理数据的速率时,接收方处理数据不及时,时间一长接收方的数据缓存空间会满。此时发送方还发送数据,接收方则会丢失数据,造成网络资源浪费。
术语:
接收窗口:接收方的缓存区的剩余空间;
发送窗口:发送方的发送数据大小;
解决:接受方每次节接受到数据后会返回自己的接受窗口大小win,发送方根据接受窗口的大小调整发送窗口,保持双方的收发平衡。
详细文章请看 通俗易懂讲解TCP流量控制机制,了解一下
TCP 拥塞控制
现象:对网络中某一资源的需求超过了该资源所能提供的可用部分,网络性能就要变坏,这种情况就叫做网络拥塞。带宽、交换结点中的缓存和处理机等,都是网络的资源。若出现拥塞而不进行控制,整个网络的吞吐量将随输入负荷的增大而下降。
术语:
拥塞窗口(cwnd/swnd):发送方发送报文的数量。不是固定值,初始1,没有发生拥塞前cwnd变大,发生拥塞后cwnd变小。
慢开始门限(ssthresh):一个状态变量,用于控制 拥塞窗口 增值的阈值
cwnd < ssthresh :使用 慢开始 增长cwnd 的值
cwnd > ssthresh : 改用 拥塞控制 增长cwnd 的值
cwnd = ssthresh :慢开始/拥塞控制 增长cwnd 的值
慢开始:每个传输轮次,按指数(乘2)增长 cwnd 的值。
拥塞控制:每个传输轮次,按线性(加1)增长 cwnd 的值。
快重传:尽快的重传(连续收到3个重复确认),不用等到超时重传。
快恢复:发送拥塞后cwnd 不是变成1,而是 = 拥塞值/2 向下取整。
解决方案:
发送方开始发生数据时是1个报文,收到确认报文校验没有问题,每轮传输后按**慢开始算法(乘2)快速增长拥塞窗口。当到达慢开始门限后,按拥塞避免算法(加一)**缓慢的增加拥塞窗口。如果发生拥塞(超时重传),则调用 快恢复 算法调整 拥塞窗口和慢开始门限= 拥塞值/2 向下取整,再发生数据。