『 Linux 』网络传输层 - TCP(二)

文章目录

TCP六个标志位

在TCP协议报文的报头中存在一个用于标志TCP报文类型的标志位(不考虑保留标志位),这些标志位以比特位选项的方式存在,即对应标志位为0则表示为假,对应标志位为1则为真;

  • SYN

    用于建立连接,连接发起方发送SYN请求,表示希望发起一个连接,同步序列号;

  • ACK

    标识确认,该位被设置时,确认号字段有效,用于确认接收到的数据;

  • FIN

    用于关闭连接,一方发送FIN表示它已经完成数据发送并希望关闭连接;

  • RST

    用于重置连接,表示当前连接有问题,需要强制断开,通常用于错误或异常情况;

    RST报文被接收后需要重新进行三次握手来建立一个新的连接;

  • PSH

    推送数据,提示接收方应用程序应立即读取数据而不是在缓存中等待更多数据;

    本质上TCP也是一种生产者消费者模型的体现,其中发送方的用户层与接收方上层的用户层各担任了生产者和消费者的身份,其中发送方的用户层作为生产者,接收方的用户层作为消费者,当接收缓冲区满了后发送方再向接收方发出的数据流将被丢弃,在对于流量控制而言本质上就是在网络中的一种同步应用;

    PSH报文可以通过告诉接收方应尽快将这些数据流传给应用层避免数据长时间积累在接收缓冲区中导致接收方的接收缓冲区被写满;

  • URG

    表示紧急数据,紧急指针字段有效,用于标示紧急数据的位置,提示接收方优先处理;

    在操作系统中存在一个专门用于处理紧急数据的空间,当紧急数据被发送过后且URG标志位被设置,表示紧急指针有效,对应的紧急数据将会优先被处理;

    对于TCP而言,其数据流是按序到达的,所以为了不导致大面积的数据乱序,紧急指针所指向的紧急数据很小,通常只有一个字节;

    大部分情况下紧急数据用来处理或者检查对端接收缓冲区中数据长时间不被处理导致的发送方所发送的数据无法被接收方接收导致发送方未接收到应答等情况;

    可以将URG标志位理解为一个优先通道;

    可以通过系统调用接口send()设置对应的MSG_OOB选项来设置紧急数据,对应的也可通过系统调用接口recv()同样设置MSG_OOB选项来读取对应的紧急数据;

六个标志位中每一个标志位都标示着该TCP报文的类型,即一个TCP报文可能是多种类型,如在进行三次握手时,被握手方需要返回一个SYN+ACK的报文,其中该报文中的SYN表示也需要向对方建立起连接,而ACK表示的是对对方发起的SYN报文进行应答;

同时为了保证安全性,操作系统通常不会直接让用户直接修改对应的标志位,但对应的操作系统会为用户提供一些系统调用接口来间接修改报文的属性类型;

如当用户调用connect()系统调用接口向一个服务端发起连接时,本质上是让系统构建一个SYN类型的TCP报文并发送给对端;

对应的当一个已经连接的服务端或是客户端调用close()时本质上也是让系统在底层构建一个FIN类型的报文,并发送给对方表示希望断开该次连接;


TCP的连接

TCP是一个保证可靠传输的网络协议,其为了保证数据传输的可靠性,通常要与服务端建立起连接,但尽管TCP协议制定了若干的约定来保证数据在传输中的可靠性但也避免不了一些不可抗拒的因素;

本质上TCP协议保证可靠传输的前提是在与对端建立连接的前提下,保证未发生不可抗力异常时数据的传输(不可抗力异常通常为断电或设备故障,物理连接中断或是网络设备配置错误,任何一端应用层的崩溃等情况);

通常情况下服务器都是一对多的,即一台服务器将对应存在若干个客户端,当一台客户端向服务器发起连接请求时服务器中可能也在处理来自其他客户端的连接请求或是维护与其他客户端的连接,维护的方式同样的采用的是 "先描述后组织" ,无论是服务端还是客户端为保证连接的可靠性系统层面都要对已经建立好的连接创建一个对应的结构体,通过实例化结构体对象再以特定的数据结构将多个该结构体对象组织在一起,如链表;

对应的任何一端对连接的维护都是具有成本的,即需要花费时间和空间;

  • TCP允许连接建立失败

TCP所保证的可靠性是建立在已经建立连接的基础上,但实际上在建立连接时可能会因为多种因素导致连接建立失败,TCP能保证数据传输可靠但是无法保证在建立连接时能完全避免连接建立失败的情况;


