tcp协议知识详解

一、网络模型

OSI七层模型

简单了解,七层模型的应用并不广泛,主要是因为商业推动力不足、设计比较复杂,运行效率低、设计周期太长,未能及时进入市场、部分分层功能重复。
在这里插入图片描述

  • 物理层:

物理层负责在设备和物理传输介质之间传输、接收非结构化数据,它将数字 bits 转换为电、无线电或光信号。规范定义了如电压、电压变化时间、物理速率、最大传输距离等。
物理层负责将单个 bits 从一个节点传输到下一个节点。当接收数据时,物理层接收信号、将其转换为0和1,并发送给数据链路层,链路层将 frame 组合为原始状态。

可见,物理层是对二进制0和1与电压的转换。这是计算机的最底层。

  • 数据链路层:
    数据链路层功能如下:
    1.封装成帧 framing:它为发送方提供了一种对接收方有意义的一组比特,其通过将特殊的位模式附加到帧的开头和结尾来实现。
    2.物理寻址 physical addressing:创建帧后,data link layer 为每一帧的 header 添加发送方、接收方的 MAC 地址。
    3.错误控制 error control:data link layer 的错误控制机制会检测、重发丢失或损坏的帧。
    4.流控制 flow control:双方的速率必须保持恒定,否则数据可能会被损坏。Flow control 协调在收到确认之前可发送的数据量。
    5.访问控制 access control:多个设备共享一个通信通道时,MAC 子层用于确定给定时间哪个设备可以控制该通道。

链路层中的数据包称为帧。

  • 网络层
    网络层用于将数据从一台主机传输到位于不同网络中的另一台主机。它还负责分组路由,即从多条路线中选取路径最短的。Network layer 会把发送者、接收者的 IP 地址放到 header 中。
    网络层功能如下:
    1.路由 routing:网络层协议决定从源到目的地选取哪条线路。
    2.逻辑寻址 logical addressing:为唯一地标识互联网上的每个设备,网络层定义了一种寻址方案。发送者和接收者的 IP 地址由网络层放置到 header 中。

  • 传输层
    传输层从网络层获取服务,并向应用层提供服务。Transport layer 提供端到端的消息传递服务,发送成功后返回确认、数据出错后重发的功能。Transport layer 中的数据称为 segments。
    提供以下的功能:
    1.分段和重组 segmentation and reassembly:传输层从 session layer 接收 message,将 message 分割为更小单位,每个 segment 都关联一个 header。传输层在目标端重组 message。
    2.服务点寻址 service point addressing:为了将消息传递给正确的进程,transport layer header 包含 service point addressing 或 port address。

TCP和UDP协议就在这一层。这一层由操作系统来控制实现

  • 会话层

会话层负责建立连接,维护会话、认证,并确保安全。

  • 表示层
    表示层也称为转换层(translation layer)。在表示层提取应用层的数据,并根据需要转换格式,以便通过网络传输。
    例如,将 ASCII 转换为 EBCDIC。加密、解密。加密时将数据转换为另一种样式,加密后的数据被称为密文,解密后的数据被称为明文。加密、解密时需要使用对应的 key。压缩可以减少需传送的数据量。
  • 应用层
    应用层是 app 访问网络、向用户显示接收到信息的窗口。

TCP/IP四层模型

四层模型是对上述七层模型的优化和提炼。包括网络接口层 Network Interface、网络层 Internet Layer、传输层 Transport Layer / Host-to-Host、应用层 Application Layer。
其中,TCP/IP是Transmission Control Protocol/Internet Protocol 的缩写。这里的TCP/IP是一个协议族。里面包含了很多种协议。要和TCP协议以及我们平时所说的ip地址要区分开。

