UDP/TCP ②-三次握手 || 四次挥手 || 确认应答 || 超时重传

这里是Themberfue

在讲完了UDP协议后,我们进入更为重要也是更为复杂的TCP协议 ,探究其是如何让数据可靠传输的。


TCP协议报文格式

  • 关于TCP协议的一些特点我也不过多赘述了,详情请见:TCP
  • TCP:有连接、可靠传输、面向字节流、全双工
  • 我们依然从协议报文格式开始
  • 第一眼看着这个报文格式,不禁感叹这些都是什么玩意儿?不急,我们一个一个看。首先,源端口和目的端口与UDP报文格式的含义一样,因为端口本身就是传输层所管理的,不管是哪个传输层协议的报文格式,都必须有源端口和目的端口。
  • 我们看到 首部长度(4位) ,由于这个字段以及选项字段 的存在,所以TCP的报头长度是可变的,首部长度这里最高4位,那么可表示的范围就是 0-15,这里的单位是4字节 ,所以TCP报头的最大长度为60字节(15 * 4) ,其中,20字节为固定部分,那么剩下的部分就放在选项字段。选项字段是否存在,取决于报头的大小是否超过20。例如:报头的大小为32字节,20字节为固定长度,那么12字节就在选项字段中。
  • 保留(6位),由于UDP协议的长度最长为64kb,如果要传输更大的数据就需要拆包组包等操作,十分麻烦。为了避免出现UDP类似的问题,该字段的设计初衷是用于未来扩展,以便在需要时新增功能而无需重新定义协议,一般情况下都填充为0,先占个位置~
  • 16为校验和,与UDP协议一样,都是用来校验传输过程中数据是否发生错误的。

  • 剩下几个字段我们放到后面讲,我们先讲解一下TCP协议的一些核心机制。在保留字段的右边,有六个字母简写 ,这六个非常重要,是TCP协议最核心的六个标志位

TCP的核心机制

  • ✨我们知道,TCP有一个特点就是其可靠性。因为数据在传输过程中会经历各种路由器设备的转发,所以,数据在传输过程不可能百分之百到达对端。保证数据的可靠传输一直都是开发人员的研究方向。

确认应答

  • 确认应答是网络通信中保证数据传输可靠的重要机制之一,保证可靠传输的一个关键前提就是发送方知道我的数据发送到了接收方那里。
  • **确认应答(Acknowledgment, ACK)**是指数据接收方在成功接收到数据后,向发送方发送的反馈信号,表示该数据已正确接收。
  • 发送方在收到 ACK 之前,会保留已发送的数据副本,以便在超时或丢包情况下重新发送。
  • 确认应答就是六个标志位的ACK标志位,通常将ACK位置置为1表示其为一个确认报文。

  • 但是,简单的发送确认报文并不能解决可靠性,因为发送方一次性向接收方发送的数据一般不止一条,可能存在多条,而数据在传输过程中通常要经过许多节点,这就导致了,先发送的数据可能会比后发送的数据后到,也就是后发先至。比如你向你的好友先发送 "在吗?" 后发送 "你好",但是对方可能先收到 "你好" 后收到 "在吗?",这就感觉很奇怪。
  • 此时,TCP协议报文格式中的序号字段确认序号字段 就发挥作用了,对每个数据都进行编号,但是TCP协议是面向字节流的,所以是按照 "字节" 进行编号的,一个字节一个编号,为了编号的有序性,序号通常是从1开始逐渐递增的。
  • 由上图可知,将整个TCP数据报以1000字节为分,假设总共有5000字节,那么就会发五次,就可能有五个确认应答报文。而确认序号就是将接受的载荷部分的最后一个字节序号+1,填写到确认序号字段中。比如,发送方发送 1-1000 的载荷部分,接收方则发送1001的确认应答报文。
  • 此时的确认报文1001就有两个作用:说明1001字节之前的数据都已经收到了发送方下次从1001字节开始发送