三次握手

TCP协议建立连接时需要先进行三次握手;

所谓三次握手即为,请求建立连接方向对端发送SYN报文表示希望与该端建立连接,对端进行应答并告知也希望与该端建立连接返回一个SYN+ACK报文,请求方向对端返回ACK报文表示接收到对端的请求;

以该图为例:

  • Client向Server发送SYN报文
  • Server向Client返回SYN+ACK报文
  • Client向Server发送ACK报文

其中图中的每次报文发送的斜线表示每次报文的发送与接收具有时间差;

以客户端的视角而言,当客户端发送第三次握手的ACK报文后则表示该次握手已经建立成功,将会创建一个对应的结构体用于管理该连接;

而对于服务端而言,当其接收到第三次握手的ACK报文才表示该次连接真正被建立成功,才会生成对应的结构体对象来管理该连接,当服务端接收到三次握手中的最后一个ACK报文后不会再对该ACK报文进行应答,这也是 "应答不会再被进行应答" ;

即通常情况下对于客户端而言,当其认为连接已经建立成功后将会正常与服务端进行正常通信,若是服务端未接收到最后一个ACK报文就接收到了客户端的正常通行报文(数据流),服务端将会认为该连接并未建立成功,并不会接收该报文,而是向客户端发回一个RST报文告诉该连接并未建立成功,要求客户端重新发出最后一个ACK报文以要求建立连接;

这里也涉及到确认序号的问题,当发送端发出一个TCP报文给接收端,对应的接收端将对该报文向对端返回一个ACK报文表示应答,其中返回的ACK报文中的确认序号通常为 所接受报文序号+有效载荷大小 ,通常SYN标志位和FIN标志位将会占用一个序列号,所以对应的当服务器端接收到一个SYN报文时,假设该报文序列号大小为x,则服务端需要返回一个序列号大小为x+1SYN+ACK报文,示例为如下:

  • 第一次握手

    客户端发送一个SYN报文,假设序列号为x;

    bash 复制代码
    Client -> Server : SYN, SEQ = x
  • 第二次握手

    服务器收到SYN报文,返回SYN+ACK报文,假设服务器的初始序列号为y;

    bash 复制代码
    Server -> Client : SYN+ACK, SEQ = y, ACK = x+1
  • 第三次握手

    客户端收到SYN+ACK报文,返回ACK报文;

    bash 复制代码
    Client -> Server: ACK, SEQ = x+1, ACK = y+1

当应用层想要该端与一个服务器端建立起连接,需要调用connect()系统调用接口使底层构建一个SYN报文,但实际上connect()只负责要求系统构建报文,其函数本身并不参与握手(只发起握手,不关心握手细节);

当一个执行流调用connect()系统调用接口使系统构建SYN报文后将进行阻塞,直到握手完成,当以客户端的视角第三次握手被发出后对应的连接将视为成功,对应的系统将为该连接维护对应的结构体,并且将该连接以套接字描述符的方式返回给connect()函数表示连接建立完成;

对应的当一个服务端需要获取一个来自客户端的连接时同样要调用accept()系统调用接口,同样的该接口不参与握手细节,当一个执行流调用该系统调用接口时对应的该执行流也会进入阻塞状态,直到三次握手完成并且连接被建立成功,对应的该被维护起来的连接将以套接字描述符的方式交给该函数作为返回值;


四次挥手

四次挥手是正常情况下连接断开的过程,通常为申请断开连接方调用系统调用接口close()让系统构建一个FIN报文并发送给对方;

四次挥手的过程为:

  • 第一次挥手

    发送方向接收方发送一个FIN报文表示希望与对端断开连接;

  • 第二次挥手

    接收方向发送方返回一个ACK报文表示接收到这个断开连接请求;

  • 第三次挥手

    接收方向发送方发送一个FIN报文表示希望与对端断开连接;

  • 第四次挥手

    发送方向接收方返回一个ACK报文表示也收到了这个断开连接请求,至此连接彻底断开;