在这里插入图片描述
由上图可知,四层模型的网络接口层对应七层模型的物理层和链路层;应用层对应应用层、表现层和会话层;网络层和传输层与七层模型保持一致。

  • 网络接口层
    TCP/IP 模型中的 network interface 对应 OSI model 中的 data link 和 physical。网络接口层进行硬件寻址、物理传输数据。
  • 网络层
    TCP/IP模型中的网络层与 OSI 模型中的网络层对应,定义了数据逻辑传输的协议。网络层主要协议有IP、ICMP、ARP协议。这些就是上述所说的IP族的协议。
  • 传输层
    TCP/IP 模型中的 transport layer 对应 OSI 模型中的 transport layer,负责端到端数据传输和错误控制。Transport layer 主要协议有面向连接的 TCP 协议、无链接的 UDP 协议。
  • 应用层
    TCP/IP 模型中的应用层对应 OSI 模型中的 application layer、presentation layer、session layer 三层。负责节点到节点的通信,并控制用户界面。
    应用层协议有:HTTP、HTTPS、FTP、TFTP、Telnet、SSH、SMTP、SNMP、DNS等。

五层模型

虽然 OSI 模型由国际标准组织制定,但其实现过于复杂、制定周期过长,在其整套标准推出之前,TCP/IP 模型已经在全球范围内被广泛使用,因此,TCP/IP 模型才是事实上的标准。

TCP/IP 模型定义了应用层、传输层、网际层、网络接口层共四层,但并没有给出接口层的具体实现。因此,通常将网络接口层替换为 OSI 七层模型中的数据链路层和物理层,这就是五层网络模型:
在这里插入图片描述

二、TCP协议握手挥手

随着对TCP协议的深入研究,发现TCP的握手和挥手这个老生常谈的问题,在实际应用中碰到的TCP问题中,频率并不多见。那为啥还对TCP的握手和挥手有如此多的人研究呢?原因很简单,因为面试要考,是八股文。没办法,博主也改变不了这个八股文的现状,这里简单记录一下吧。

三次握手:

在这里插入图片描述
第一次握手:客户端将请求报文标志位 SYN 置为 1,请求报文的 Sequence Number 字段(简称 seq)中填入一个随机值 J,并将该数据包发送给服务器端,客户端进入 SYN_SENT 状态,等待服务器端确认。

第二次握手:服务器端收到数据包后由请求报文标志位 SYN=1 知道客户端请求建立连接,服务器端将应答报文标志位 SYN 和 ACK 都置为 1,应答报文的 Acknowledgment Number字段(简称 ack)中填入 ack=J+1,应答报文的 seq 中填入一个随机值 K,并将该数据包发送给客户端以确认连接请求,服务器端进入 SYN_RCVD 状态。

第三次握手:客户端收到应答报文后,检查 ack 是否为 J+1,ACK 是否为 1,如果正确则将第三个报文标志位 ACK 置为 1,ack=K+1,并将该数据包发送给服务器端,服务器端检查 ack 是否为 K+1,ACK 是否为 1,如果正确则连接建立成功,客户端和服务器端进入ESTABLISHED 状态,完成三次握手,随后客户端与服务器端之间可以开始传输数据了。

四次挥手

在这里插入图片描述
(1)某个应用进程首先调用 close,我们称该端执行主动关闭(active close)。该端的 TCP于是发送一个 FIN 分节,表示数据发送完毕,应用进程进入 FIN-WAIT-1(终止等待 1)状态。

(2)接收到这个 FIN 的对端执行被动关闭(passive close),发出确认报文。因为 FIN 的接收意味着接收端应用进程在相应连接上再无额外数据可接收,接收端进入了 CLOSE-WAIT(关闭等待)状态,这时候处于半关闭状态,即主动关闭端已经没有数据要发送了,但是被动关闭端若发送数据,主动关闭端依然要接受。这个状态还要持续一段时间,也就是整个CLOSE-WAIT 状态持续的时间。主动关闭端收到确认报文后进入 FIN-WAIT-2(终止等待 2)状态。

(3)一段时间后,被动关闭的应用进程将调用 close 关闭它的套接字。这导致它的 TCP 也发送一个 FIN,表示它也没数据需要发送了。

