TCP 知识自我汇总 (三次握手和四次握手,流量控制以及拥塞控制

2020-06-06  本文已影响0人  TimLi_51bb

1.TCP基本认识

什么是 TCP ?

TCP 是面向连接的、可靠的、基于字节流的传输层通信协议。因为 TCP 是一个工作在传输层可靠数据传输的服务,它能确保接收端接收的网络包是无损坏、无间隔、非冗余和按序的。

建立一个 TCP 连接是需要客户端与服务器端达成上述三个信息的共识。

Socket:由 IP 地址和端口号组成,序列号:用来解决乱序问题等,窗口大小:用来做流量控制

源地址和目的地址的字段(32位)是在 IP 头部中,作用是通过 IP 协议发送报文给对方主机。

源端口和目的端口的字段(16位)是在 TCP 头部中,作用是告诉 TCP 协议应该把报文发给哪个进程

序列号:在建立连接时由计算机生成的随机数作为其初始值,通过 SYN 包传给接收端主机,每发送一次数据,就「累加」一次该「数据字节数」的大小。用来解决网络包乱序问题。

确认应答号:指下一次「期望」收到的数据的序列号,发送端收到这个确认应答以后可以认为在这个序号以前的数据都已经被正常接收。用来解决不丢包的问题。

控制位:

ACK:该位为 1 时,「确认应答」的字段变为有效,TCP 规定除了最初建立连接时的 SYN 包之外该位必须设置为 1 。

RST:该位为 1 时,表示 TCP 连接中出现异常必须强制断开连接。

SYC:该位为 1 时,表示希望建立连,并在其「序列号」的字段进行序列号初始值的设定。

FIN:该位为 1 时,表示今后不会再有数据发送,希望断开连接。当通信结束希望断开连接时,通信双方的主机之间就可以相互交换 FIN 位置为 1 的 TCP 段。

2.TCP与UDP区别

UDP 和 TCP 有什么区别呢?分别的应用场景是?

UDP 不提供复杂的控制机制,利用 IP 提供面向「无连接」的通信服务。

UDP 协议真的非常简单,头部只有 8 个字节( 64 位),UDP 的头部格式如下:

1. 连接

TCP 是面向连接的传输层协议,传输数据前先要建立连接。UDP 是不需要连接,即刻传输数据。

2. 服务对象

TCP 是一对一的两点服务,即一条连接只有两个端点。UDP 支持一对一、一对多、多对多的交互通信

3. 可靠性

TCP 是可靠交付数据的,数据可以无差错、不丢失、不重复、按需到达。UDP 是尽最大努力交付,不保证可靠交付数据。

4. 拥塞控制、流量控制

TCP 有拥塞控制和流量控制机制,保证数据传输的安全性。

UDP 则没有,即使网络非常拥堵了,也不会

影响 UDP 的发送速率。

5. 首部开销

TCP 首部长度较长,会有一定的开销,首部在没有使用「选项」字段时是 20 个字节,如果使用了「选项」字段则会变长的。

UDP 首部只有 8 个字节,并且是固定不变的,开销较小。

TCP 和 UDP 应用场景:

由于 TCP 是面向连接,能保证数据的可靠性交付,因此经常用于:

FTP 文件传输,HTTP / HTTPS

由于 UDP 面向无连接,它可以随时发送数据,再加上UDP本身的处理既简单又高效,因此经常用于:

包总量较少的通信,如 DNS 、SNMP 等。 视频、音频等多媒体通信和广播通信

为什么 UDP 头部没有「首部长度」字段,而 TCP 头部有「首部长度」字段呢?

原因是 TCP 有可变长的「选项」字段,而 UDP 头部长度则是不会变化的,无需多一个字段去记录 UDP 的首部长度。

为什么 UDP 头部有「包长度」字段,而 TCP 头部则没有「包长度」字段呢?

先说说 TCP 是如何计算负载数据长度:TCP数据长度 = IP总长度 - IP首部长度 - TCP首部长度

TCP 三次握手过程和状态变迁

TCP 是面向连接的协议,所以使用 TCP 前必须先建立连接,而建立连接是通过三次握手而进行的

一开始,客户端和服务端都处于 CLOSED 状态。先是服务端主动监听某个端口,处于 LISTEN 状态

第一个报文:SYN报文
客户端会随机初始化序号(client_isn),将此序号置于 TCP 首部的序号字段中,同时把 SYN 标志位置为 1 。表示 SYN 报文。接着把第一个 SYN 报文发送给服务端,表示向服务端发起连接,该报文不包含应用层数据,之后客户端处于SYN-SENT 状态。

第二个报文 —— SYN + ACK 报文
服务端收到客户端的 SYN 报文后,首先服务端也随机初始化自己的序号(server_isn),将此序号填入 TCP 首部的「序号」字段中,其次把 TCP 首部的「确认应答号」字段填入 client_isn + 1, 接着把 SYN 和 ACK 标志位置为1。最后把该报文发给客户端,该报文也不包含应用层数据,之后服务端处于SYN-RCVD 状态。

第三个报文 —— ACK 报文
客户端收到服务端报文后,还要向服务端回应最后一个应答报文,首先该应答报文 TCP 首部 ACK 标志位置为 1 ,其次「确认应答号」字段填入 server_isn + 1,最后把报文发送给服务端,这次报文可以携带客户到服务器的数据,之后客户端处于 ESTABLISHED 状态。

服务器收到客户端的应答报文后,也进入 ESTABLISHED 状态。

从上面的过程可以发现第三次握手是可以携带数据的,前两次握手是不可以携带数据的,这也是面试常问的题。

一旦完成三次握手,双方都处于 ESTABLISHED 状态,此致连接就已建立完成,客户端和服务端就可以相互发送数据了。

如何在 Linux 系统中查看 TCP 状态?

TCP 的连接状态查看,在 Linux 可以通过 netstat -napt 命令查看。

为什么是三次握手?不是两次、四次?

接下来以三个方面分析三次握手的原因:

三次握手才可以阻止历史重复连接的初始化(主要原因)

三次握手才可以同步双方的初始序列号

三次握手才可以避免资源浪费

原因一:避免历史连接

三次握手的首要原因是为了防止旧的重复连接初始化造成混乱。

客户端连续发送多次 SYN 建立连接的报文,在网络拥堵等情况下:

一个「旧 SYN 报文」比「最新的 SYN 」 报文早到达了服务端;

那么此时服务端就会回一个 SYN + ACK 报文给客户端;

客户端收到后可以根据自身的上下文,判断这是一个历史连接(序列号过期或超时),那么客户端就会发送 RST 报文给服务端,表示中止这一次连接。

如果是两次握手连接,就不能判断当前连接是否是历史连接,三次握手则可以在客户端(发送方)准备发送第三次报文时,客户端因有足够的上下文来判断当前连接是否是历史连接:

如果是历史连接(序列号过期或超时),则第三次握手发送的报文是 RST 报文,以此中止历史连接;如果不是历史连接,则第三次发送的报文是 ACK 报文,通信双方就会成功建立连接;所以, TCP 使用三次握手建立连接的最主要原因是防止历史连接初始化了连接。

原因二:同步双方初始序列号

TCP 协议的通信双方, 都必须维护一个「序列号」, 序列号是可靠传输的一个关键因素,它的作用:

接收方可以去除重复的数据;

接收方可以根据数据包的序列号按序接收;

可以标识发送出去的数据包中, 哪些是已经被对方收到的;

序列号在 TCP 连接中占据着非常重要的作用,所以当客户端发送携带「初始序列号」的 SYN 报文的时候,需要服务端回一个 ACK 应答报文,表示客户端的 SYN 报文已被服务端成功接收,那当服务端发送「初始序列号」给客户端的时候,依然也要得到客户端的应答回应,这样一来一回,才能确保双方的初始序列号能被可靠的同步。

原因三:避免资源浪费

如果只有「两次握手」,当客户端的 SYN 请求连接在网络中阻塞,客户端没有接收到ACK 报文,就会重新发送 SYN ,由于没有第三次握手,服务器不清楚客户端是否收到了自己发送的建立连接的 ACK 确认信号,所以每收到一个 SYN 就只能先主动建立一个连接,这会造成什么情况呢?

如果客户端的 SYN 阻塞了,重复发送多次 SYN 报文,那么服务器在收到请求后就会建立多个冗余的无效链接,造成不必要的资源浪费。

MTU 与 MSS

MTU:一个网络包的最大长度,以太网中一般为 1500 字节;

MSS:除去 IP 和 TCP 头部之后,一个网络包所能容纳的 TCP 数据的最大长度;

如果TCP 的整个报文(头部 + 数据)交给 IP 层进行分片,会有什么异常呢?

当 IP 层有一个超过 MTU 大小的数据(TCP 头部 + TCP 数据)要发送,那么 IP 层就要进行分片,把数据分片成若干片,保证每一个分片都小于 MTU。把一份 IP 数据报进行分片以后,由目标主机的 IP 层来进行重新组装后,在交给上一层 TCP 传输层。

这看起来井然有序,但这存在隐患的,那么当如果一个 IP 分片丢失,整个 IP 报文的所有分片都得重传

因为 IP 层本身没有超时重传机制,它由传输层的 TCP 来负责超时和重传。

当接收方发现 TCP 报文(头部 + 数据)的某一片丢失后,则不会响应 ACK 给对方,那么发送方的 TCP 在超时后,就会重发「整个 TCP 报文(头部 + 数据)」。

因此,可以得知由 IP 层进行分片传输,是非常没有效率的。

所以,为了达到最佳的传输效能 TCP 协议在建立连接的时候通常要协商双方的 MSS 值,当 TCP 层发现数据超过 MSS 时,则就先会进行分片,当然由它形成的 IP 包的长度也就不会大于 MTU ,自然也就不用 IP 分片了。

什么是 SYN 攻击?如何避免 SYN 攻击?

SYN 攻击

我们都知道 TCP 连接建立是需要三次握手,假设攻击者短时间伪造不同 IP 地址的SYN 报文,服务端每接收到一个 SYN 报文,就进入SYN_RCVD 状态,但服务端发送出去的 ACK + SYN 报文,无法得到未知 IP 主机的 ACK 应答,久而久之就会占满服务端的 SYN 接收队列(未连接队列),使得服务器不能为正常用户服务。

避免 SYN 攻击方式一

其中一种解决方式是通过修改 Linux 内核参数,控制队列大小和当队列满时应做什么处理。

当网卡接收数据包的速度大于内核处理的速度时,会有一个队列保存这些数据包。控制该队列的最大值如下参数:

net.core.netdev_max_backlog

SYN_RCVD 状态连接的最大个数:

net.ipv4.tcp_max_syn_backlog

超出处理能时,对新的 SYN 直接回 RST,丢弃连接:

net.ipv4.tcp_abort_on_overflow

四次挥手都做什么?

TCP的连接是全双工的,所以连接的拆除需要单独将两个通道分别拆除,而四次挥手所做的事情就是拆除两条通道释放资源

TCP 提供了连接的一端结束他的发送后,还能接收来自另一端数据的能力,也就是所谓的半关闭。

这里以Client作为主动发起端,Server作为被动关闭端。

第一步,Client主动发起一个Req给Server,里面包含FIN标识位=1,CLient的Seq序列号N,表示的是当前Client在该连接上的当前序列号。

第二步,Server端在收到这个含有FIN的Req消息之后,校验无误之后会立马回复ACK消息给CLient端,消息内部包含ACK标志位为1,同时Seq号码是FIN的请求消息的Seq号+1。此时的Sever同时会主动发个结束标识给Server上面的应用层程序,应用层程序可以决定是立马结束,还是等到服务其上面的该连接中的数据处理完了之后,在发送FIN消息给Client来关掉另外的一半连接。

第三步,Server端在处理完该连接上面的Pending住的数据之后,应用程序会close这个连接。Client会主动发起FIN的Req消息给Client端。消息内部带有,FIN=1的结束符标识位,以及Server端的Seq序列号。

第四步,Client端在收到对应的FIN消息之后,会主动通知应用层程序,告知这个连接现在需要关闭了。然后,Client会回复ACK消息给Server,以便断开另外一个方向的通道,这个消息包含ACK=1的标识位和FIN的REQ带过来的Seq+1。

为什么挥手需要四次?

因为TCP是一个全双工协议,必须单独拆除每一条信道。4次挥手的目的是终止数据传输,并回收资源,此时两个端点两个方向的序列号已经没有了任何关系,必须等待两方向都没有数据传输时才能拆除虚链路,不像初始化时那么简单,发现SYN标志就初始化一个序列号并确认SYN的序列号。因此必须单独分别在一个方向上终止该方向的数据传输。

再来回顾下四次挥手双方发 FIN 包的过程,就能理解为什么需要四次了。

关闭连接时,客户端向服务端发送 FIN 时,仅仅表示客户端不再发送数据了但是还能接收数据。

服务器收到客户端的 FIN 报文时,先回一个 ACK 应答报文,而服务端可能还有数据需要处理和发送,等服务端不再发送数据时,才发送 FIN 报文给客户端来表示同意现在关闭连接。

从上面过程可知,服务端通常需要等待完成数据的发送和处理,所以服务端的 ACK 和FIN 一般都会分开发送,从而比三次握手导致多了一次。

四次挥手的状态转换?

Client端:

ESTABLISHED---发送FIN给Server-->FIN_WAIT_1---接收到Server端的FIN对应的ACK-->FIN_WAIT_2---收到Server端发送过来的FIN消息-->FIIN_WAIT--2MSL之后会进入-->CLOSED

Server端:

ESTABLISHED---接收到Client端的FIN->CLOSED_WAIT--Server端的应用程序关闭发送FIN--> LAST_ACK---收到Client对于FIN的ACK响应-->FIIN_WAIT---->CLOSED

最简单的理解

一:建立TCP连接:三次握手协议

客户端:我要对你讲话,你能听到吗;

服务端:我能听到;而且我也要对你讲话,你能听到吗;

客户端:我也能听到。

…….

互相开始通话

……..   

二:关闭TCP连接:四次握手协议

客户端:我说完了,我要闭嘴了;

服务端:我收到请求,我要闭耳朵了;

(客户端收到这个确认,于是安心地闭嘴了。)

…….

    服务端还没倾诉完自己的故事,于是继续唠唠叨叨向客户端说了半天,直到说完为止

…….

服务端:我说完了,我也要闭嘴了;

客户端:我收到请求,我要闭耳朵了;(事实上,客户端为了保证这个确认包成功送达,等待了两个最大报文生命周期后,才闭上耳朵。)

(服务端收到这个确认,于是安心地闭嘴了。)

TCP如何流量控制

TCP 为了防止发送方无脑的发送数据,导致接收方缓冲区被填满,所以就有了滑动窗口的机制,它可利用接收方的接收窗口来控制发送方要发送的数据量,也就是流量控制。

接收窗口是由接收方指定的值,存储在 TCP 头部中,它可以告诉发送方自己的 TCP 缓冲空间区大小,这个缓冲区是给应用程序读取数据的空间:

如果应用程序读取了缓冲区的数据,那么缓冲空间区的就会把被读取的数据移除

如果应用程序没有读取数据,则数据会一直滞留在缓冲区。

接收窗口的大小,是在 TCP 三次握手中协商好的,后续数据传输时,接收方发送确认应答 ACK 报文时,会携带当前的接收窗口的大小,以此来告知发送方。

假设接收方接收到数据后,应用层能很快的从缓冲区里读取数据,那么窗口大小会一直保持不变,过程如下:

但是现实中服务器会出现繁忙的情况,当应用程序读取速度慢,那么缓存空间会慢慢被占满,于是为了保证发送方发送的数据不会超过缓冲区大小,则服务器会调整窗口大小的值,接着通过 ACK 报文通知给对方,告知现在的接收窗口大小,从而控制发送方发送的数据大小。

零窗口通知与窗口探测

假设接收方处理数据的速度跟不上接收数据的速度,缓存就会被占满,从而导致接收窗口为 0,当发送方接收到零窗口通知时,就会停止发送数据。

如下图,可以接收方的窗口大小在不断的收缩至 0:

窗口大小在收缩

接着,发送方会定时发送窗口大小探测报文,以便及时知道接收方窗口大小的变化。

以下图 Wireshark 分析图作为例子说明:

零窗口 与 窗口探测

发送方发送了数据包 1 给接收方,接收方收到后,由于缓冲区被占满,回了个零窗口通知;

发送方收到零窗口通知后,就不再发送数据了,直到过了 3.4 秒后,发送了一个 TCP Keep-Alive 报文,也就是窗口大小探测报文;

当接收方收到窗口探测报文后,就立马回一个窗口通知,但是窗口大小还是 0;

发送方发现窗口还是 0,于是继续等待了 6.8(翻倍) 秒后,又发送了窗口探测报文,接收方依然还是回了窗口为 0 的通知;

发送方发现窗口还是 0,于是继续等待了 13.5(翻倍) 秒后,又发送了窗口探测报文,接收方依然还是回了窗口为 0 的通知;

可以发现,这些窗口探测报文以 3.4s、6.5s、13.5s 的间隔出现,说明超时时间会翻倍递增。

TCP 的拥塞控制原理

什么是 TCP 拥塞控制

TCP 拥塞控制的目标是最大化利用网络上瓶颈链路的带宽。

Reno

Reno 被许多教材(例如:《计算机网络——自顶向下的方法》)所介绍,适用于低延时、低带宽的网络,它将拥塞控制的过程分为四个阶段:慢启动、拥塞避免、快重传和快恢复,对应的状态如下所示:

慢启动阶段思路是不要一开始就发送大量的数据,先探测一下网络的拥塞程度,也就是说由小到大逐渐增加拥塞窗口的大小,在没有出现丢包时每收到一个 ACK 就将拥塞窗口大小加一(单位是 MSS,最大单个报文段长度),每轮次发送窗口增加一倍,呈指数增长,若出现丢包,则将拥塞窗口减半,进入拥塞避免阶段;

当窗口达到慢启动阈值或出现丢包时,进入拥塞避免阶段,窗口每轮次加一,呈线性增长;当收到对一个报文的三个重复的 ACK 时,认为这个报文的下一个报文丢失了,进入快重传阶段,要求接收方在收到一个失序的报文段后就立即发出重复确认(为的是使发送方及早知道有报文段没有到达对方,可提高网络吞吐量约20%)而不要等到自己发送数据时捎带确认;

快重传完成后进入快恢复阶段,将慢启动阈值修改为当前拥塞窗口值的一半,同时拥塞窗口值等于慢启动阈值,然后进入拥塞避免阶段,重复上述过程。

BBR 算法不将出现丢包或时延增加作为拥塞的信号,而是认为当网络上的数据包总量大于瓶颈链路带宽和时延的乘积时才出现了拥塞,所以 BBR 也称为基于拥塞的拥塞控制算法(Congestion-Based Congestion Control),其适用网络为高带宽、高时延、有一定丢包率的长肥网络,可以有效降低传输时延,并保证较高的吞吐量,

BBR 算法周期性地探测网络的容量,交替测量一段时间内的带宽极大值和时延极小值,将其乘积作为作为拥塞窗口大小,使得拥塞窗口始的值始终与网络的容量保持一致。

在有一定丢包率的网络链路上充分利用带宽。降低网络链路上的 buffer 占用率,从而降低延迟。

基于丢包的拥塞控制:将丢包视为出现拥塞,采取缓慢探测的方式,逐渐增大拥塞窗口,当出现丢包时,将拥塞窗口减小,如 Reno、Cubic 等。

基于时延的拥塞控制:将时延增加视为出现拥塞,延时增加时增大拥塞窗口,延时减小时减小拥塞窗口,如 Vegas、FastTCP 等。

基于链路容量的拥塞控制:实时测量网络带宽和时延,认为网络上报文总量大于带宽时延乘积时出现了拥塞,如 BBR。

基于学习的拥塞控制:没有特定的拥塞信号,而是借助评价函数,基于训练数据,使用机器学习的方法形成一个控制策略,如 Remy。

上一篇下一篇

猜你喜欢

热点阅读