为什么是三次握手和四次挥手

  • 为什么是三次握手

    三次握手是TCP为了保证双方通信时具有可靠性所定制的一种策略;

    而三次握手可以确保客户端和服务端至少都向对方发送了一次消息,从而确保连接建立的更为可靠;

    以朴素的角度来看三次握手实际上也可以被解析成四次握手;

    但为了提升连接创建的效率,TCP采用捎带应答的策略,在第二次握手中将ACK报文与SYN报文整合成了一个报文,即为SYN+ACK报文;

    在三次握手中能够确保双端都对对方进行一次消息的发送以及消息的接收来确保连接的稳定,因此三次握手中任何一次握手都不能少;

    假设三次握手的数量减少,连接的可靠性将大大降低,如以下几种情况:

    • 单次握手

      单次握手时通常为当客户端向服务端发起一个连接请求后服务端无法保证连接的可靠性就直接建立起对应的连接并进行维护,既不能保证客户端向服务端方向的数据传输是否可靠(服务端不进行应答即建立连接,不能保证服务端是否接收到对应的连接请求),也无法保证服务端向客户端方向数据传输是否可靠;

      同时维护连接通常是具有成本的,单次握手可能会导致同一个客户端向一个服务端大量发送连接请求,只要服务端一接收就建立起对应的连接并且对这些连接进行维护,将会大大占用服务端资源;

      一次握手服务端无法确认客户端的状态,无法保证连接的有效性和安全性;

    • 二次握手

      二次握手对于客户端而言,仍无法保证服务器已经准备好通信,二次握手通常意味着客户端发送SYN报文,服务端返回SYN+ACK报文,在未接收到客户端的ACK前服务器就已经认为连接已经成立,但实际上客户端收到SYN+ACK后可能由于种种原因无法进行数据传输,如网络中断等;

      而服务端将会为一个可能无效的连接预留资源,同样会面临无法确认客户端是否通信就绪的问题;

    三次握手将建立一个稳定可靠且能够确保全双工的通信连接,即任意一方都可以有效的进行发送数据和接收数据;

    通常情况下,客户端与服务端的关系通常是一对多的,即一个服务端对应着若干个客户端,而一次握手和二次握手两种方案最终都把负载交到了服务端身上,服务端随时都维护着一些无用资源,即并不进行通信了连接进行维护;

    三次握手可以将负担均匀的双方分配而不是只有单单服务端具有负担;

    同时一次握手和二次握手的方案将大大提升服务端受到SYN Flood(通过发送大量伪造的SYN报文,迫使服务器分配大量资源维护无效连接从而使其无法处理正常请求)等DoS攻击;

    同时三次握手不仅确保了连接的可靠性,三次握手时双方所发的报文也是一种协商报文,协商的包括双方的起始序号,确认序号位置,窗口大小等等;

    同时当任意一端发送RST报文后,表明当前连接被强行中断,任何已建立的连接状态信息都会被销毁,如果需要重新建立通信必须进行新的三次握手,以建立一个全新的TCP连接;

  • 为什么是四次挥手

    三次握手是建立通信连接的过程,而四次挥手是断开连接的过程,断开连接与建立连接是不同的,在连接建立过后数据是双向发送的,客户端和服务端任意一方都既扮演着发送方,也扮演者接收方的角色;

    当一端将数据发送完后准备关闭连接其无法确保对端是否还有数据未发完,所以断开连接的操作通常为四次挥手,即双方在确保没有数据要发送给对方时都要向对方发送连接关闭的请求;

    假设客户端主动向服务端发起断开连接的请求(客户端断开连接请求前的所有数据段已经发送完成),即客户端调用系统调用接close()时系统将生成一个FIN报文发送给对方并进入一个FIN_WAIT_1的状态,即第一次挥手;

    当服务端接收到FIN报文后将无条件进行ACK报文答复,即第二次挥手;

    此时连接并没有断开,只是客户端不再向服务端发送数据,对应的客户端将进入一个FIN_WAIT_2的状态,对应服务端也将进入一个CLOSE_WAIT的状态,这里客户端不发送数据表示不主动发送数据,对于数据的应答客户端还是会照常进行;

    在连接完全断开前也要保证数据传输的可靠,即当客户端一样可以接受来自服务端的数据并且处理对应数据;

    当服务端发送最后一条数据后服务端将主动向客户端发送一个FIN报文,即第三次握手;

    同时服务端能够确认自己没有数据需要再发送给客户端也能确认客户端没有数据再发送给本端,但是其无法保证客户端的数据已经全部处理完毕或是无法保证数据没有丢包,所以连接还是不能断开;

    若是数据段在网络中丢失或是出现其他情况时服务端需要启用重传策略;

    当客户端接收完并处理完所有数据段后将对服务端再次发送一个ACK报文表示确认服务端的连接断开请求;

    至此连接彻底关闭;


重传机制