(4)接收这个最终 FIN 的原发送端 TCP(即执行主动关闭的那一端)确认这个 FIN 发出一个确认 ACK 报文,并进入了 TIME-WAIT(时间等待)状态。注意此时 TCP 连接还没有释放,必须经过 2∗MSL(最长报文段寿命/最长分节生命期 max segement lifetime,MSL 是任何 IP数据报能够在因特网中存活的最长时间,任何 TCP 实现都必须为 MSL 选择一个值。RFC 1122[Braden 1989]的建议值是 2 分钟,不过源自 Berkelcy 的实现传统上改用 30 秒这个值。这意味着 TIME_WAIT 状态的持续时间在 1 分钟到 4 分钟之间)的时间后,当主动关闭端撤销相应的 TCB 后,才进入 CLOSED 状态。

(5) 被动关闭端只要收到了客户端发出的确认,立即进入 CLOSED 状态。同样,撤销 TCB后,就结束了这次的 TCP 连接。可以看到,被动关闭端结束 TCP 连接的时间要比主动关闭端早一些。

既然每个方向都需要一个 FIN 和一个 ACK,因此通常需要 4 个分节。我们使用限定词“通常”是因为:某些情形下步骤 1 的 FIN 随数据一起发送;另外,步骤 2 和步骤 3 发送的分节都出自执行被动关闭那-一端,有可能被合并成一个分节。

为什么需要 TIME-WAIT 状态?
1、可靠的终止 TCP 连接。
2、保证让迟来的 TCP 报文有足够的时间被识别并丢弃。

根据前面的四次握手的描述,我们知道,客户端收到服务器的连接释放的 FIN 报文后,必须发出确认。如最后这个 ACK 确认报文丢失,那么服务器没有收到这个 ACK 确认报文,就要重发 FIN 连接释放报文,客户端要在某个状态等待这个 FIN 连接释放报文段然后回复确认报文段,这样才能可靠的终止 TCP 连接。

在 Linux 系统上,一个 TCP 端口不能被同时打开多次,当一个 TCP 连接处于 TIME_WAIT状态时,我们无法使用该链接的端口来建立一个新连接。反过来思考,如果不存在 TIME_WAIT状态,则应用程序能过立即建立一个和刚关闭的连接相似的连接(这里的相似,是指他们具有相同的 IP 地址和端口号)。这个新的、和原来相似的连接被称为原来连接的化身。新的化身可能受到属于原来连接携带应用程序数据的 TCP 报文段(迟到的报文段),这显然是不该发生的。这是 TIME_WAIT 状态存在的第二个原因。

三、TCP传输过程

说了TCP的握手和挥手,那么TCP在连接成功后,是如何相互传输数据的呢?
在这里插入图片描述
通过上图做一个简要说明:主机A和主机B通过三次握手建立连接后,主机A开始向主机B发送数据。主机A向主机B发送了字节长度为100的一个包。此数据包的Seq序列号为1200。当主机B收到此数据包后,通过计算,1200+100=1300,那么下一次接收的数据的Seq该是1301了。所以,主机B会反馈给主机A一个ack信息,并携带Seq=1301的信息给主机A。主机A收到ack信息后,从Seq=1301的数据开始,再次发送字节长度为100的数据包给主机B,以此类推,进行TCP数据的传输。(注意:这里的一个数据包长度100字节只是用于举例。具体数据包的长度跟发送TCP的应用程序定义有关,也与网路中各层级的限制有关,这点儿下面会讲到)
在这里插入图片描述
这是单次TCP传输的过程。便于理解。

四、滑动窗口

通过第三章节TCP传输过程的描述发现,主机A发送一个数据包后,需要等主机B反馈ack信息,才会继续往下发送数据包。这就严重影响了TCP的传输效率。因此出现了滑动窗口的概念。TCP协议分为发送滑动窗口和接收滑动窗口两种类型。发送窗口用来发送数据,接收窗口用来接收数据。

发送窗口:

只要处于发送窗口范围中的数据包都可以被发送,不需要等待前面数据包的ack包。
在这里插入图片描述
工作机制:发送窗口存在于操作系统中开辟的一块缓冲区,用来存放当前需要发送的数据。本质是一个循环数组的实现。利用三个指针来维护相关的区域。发送窗口就是一个循环利用的缓冲区,应用层发送数据,就是往缓冲区中写入数据。收到ACK后,就相当于从缓冲区中移除数据,不过并不会真正移除数据,只需要后移对应的指针就可以了。应用层会将数据写入到缓冲区中,当超过缓冲区的最大地址后,就循环利用头部,覆盖头部的数据。