工作流程:

  • **数据发送:**发送方将数据包分段后,附加序列号,并发送给接收方。
  • **接收与确认:**接收方接收数据后,检查数据包的完整性。如果正确,则发送一个包含确认号的 ACK;确认号表示下一个期望接收的数据的序列号。
  • **发送方处理:**发送方收到 ACK 后,将对应的数据包标记为已发送并清除缓存;若超时未收到 ACK,发送方会重传数据包。

  • 接收方收到完整数据后会先根据序号将这些分段的数据进行排序,排完序后才开始真正地开始读取数据。接收方这边通常会有一个接收方缓冲区用于暂时存储接收到的全部数据,等到数据全部接收完后,在缓冲区里直接对这些数据进行排序,随后应用程序将这些排序好的数据再在进行读取**(应用层这里在数据没有完整传输过来以及排序之前都会阻塞在read()这里)**。当然,这些过程不需要我们实现,这些都是socket api底层实现好的,应用层这边直接读取传输来的数据即可。

超时重传

  • 数据包在网络传输中是需要经过许多节点的,但是数据包又不止你一个人在发送,所以,在数据包经过某个节点路由器或者交换机时,若该路由器或者交换机的转发量达到了上限,那么就得丢弃一些数据包,这便是我们常说的的丢包。
  • ✨解决丢包问题是保证可靠传输的一个重要话题。超时重传便应运而生,超时重传是TCP可靠传输的核心机制之一,用于解决数据在传输过程中可能出现的丢失问题。
  • 丢包是数据包在传输过程不可避免地客观现象,我们只能尽可能地减少丢包出现的概率,也就是丢包率重传就是可以大幅度降低丢包率的一个重要手段;假设某一个时刻的丢包率为10%,假如此时真的丢包了,那么我再发送一个数据包,根据概率论,我的丢包率可以下降到1%(10% * 10%);所以,随着重传次数的不断增加,丢包率可以不断地减小。
  • 丢包出现的情况会有两种,一种是发送方向接收方发送的数据包丢了另一种是接收方向发送方发送的确认报文ACK丢了;针对这两种情况,发送方是无法区分的,唯一的做法就是重传数据包。

超时重传机制:

  • 发送数据包: TCP协议在发送数据包时,会为每个数据包设置一个**(Timeout)超时时间**。发送方将数据包发出后,开始等待接收方的确认应答(ACK)。
  • **超时判断:**如果在超时时间内,发送方没有收到对应的ACK,则认为该数据包可能在传输中丢失。此时,发送方会重新发送该数据包,确保数据能够送达。
  • 重复确认的处理: 如果接收方已经收到了数据包,但ACK没有及时传回,发送方可能会重传数据。接收方会根据序列号检查是否是重复数据(也就是接收方这边会根据序列号做一个去重处理,避免收到了两个相同的数据包),若是,则直接丢弃重复包。

关键点:

  • 超时时间(Timeout): 超时时间是TCP可靠性的重要参数,设置得太短会导致过多的重传;太长则会增加等待时间,影响效率。所以作出动态调整是很重要的,TCP会根据网络情况动态调整超时时间。常见的计算方式是使用RTT(往返时间)RTT的方差来估算网络的延迟。在BSD的Unix以及Windows系统中,超时都是0.5秒为单位进行控制,因此重发超时都是0.5秒的整数倍。不过,由于最初的数据包还不知道往返时间,所以其重发时间一般设置为6秒左右。数据被重发之后若还是收不到应答,则进行再次发送。此时等待确认的应答时间将会以2倍、4倍的指数函数延长。
  • **重传次数限制:**TCP通常限制重传次数。如果多次重传仍未收到ACK,发送方会放弃连接,并向应用层报告错误,表示对方网络可能发生故障。例如,TCP可能重传 5 次,如果仍无回应则断开连接。
  • **快速重传:**如果接收方连续收到 3 次对同一数据包的重复确认(Dup ACK),发送方会立即重传该数据包,而不等待超时。这是一种优化机制,能快速修复轻微的丢包问题。

工作流程:

  • **发送方:**发出一个数据包,并将数据包的序列号标记为等待确认。设置超时时间,并开始计时。
  • **接收方:**收到数据包后,发送ACK确认包。如果ACK丢失,发送方无法知道接收方已经收到数据。
  • **发送方:**若超时时间到达仍未收到ACK,重发对应的数据包。继续等待ACK,直到接收到或达到重传次数限制。