为了保证数据传输时的可靠性,TCP定制了一系列的重传机制以避免数据丢失,常见的重传机制有快速重传,超时重传,选择性重传和快速恢复;

  • 快速重传

    快速重传用于发送方识别到所发数据丢失从而快速对数据进行重发;

    通常情况下快速重传机制将在接收方返回三个重复的ACK报文时被触发;

    假设发送方向接收方发送三个报文,对应报文的序列号为499,999,1499,将在下面情况发生快速重传;

    • 发送方发送三个报文段

      报文段1 : 序号499

      报文段2 : 序号999

      报文段3 : 序号1499

    • 接收方行为

      接收方接收到序号499报文,返回ACK报文,序号为500;

      接收方未接收到序号999报文,继续等待;

      接收方接收到序号1499报文,因为期望先收到序号999报文,因此发送重复序号为500ACK报文;

    • 发送方行为

      发送方收到第一个序号为500ACK报文,忽略;

      发送方收到第二个重复序号为500ACK报文,怀疑记录丢失,记录第二个重复ACK报文;

      发送方收到第三个序号为500ACK报文,触发重传机制;

    • 触发快速重传

      发送方立即重传丢失的报文段(序号为999);

  • 超时重传

    超时重传是TCP最基本的一种重传机制,通过定时器的方式来决定何时需要重传数据;

    其工作原理为:

    • 发送数据并启动定时器

      发送方再发送一个数据段后会启动一个定时器并记录下此数据段的序列号;

      定时器设置的超时时间通常是由 往返时间 和网络状态估算来的;

    • 等待确认

      发送方等待接收方响应回的ACK报文来确认已经成功接收到该段数据段;

      如果在定时器超时之前收到对应数据段的ACK报文则停止定时器并继续发送后续数据段;

    • 定时器超时并重传

      如果定时器到期时仍未收到对应报文的ACK应答,发送方将在新的定时器周期内重新发送该未经确认的数据段;

      超时时间一般采用 指数回退算法 来避免频繁重传所引起的网络拥塞;

  • 快速恢复

    快速回复机制通常与快速重传机制结合使用,当检测到丢失的数据段时,通过调整拥塞窗口以避免传输速率下降过多;

    • 快速重传后的拥塞控制

      当接收到三个重复的ACK报文并且执行了快速重传后,发送方将窗口大小减半并设置慢启动门限;

      具体来说 : ssthresh = cwnd/2 , 然后 cwnd = ssthresh + 3 * MSS;

    • 继续发送数据

      在快速恢复期间,发送方按照新的cwnd继续发送数据段而不进入漫长的慢启动过程;

      发送新的数据段是为了利用窗口中未消费的部分确保不浪费带宽;

    • 恢复正常传输

      当收到新的ACK报文后,将会把cwnd调整到适当的大小并恢复正常传输;

  • 选择性确认

    选择性确认也被称为SACK;

    可以告知发送方具体那些数据段已经被接收和丢失,前提是通信支持SACK机制;

    假设发送方发送三个报文段,序号分别为200,400(丢失),600;

    • 接收方发送SACK

      接收方接收到序号200的报文段,发送ACK201并附带SACK选项标记接受情况;

      接收方接收到序号600,发送ACK201并在SACK中标记已接收区间以及遗漏部分400-599;

    • 发送方根据SACK重传

      发送方根据SACK选项确认丢失的报文段(序号400-599)并仅重传这部分数据;

相关推荐
速盾cdn18 分钟前
速盾:CDN缓存的工作原理是什么?
网络·安全·web安全
东华果汁哥26 分钟前
【linux 免密登录】快速设置kafka01、kafka02、kafka03 三台机器免密登录
linux·运维·服务器
咖喱鱼蛋1 小时前
Ubuntu安装Electron环境
linux·ubuntu·electron
ac.char1 小时前
在 Ubuntu 系统上安装 npm 环境以及 nvm(Node Version Manager)
linux·ubuntu·npm
肖永威1 小时前
CentOS环境上离线安装python3及相关包
linux·运维·机器学习·centos
tian2kong1 小时前
Centos 7 修改YUM镜像源地址为阿里云镜像地址
linux·阿里云·centos
布鲁格若门1 小时前
CentOS 7 桌面版安装 cuda 12.4
linux·运维·centos·cuda
Eternal-Student1 小时前
【docker 保存】将Docker镜像保存为一个离线的tar归档文件
运维·docker·容器
网络安全-杰克1 小时前
网络安全概论
网络·web安全·php
C-cat.1 小时前
Linux|进程程序替换
linux·服务器·microsoft