如上图所示,滑动窗口的大小为20。在32到51区间的数据是滑动窗口里的数据。在#2区间,是已经发送的数据,但是没有收到接收方ack信息。在#3区间是发送方还未发送的数据但可以发送的数据。这里的意思就是32到45之间的数据虽然没有收到ack,但是46到51之间的数据在发送滑动窗口,那就不用等待ack信息也能发送。#4区间的数据没在发送窗口,所以不能发送。

接收方收到#2区域的数据,并不会每个数据都返回ack信息,而是批量的找一个最大的值返回ack信息。比如在37的位置,返回了ack,就证明,37之前的数据,都收到了。如果37前的数据有丢包存在,那么接收方是不会发送37的ack信息的,这个下面丢包机制中详细讲。

发送方收到37的ack后,发送滑动窗口就移动到36后面,移动了5位,也就是又有新的5个位置的数据可以发送了,如下图所示:
在这里插入图片描述
依次类推,进行数据的发送。

接收窗口

接收窗口中的字节序列号都是与发送窗口一一对应的。
在这里插入图片描述
工作机制:
接收窗口也是存在于操作系统中开辟的一块缓冲区,用于接收数据。缓冲区本质是一个循环数组的实现。利用两个指针来维护相关的区域。接收窗口存在于一个循环利用的缓冲区,接收数据 就是往缓冲区中写入数据。应用层读取数据后,就相当于从缓冲区中移除数据,不过并不会真正移除数据,只需要后移对应的指针就可以了。当数据写入超过缓冲区的最大地址后,就循环利用头部,覆盖头部的数据。

如上图所示,#1和#2区域是已经发送ack的数据。这部分数据不被包含在接收窗口中。#3是接收窗口,表示未收到数据但是可以接收的数据。#4表示未接收数据,但是发送方也不能发送的数据。

接收窗口由一个RCV_NEXT和接收窗口大小WND来维护。
RCV_NEXT: 下一个希望接收的数据包的序列号。如上图的32。当接收到对应的数据包后,会将RCV_NEXT右移,右移到缓冲区中第一个为空的位置。同时将WND 减去 移动的个数。
WND: 剩余接收窗口的大小。有以下两种变化:
接收窗口 有序的接收到对应的数据包,WND会减去对应的数据包长度。
应用程序读取了数据包,会将WDN增加对应的数据包的长度

接收窗口接收数据包后,即使应用程序没有读取对应的数据包,也会立马返回ack应答包,ack应答包中会携带当前接收窗口的大小WND和接收窗口的数据包缓存信息。
1.当数据包的seq == RCV_NEXT的数据包的时候,将接收窗口的RCV_NEXT右移到缓冲区中第一个为空的位置,WND减去对应的移动字节数;
2.当数据包的seq > RCV_NEXT 并且 seq < RCV_NEXT+ WND的时候,会将其加入到滑动窗口中对应的位置。
3.如果数据包的seq < RCV_NEXT,说明该数据包已经被接收了,但是对应的ack包因为阻塞或者异常没有发送到发送方。这时接收方会利用其发送窗口发送一个ack包。
4.当应用程序将接收到的数据包读取之后,会将WND加上对应的读取数据包的大小。

滑动窗口的大小

发送窗口的大小要取决于接收窗口,如果发送窗口大于接收窗口的大小,会导致接收窗口无法完全接收数据包,导致一些数据包被丢弃,导致发送窗口的超时重传,浪费资源。
tcp报文头部中有一个window字段,代表着接收窗口的接收数据能力,发送窗口会根据window字段来调整发送窗口大小,保证接收窗口正常接收数据包。如下图所示:
在这里插入图片描述

那么,接收窗口的大小由什么决定呢?
“接收窗口”大小取决于应用、系统、硬件的限制(TCP传输速率不能大于应用的数据处理速率),所以,滑动窗口的大小是受多方面因素影响的,不是固定不变的。