连接管理

  • ✨TCP的特点之一就是有连接 ,就是通信双方在通信时是连接在一起的。当然,这不是物理上的连接,而是逻辑上的连接,双方通过交换一些信息从而实现逻辑上的连接 。连接管理通常分为两个:建立连接断开连接

三次握手

  • 握手 这个流程我们在之前HTTPS协议的学习中就已经了解过了,获取证书验证证书加密对称密钥传输对称密钥确认对称密钥收到 ,这便是SSL协议的握手流程。
  • TCP在为了保证可靠传输,在双方通信前也需要双方进行 "握手"。还记得前面提到TCP协议首部报文中的那六个重要的字段之一------SYN 吗?不记得没关系,划上去再看看。这个SYN就是synchronize的缩写------同步。双方在通信之前,通过SYN报文(SYN这个字段标为1)表示是在建立连接。
  • ✨这里以客户端与服务器建立连接的过程为例:首先,客户端先发送一个SYN报文,然后服务器发送ACK报文表示收到了,服务器发送完毕后ACK又发送一个SYN报文,客户端收到服务器发送的SYN报文后,发送一个ACK报文表示收到了。至此连接建立完毕。
  • ❓到这里就有人疑惑了,这不是四次握手吗?怎么就三次了?仔细观察我们可以发现,服务器发送的SYN报文和ACK报文可以打包放一起发送给客户端,所以就变成三次,也就是三次握手
  • 实际上,三次握手时,客户端和服务器都会在某个阶段进入对应的状态,有点类似于多线程的WAITING、RUNNABLE等状态。以下是三次握手的详细过程:

三次握手的过程:

第一次握手(SYN)------客户端发起连接请求:

  • 客户端向服务器发送一个带有**SYN(synchronize,同步标志位)**标志的TCP报文段,表示请求建立连接。
  • 该报文中还会携带一个初始序列号(Sequence Number, 简称Seq),用于标识数据传输的起始位置。
  • 客户端进入SYN_SENT状态。

第二次握手(SYN + ACK)------服务器响应客户端:

  • 服务器收到客户端的SYN报文后,知道客户端希望建立连接。
  • 服务器会向客户端发送一个带有SYN 和**ACK(acknowledgment,确认标志位)**标志的报文:
    • SYN表示服务器也希望建立连接。
    • ACK用来确认客户端的SYN报文(ACK的值等于客户端的Seq + 1)。
  • 服务器进入SYN_RCVD状态。

第三次握手(ACK)------客户端确认服务器:

  • 客户端收到服务器的SYN + ACK报文后,知道服务器同意建立连接。
  • 客户端再次发送一个ACK报文,确认服务器的SYN(ACK的值等于服务器的Seq + 1)。
  • 该报文可以携带数据,也可以不携带。
  • 客户端进入ESTABLISHED 状态,服务器在收到ACK后也进入ESTABLISHED状态。
  • 双方连接建立完成,可以开始通信。

为什么需要三次握手?

  • 在正在开始通信前,三次握手相当于一个 "投石问路" 的过程,用于确保双方的网络链路都通畅,以免在真正通信前出现不必要的错误。
  • **验证双方的发送能力和接受能力都正常:**好比你和朋友一起开麦打游戏,打游戏前肯定要验证双方的麦克风和耳机是否正常。你说一句摩西,对方若听到了,表示你的麦克风和对方的耳机没有问题;对方说一句摩西摩西,若你也听到了,表示你的耳机和对方的麦克风没有问题,此时你确认了你的耳机和麦克风以及对方的耳机和麦克风都没有问题;你再说一句摩西摩西摩西,对方若收到了,表示对方确认了你的耳机和麦克风以及对方的耳机和麦克风都没有问题;然后你们就可以愉快地玩耍了。
  • 第一次握手: 确认客户端发送能力、服务器接收能力正常。第二次握手: 确认服务器发送能力、客户端接收能力正常。**第三次握手:**确认客户端的发送能力和服务器的接收能力仍然正常。
  • **协商一些关键数据:**比如确定好双方通信前的最初始的序号是什么,可能会认为初始序号默认是0,其实不然,一般不是从0开始的,比如从1000、8000开始都有可能。
  • **防止历史连接导致的误操作:**如果客户端发出的第一次握手(SYN)因为网络延迟滞留,服务器收到后建立了连接,可能会导致错误通信。三次握手可以确保双方确认当前的连接是新建的,而非历史连接。

四次挥手

  • 建立连接后,不可能说一直就连接了,服务器肯定有处理上限,对于断连的客户端肯定要做断开连接处理,不然只会占用不必要的资源。于是,四次挥手的作用就体现出来了。
  • ✨在某一方想要断开连接时,通常不是主动发直接断开就了事,这是需要双方确认的,与 "握手" 类似,这个过程称为 "挥手" 。挥手通过TCP首部协议报文的六个字段之一------FIN确定,FIN就是finish的意思。主动方通过发送FIN报文告知被动方我要断开连接了。
  • 由于客户端和服务器都可能主动发送FIN报文,不过大多数还是客户端主动FIN报文,所以我们这里依然以客户端作为主动方为例:首先客户端发送FIN报文表示自己没什么数据发送了,想要断开连接了;服务器收到FIN报文后,发送ACK报文表示服务器这边收到了;随后再发送一个FIN报文表示我这里也没什么数据发送了,可以断开连接了;客户端收到服务器发送的FIN报文后,发送ACK报文表示客户端这边收到了。
  • 与三次握手类似,在每个报文的发送阶段,双方都会进入某些状态。以下是四次挥手的详细过程:

四次挥手的过程

第一次挥手(FIN)------主动关闭方发送断开请求:

  • 主动关闭方(通常是客户端)发送一个**FIN(finish)**标志的报文,表示自己没有数据要发送了,希望断开连接。
  • 此时,主动关闭方进入FIN_WAIT_1状态。

第二次挥手(ACK)------被动关闭方确认断开请求:

  • 被动关闭方(通常是服务器)收到FIN报文后,发送一个**ACK(acknowledgment)**报文进行确认,表示"我知道你要关闭连接了"。
  • 此时,被动关闭方进入CLOSE_WAIT 状态,而主动关闭方进入FIN_WAIT_2状态。

第三次挥手(FIN)------被动关闭方发送断开请求:

  • 被动关闭方在确认了主动关闭方的请求后,如果自己也没有数据要发送了,就会发送一个FIN报文,表示可以关闭连接。
  • 被动关闭方进入LAST_ACK状态。

第四次挥手(ACK)------主动关闭方确认断开请求:

  • 主动关闭方收到被动关闭方的FIN报文后,发送一个ACK报文进行确认。
  • 主动关闭方进入TIME_WAIT状态,并在等待一段时间(通常是2倍的最大报文寿命,2MSL)后才真正关闭连接,以确保被动关闭方收到ACK。
  • 被动关闭方在收到ACK后立即进入CLOSED状态,完成连接的关闭。

为什么需要四次挥手?

全双工的连接特性:

  • TCP是全双工通信,每个方向的连接都需要单独关闭。
  • 第一次挥手和第二次挥手关闭一个方向的数据传输,第三次和第四次关闭另一个方向的数据传输。

确保数据的完整性:

  • 被动关闭方可能还有未发送完的数据,四次挥手允许被动关闭方完成最后的数据传输。