举例:
在这里插入图片描述
上图是用wireshark进行的TCP抓包。可以看到:
包175,发送ACK携带WIN = 384,告知客户端,现在只能接收384个字节;
包176,客户端果真只发送了384个字节,Wireshark也比较智能,也宣告TCP Window Full;
包177,服务器回复一个ACK,并通告窗口为0,说明接收方已经收到所有数据,并保存到缓冲区,但是这个时候应用程序并没有接收这些数据,导致缓冲区没有更多的空间,故通告窗口为0, 这也就是所谓的零窗口,零窗口期间,发送方停止发送数据;
客户端察觉到窗口为0,则不再发送数据给接收方;
包178,接收方发送一个窗口通告,告知发送方已经有接收数据的能力了,可以发送数据包了;
包179,收到窗口通告之后,就发送缓冲区内的数据了.

四、TCP丢包与重传机制

先讲没有滑动窗口的情况,如下图:
在这里插入图片描述
当主机A给主机B发送数据包丢失时,主机B就不会发送ack消息。到达超时时间后,主机A没收到ack反馈,就认为消息丢失了,就会自动重新发送丢失的数据包。
那如果是主机B给主机A的ack消息丢失了呢? 这种情况下,主机A也收不到ack消息,也是一直等待。等到超时时间,自动发送丢包数据到主机B。主机B对比Seq发现这个数据已经收到了,就认为是ack消息丢失了,所以主机A才会发送重复数据。所以主机B会再次发送ack消息给主机A,但是重复的数据不会再处理了,因为已经接收过一次了。