❓为什么四次挥手不能像三次握手那样将服务器的ACK和SYN一起发送,也就是ACK和FIN一起发送。

  • 服务器在收到FIN报文后,是内核控制服务器发送ACK报文表示确认收到FIN报文的,但是服务器发送FIN报文就不是内核控制的,也就是说,三次握手的过程都是内核控制的,但是四次挥手的过程中,并不全是内核控制的。

  • 服务器发送FIN报文的时机取决于程序员是怎么编写代码的,还记得网络编程那一节讲的代码吗?当服务器调用 socket.close() 后,服务器才发送FIN报文,在调用close之前,之前可能还会有一段逻辑。所以ACK报文和FIN报文的时机不一样,当然,也可能这两个报文会一起发送。

  • 如果是内核控制服务器发送FIN报文可能就会如下情况:主动方没有等待被动方完成传输:主动关闭方发送FIN: 主动关闭方告诉被动关闭方"我已经不发送数据了"。被动关闭方发送FIN+ACK: 被动关闭方回复ACK表示"我知道你不发送数据了",并直接发送FIN,表示"我也不发送数据了"。主动关闭方发送ACK: 主动关闭方回复ACK,表示"我知道你也不发送数据了",然后直接关闭连接。💎那么好,此时就出现问题了:被动关闭方可能还有未发送完的数据,在这种情况下,被动关闭方还未完成剩余数据的发送,连接就被关闭了,导致数据丢失。

  • TCP是一种全双工协议,这意味着数据可以同时在两个方向上传输。断开连接时,必须分别关闭两个方向的数据传输:主动关闭方需要通知被动关闭方"我这边没有数据要发送了"。被动关闭方也需要通知主动关闭方"我这边也没有数据要发送了"。因此,每个方向的连接关闭需要独立的信号,合计需要两个关闭请求(FIN)和两个确认(ACK)

  • 与三次握手不同,三次握手双方只需要确认通信通道已经建立,不涉及数据传输的关闭 。而四次挥手涉及双向数据的独立关闭,必须更加谨慎。通过四次挥手确保了双方的发送方向都得到了独立关闭。被动关闭方有机会完成最后的数据传输,避免数据丢失。

TIME_WAIT状态的作用

  • 与三次握手一样,四次挥手同样可能会出现丢包的情况。如果是客户端发送的FIN报文丢了,根据超时重传机制,再发送一遍即可;如果是服务器的ACK报文丢了,同上;如果是服务器的FIN报文丢了,同上;但如果是客户端的ACK报文丢了呢?
  • 在客户端接收到服务器发送的FIN报文后,表示服务器那边没什么数据发送,可以断开了,此时客户端在发送完ACK报文后直接断开的话,如果ACK报文丢包了,但是客户端又断开了,那我这个ACK报文谁来重传?所以,TIME_WAIT的作用出来了。
  • ✨客户端进入TIME_WAIT 状态表示需要等一会再完全断开与服务器的连接,这个时间通常是2MSL(Maximum Segment Lifetime,网络上任何两个节点传输过程需要消耗的最大时间),通常 MSL 被设置为 60s,所以通常会等待 2min 后再完全断开连接。
  • 有了TIME_WAIT,可以确保ACK的可靠性: 在第四次挥手后,主动关闭方需要等待2MSL时间,以防最后的ACK丢失。防止旧连接影响新连接: 等待2MSL可以确保旧连接中的残留报文不影响新连接。
  • 当然,如果服务器迟迟收不到ACK报文的话,服务器也不会傻傻地等着,而是会主动断开连接,服务器推测可能客户端出现异常。

状态转换图

主动关闭方的状态:

  • ESTABLISHED → FIN_WAIT_1 → FIN_WAIT_2 → TIME_WAIT → CLOSED

被动关闭方的状态:

  • ESTABLISHED → CLOSE_WAIT → LAST_ACK → CLOSED


  • TCP协议的机制还有很多哦,下节我们将继续深入TCP协议的机制~~~
  • 毕竟不知后事如何,且听下回分解
  • ❤️❤️❤️❤️❤️❤️❤️
相关推荐
Swift社区1 小时前
【ELK 实战篇】日志聚合与可视化全流程详解:从部署到洞察数据的高效指南
网络
浅念同学2 小时前
网络编程-网络原理HTTP初识
java·网络·网络协议·http
A charmer2 小时前
探秘 Linux 进程状态:解锁系统运行的密码
linux·服务器·网络
Mr.kanglong3 小时前
【Linux】网络层
linux·服务器·网络
李白你好3 小时前
家用路由器:WAN口和LAN口区别?
网络·智能路由器
心平气和️3 小时前
第八章 动态主机配置协议的配置和应用
服务器·网络·计算机网络
车到山前必有路5293 小时前
计算机网络(终章)——应用层
网络·计算机网络
hao_wujing3 小时前
Linux 高级路由与流量控制-用 tc qdisc 管理 Linux 网络带宽
linux·网络·智能路由器
longze_73 小时前
计算机基础问答-面试总结
java·python·计算机网络·spring·面试·c#
qq_333617245 小时前
OSPF的LSA的学习研究
网络·学习