那如果考虑进去滑动窗口呢?出现丢包了又是怎么处理的呢?
在这里插入图片描述
用上图做一个说明,滑动窗口假设是14。那么1-14的数据,不用等接收发ack,就可以直接发送出去。假设发送2这个数据的时候,丢失了,因为不需要等待ack消息,所以发送方可以继续发送后面的数据出去。当接收方收到Seq是3的数据时,发现2的数据还没收到,确收到3的数据了,证明2的数据已经丢失了。所以接收方就向发送方发送ack 2的数据。但是在TCP报文中,这个ack的标志和正常ack的标志是不同的,这个称之为TCP dup ack(重复应答):[TCP dup ack XXX#X] 其中,XXX表示第几个包(不是Seq),X表示第几次请求。
在以后的接收数据中,无论接收到的数据Seq是几,都会发送ack 2这个消息给发送方。而不会发送后面的ack给发送方。发送方收到3次ack 2的回复后,就知道这个包丢了,立马会发送这个包给接收方,这个称之为TCP Fast Retransmission(快速重传)。接收方收到丢失的包后,才会继续发送后面数据的ack给发送方。

数据包4丢失也是这个道理。因为数据包丢了之后,ack就卡在丢包那里,一直发送TCP dup ack,所以滑动窗口就不会向后移动。因为TCP协议是有序性的。当发生丢包后,应用程序也不会再读取接收窗口中丢包以后的数据了,只有等到丢包数据重传后,才会按顺序往后读取。

如果丢包发生在滑动窗口的最后呢,也就是丢包以后不再有数据发送了,这种情况就和没有滑动窗口时丢包一样,只有等到超时之后,进行重传了。

由次可见,TCP在保证数据的安全性和顺序性做出的一系列设计,一旦发生了丢包现象,对性能的影响还是很大的,因为需要等到丢失的包重新发送后,应用程序才可以继续读取后面的数据。

超时计算

而超时重传机制中最最重要的就是重传超时(RTO,Retransmission TimeOut)的时间选择,很明显,在工程上和现实中网络环境是十分复杂多变的,有时候可能突然的抽风,有时候可能突然的又很顺畅。在数据发送的过程中,如果用一个固定的值一直作为超时计时器的时长是非常不经济也非常不准确的方法,这样的话,超时的时长就需要根据网络情况动态调整,就需要采样统计一个数据包从发送端发送出去到接收到这个包的回复这段时长来动态设置重传超时值,这个时长就是为 RTT,学名 round-trip time,然后再根据这个 RTT 通过各种算法和公式平滑 RTT 值后,最终确定重传超时值。

五、TCP的粘包与拆包

什么是粘包拆包

在这里插入图片描述
通过上图说明粘包和拆包的概念,发送方向接收方发送两条业务数据D1和D2:
因为tcp协议是面向字节流的,所以每次取的数据都是不固定的。因为应用程序发送数据肯定是发送一条完整的业务数据出去的。但是,在tcp协议传输时,这条完整的业务数据就会变成字节流。tcp并不知道到哪个字节,构成一条完整的业务数据。所以会出现四种情况:
1.正好D1的字节流形成一个包,发给接收方,D2的字节流形成一个包,发给接收方。这是最理想的状态,但是也是几率最小的一中情况。
2.D1和D2的字节流形成一个数据包,一起发送给了接收方。接收方收到一个数据包的字节,但是里面却是两条业务数据,这就是粘包的概念。
3.D1数据形成的字节流不够多,没有达到tcp所能传输的最大长度。就会用D2数据形成的字节流去填补,如果D2数据的字节流只填补了一部分就达到了传输的最大长度,那么就是D1的字节流和D2的一部分字节流形成一个数据包发送到接收方。D2剩余的字节流单独形成一个数据包,发送到接收方。那么对于D2数据而言,就是拆包

形成原因

产生粘包拆包的原因有3种:滑动窗口、MSS/MUT限制、Nagle算法。
滑动窗口:
上面讲滑动窗口提到,只有在滑动窗口中的数据,才会进行发送。滑动窗口是从前到后按照Seq进行滑动的,窗口滑动到哪里,只根据ack的Seq决定,并不会考虑到哪个字节是一条完整数据。所以当滑动窗口很大时,就会出现粘包的现象。当滑动窗口很小时,就会出现拆包的现象。

MSS/MTU分片
MSS: 是Maximum Segement Size缩写,表示TCP报文中data部分的最大长度,是TCP协议在五层网络模型中传输层对一次可以发送的最大数据的限制。

MTU: 最大传输单元是Maxitum Transmission Unit的简写,是OSI五层网络模型中链路层(datalink layer)对一次可以发送的最大数据的限制。

为了更好理解,先介绍一下五层模型中,TCP协议的数据是如何经过各层的:
在这里插入图片描述
对于应用层来说,只关心发送的数据DATA,将数据写入socket在内核中的发送缓冲区SO_SNDBUF即返回,操作系统会将SO_SNDBUF中的数据取出来进行发送。
传输层会在DATA前面加上TCP Header,构成一个完整的TCP报文。
当数据到达网络层(network layer)时,网络层会在TCP报文的基础上再添加一个IP Header,也就是将自己的网络地址加入到报文中。
到数据链路层时,还会加上Datalink Header和CRC。
当到达物理层时,会将SMAC(Source Machine,数据发送方的MAC地址),DMAC(Destination Machine,数据接受方的MAC地址 )和Type域加入。
可以发现数据在发送前,每一层都会在上一层的基础上增加一些内容,下图演示了MSS、MTU在这个过程中的作用。
在这里插入图片描述
MTU是以太网传输数据方面的限制,每个以太网帧都有最小的大小64bytes最大不能超过1518bytes。刨去以太网帧的帧头和帧尾,那么剩下承载上层协议的地方也就是Data域最大就只能有1500Bytes这个值 我们就把它称之为MTU。由于MTU限制了一次最多可以发送1500个字节,而TCP协议在发送DATA时,还会加上额外的TCP Header和Ip Header,因此刨去这两个部分,就是TCP协议一次可以发送的实际应用数据的最大大小,也就是MSS。
MSS长度=MTU长度-IP Header-TCP Header 。
TCP Header的长度是20字节,IPv4中IP Header长度是20字节,IPV6中IP Header长度是40字节,因此:在IPV4中,以太网MSS可以达到1460byte;在IPV6中,以太网MSS可以达到1440byte。

需要注意的是MSS表示的一次可以发送的DATA的最大长度,而不是DATA的真实长度。发送方发送数据时,当SO_SNDBUF中的数据量大于MSS时,操作系统会将数据进行拆分,使得每一部分都小于MSS,这就是拆包,然后每一部分都加上TCP Header,构成多个完整的TCP报文进行发送,当然经过网络层和数据链路层的时候,还会分别加上相应的内容。

需要注意: 默认情况下,与外部通信的网卡的MTU大小是1500个字节。而本地回环地址的MTU大小为65535,这是因为本地测试时数据不需要走网卡,所以不受到1500的限制。那么为什么不走网卡,还有最大值是65535呢?因为在操作系统中有个bug,当一次性接收的数据量大于65535时,操作系统很可能会崩掉。所以,一次性最大接收的数据,不能超过65535。

这里的MSS和滑动窗口的大小一定要区分清楚。滑动窗口表示哪些数据可以发送出去了,哪些数据还不能进行发送呢。而MSS是在可以发送出去的数据里面,进行数据的切分,使数据经过MTU时,最长不超过1460个字节。

Nagle算法
此算法不再研究,想了解的可以百度搜索。

解决方案

好端端的业务数据,经过TCP之后,就被拆分的七零八落。那么被拆散的数据还如何继续使用呢?这就需要我们在业务数据中进行自定义自己的报文头,来表示业务数据的长度有多少。这样即使数据被拆散。只要我们自行解析到自定义的报文头,然后计算出业务数据的长度,然后自己往后获取出这个长度的字节,进行解析,就是一条完整的数据。
Netty框架为我们提供了粘包拆包的功能,我们只需要告诉Netty业务数据的报文头占多少位,报文头哪里是定义数据长度的,Netty就会自动解析出这个长度的业务数据来。

那么有人不禁有疑问了,平时我们使用最多的http协议,底层也是TCP协议呀,为啥我们在使用http时从来没有遇到过粘包拆包问题呢?我们每次收到的都是一个完整鞥的http请求呀。其实,http的本质和上面我讲到的我们自定义报文头是一个道理。http协议定义的报文头里,肯定也定义了一个http请求的长度是多少,或者结尾标识是什么(如/n 换行等标识)。这样,在接收到http请求时,web服务器就根据http协议规定的报文头,去进行粘包拆包数据的处理了。是web服务器底层进行的处理,我们拿到的只是处理好后的http请求。

六、拥堵算法

网络阻塞

TCP的流量控制是通过滑动窗口实现的。本质 是由接收方的接收滑动窗口决定的。是一个点对点的流量控制。而TCP的拥堵控制是一个全局性的控制。
网络上有很多计算机,可能当前的网络状态已经比较拥堵了。此时贸然发送大量数据,会造成大量丢包。若出现拥塞而不进行控制,整个网络的吞吐量将随输入负荷的增大而下降。TCP的拥塞控制便是用于防止网络出现拥塞的。
在这里插入图片描述
也就是说,当发生网络阻塞时,TCP协议会自主的降低发送方发送的数据量,来避免网络阻塞更加严重。可以说TCP协议是牺牲了自己的性能,来维护网络的安全。TCP 是一个比较无私的协议,在这种情况下,会选择减少自己发送的包。当网络上大部分通信协议传输层都采用的是 TCP 协议时,在出现拥塞的情况下,大部分节点都会不约而同地减少自己传输的包,这样网络拥塞情况就会得到极大的缓解,一直处于比较好的网络状态。

如何判断是否发生网络阻塞呢?对于我们程序员而言,可以通过以下几个现象进行判断,本质就是通过观察网络的吞吐量与网络负载间的关系:
1.如果随着网络负载的增加,网络的吞吐量明显小于正常的吞吐量,那么网络就进入例如轻度拥塞的状况。
2.如果网络得吞吐量随着网络负载的增大反而下降,那么网络就可能进入拥塞状态。
3.如果网络的负载继续增大,而网络的吞吐量下降到零,网络就可能进入了死锁状态。

那么TCP又是如何判断是否网络阻塞了呢?
TCP的判断网络是否发生拥塞有两种方法:
1.是否发生超时:如果过了超时重传时间发送方仍未收到确认报文,那么TCP就会认为当前网络已经发生拥塞。采用慢开始算法进行处理。
2.收到连续3个重复的ACK报文
如果发送方收到了连续3个重复的ACK报文,那么TCP也会认为当前网络发生了拥塞。采用快恢复算法进行处理。

TCP控制网络阻塞的方法

TCP的拥塞控制方法一共有4种:慢开始、拥塞避免、快重传、快恢复。

拥塞窗口: TCP连接中发送方会维护一个叫拥塞窗口(cwnd)的状态变量。拥塞窗口的大小取决于网络的拥塞程度,并动态地变化着,发送方会使自己的发送窗口等于拥塞窗口。

慢开始

慢开始算法的思路是先发送少量数据探探路,然后慢慢地增大拥塞窗口的大小。
假如拥塞窗口cwnd初始值为1,之后每收到1个ACK,拥塞窗口+1。
慢启动只是初始时发送速度慢,但实际上慢启动的数据发送速度增长是非常地快的。在慢启动算法中,每经过一个传播轮次,拥塞窗口就会加倍。
在这里插入图片描述
为了不让拥塞窗口增长过快,TCP引入了慢启动门限(ssthresh),当拥塞窗口大小超过这个门限时,就不再按慢开始的算法增大拥塞窗口的大小了,而是改用拥塞避免算法。

拥堵避免

每经过一个往返时间RTT,就把发送方的拥塞窗口+1。即拥塞窗口按线性规律缓慢增大,这要比慢开始算法的拥塞窗口增长速率缓慢很多。
在这里插入图片描述

快重传

快重传算法可以让发送方尽早知道发生了个别数据段的丢失。
按照快重传算法,假如接收方收到了M1数据段和M2数据段,然后没有收到M3数据段就收到了M4数据段,那么接收方会重复发送M2确认报文,提醒发送方M3数据段已丢失。
在快重传算法中,发送方一旦收到连续3个重复的确认报文,就会立即重新发送丢失数据段。

快恢复

当发送方连续收到3个重复的确认报文时,发送方就知道当前发生了个别数据段的丢失,但网络很可能还没有发生拥塞,若采用慢开始算法速率下降过于严重,因此采用快恢复算法以缓解当前网络状态。
快恢复算法具体流程:
1.慢开始门限值改为当前拥塞窗口/2。
2.新拥塞窗口=慢开始门限
3.开始执行拥塞避免算法,使拥塞窗口缓慢地线性增大。

TCP拥塞控制总体流程图

在这里插入图片描述

TCP拥塞控制综合例子

在这里插入图片描述
一开始时,TCP采用慢开始算法逐渐增大拥塞窗口的大小。
到第1个点时,拥塞窗口达到慢开始门限值,开始采用拥塞避免算法继续增大拥塞窗口的大小。
到第2个点时,发生了超时,此时TCP认为网络已经发生了拥塞,开始采用慢开始算法重新缓慢增大拥塞窗口。
到第3个点时,拥塞窗口达到慢开始门限值,开始采用拥塞避免算法继续增大拥塞窗口的大小。
到第4个点时,发送方收到了连续3个重复的ACK确认报文,TCP采用快恢复算法进行处理。

总结

由上面的拥堵算法可知,当发生丢包时,不仅仅是等待丢包的数据重传以后再往后读取这一点影响TCP的性能。还包括发生丢包后TCP为防止网络阻塞而采取的一系列行为,来减少发送量,也会对TCP的发送效率产生影响。可以说是双重影响。
所以在网络环境不好的场景下使用TCP,就要接受它对性能产生的影响。

七、KeepAlive心跳保活机制

简单说来,keepalie并不是TCP协议本身的一个机制,而是操作系统层面实现的一种保活机制。当TCP连接一定时间不在链路层传输数据时,操作系统就会发送一个探针去试探对方是否还存货。如果不存活,则关闭这个连接,避免一直存在无效连接,影响性能。由于keepalive是操作系统层面实现的机制,所以对应用程序而言,并不灵活。所以一般都会在应用程序中自己定义心跳机制,而不采用keepalive机制。
详情可参考:TCP的KeepAlive机制

猜你喜欢

转载自blog.csdn.net/qq1309664161/article/details/125926435