【传输层协议】TCP协议详解(上)

前言

        TCP(Transmission Control Protocol,传输控制协议)是TCP/IP协议栈中的核心协议,作为互联网通信的基石,承担着确保数据可靠传输的重要职责。

        接下来我将分两篇文章,从四个部分带大家学习一些与TCP相关的基本概念和机制,首先我将带大家认识一下TCP报头字段的含义,然后了解TCP保证可靠性的一些机制,接下来是TCP进行效率优化的机制,最后是TCP与应用层相关的概念。本篇文章将带大家初步认识TCP协议。

初步认识TCP协议

TCP协议格式

以下是TCP的报头:

【传输层协议】TCP协议详解(上)_第1张图片

如果大家是第一次见这个报头,可能会觉得太复杂了,根本记不住,但是没关系,接下来我将解释每个字段的含义:

  • 源/目的端口号:定位通信进程,确定数据是哪个进程发送的,将发往对端主机的哪个进程
  • 32位序号/32位确认序号:序号标识当前报文段首字节的全局编号,解决乱序问题;确认序号用于向发送方发送期待接受的下一个字节编号
  • 4位TCP首部长度:标识该TCP报头的长度,通过这个字段可以明确报头和数据的边界
  • 6位保留字段:暂未使用
  • 16位窗口大小:与保证TCP可靠性机制和效率优化机制有关
  • 16位检验和:覆盖首部+数据的循环冗余校验码(CRC),接收端验证数据完整性,错误则直接丢弃报文
  • 16位紧急指针:与标记位URG配合使用,定位紧急数据的位置
  • 选项字段:TCP报头当中允许携带额外的选项字段,最多40字节

接下来,让我们看看报头里的这几个标记位,它们置0表示无效,置1表示有效

  • URG:标识紧急指针是否有效。
  • ACK:确认序号是否有效。
  • PSH:提示接收端应用程序立刻将TCP接收缓冲区当中的数据读走。
  • RST:用于立即终止异常连接,将RST标识有效的报文称为复位报文段。
  • SYN:请求与对方建立连接,将SYN标识有效的报文称为同步报文段。
  • FIN:告知对方,本端数据传输完成,进入版关闭状态,将FIN标识有效的报文称为结束报文段。

TCP的封装和分离

        我们要知道,任何一个协议,都必须解决三个问题:如何封装数据?如何将报头与有效载荷进行分离?如何将有效载荷交付给上层协议?

TCP如何封装数据?

        TCP在内核中其实就是定义出来的一个位段类型,给数据封装TCP报头时,实际上就是用该位段类型定义一个变量,然后填充TCP报头当中的各个属性字段,最后将这个TCP报头拷贝到数据的首部,至此便完成了TCP报头的封装。

TCP如何分离报头与有效载荷?

        在收到一份TCP报文后,我们需要能明确报头和数据的边界。我们先读取报文前20字节的固定长度,其中包含4位首部长度。所以可以按如下步骤分离报头与有效载荷:

  • 首先读取报文的前20个字节,并从中提取出4位的首部长度,此时获得了TCP报头的大小size
  • 如果size的值大于20字节,则需要继续从报文当中读取size-20字节的数据,这部分数据就是TCP报头当中的选项字段。
  • 读取完TCP的基本报头和选项字段后,剩下的就是有效载荷了。

这时候比较细心的同学可能就会有疑问了,4位的首部长度是如何表示20字节以上的数据长度的呢?这是因为首部长度字段的基本单位是4字节,因为4位能表示0-15,所以这个首部长度字段最多能够表示60字节的长度。

TCP如何决定将有效载荷交付给上层的哪一个协议?

        我们要知道,网络通信本质上可以理解为是进程间通信,TCP报头包含了16位目的端口,所以TCP报文在到达目的主机后,在目的主机的传输层,可以根据目的端口,找到应用层进程,将有效载荷交付给这个目的进程。

补充知识: 内核中用哈希的方式维护了端口号与进程ID之间的映射关系,因此传输层可以通过端口号快速找到其对应的进程ID,进而找到对应的应用层进程。

认识序号与确认序号

        我们前面提到过,TCP是能够保证数据可靠传输的一个协议,那么我们如何理解可靠性呢?

什么是可靠?

        由于网络通信过程中,发送方发送数据之后,并不能保证成功被对端接收,这是因为数据传输过程中可能会出现各种错误,但是当收到对端主机发送的响应时,发送方就能够确定上次发送的数据被对端可靠地收到了,我们认为,这就是可靠。而这种通过收到对方应答消息来确认消息可靠地完成了传输的机制,就称为确认应答机制

为什么需要32位序号?

        根据前面提到的确认应答机制,为了保证可靠传输,发送方发送的数据需要收到对端的响应后才能视作传输成功,为了提高通信的效率,双方数据通信时,肯定不会等待收到上一次响应后再发送新的数据,而是连续发送多个报文,只要保证每个报文都收到对应的响应消息,就认为这些报文都确实被对方接收了。

        但是这就带来了一个问题,发送方连续发送的多个报文在网络传输时选择的路径可能是不同的,所以报文的到达顺序和发送顺序往往是不一致的,这就导致报文乱序到达,所以我们可以给发送报文的首字节进行编号,根据编号就能对收到的报文进行排序,重新得到有序的报文,这个编号就是TCP报头字段中的32位序号

【传输层协议】TCP协议详解(上)_第2张图片

32位确认序号的作用?

        32位确认序号的作用是,告诉对端,当前已经收到了哪些数据,下次发送应该从什么位置继续发送。

        以上图为例,当主机B收到主机A发送过来的32位序号为1的报文时,由于该报文当中包含1000字节的数据,因此主机B已经收到序列号为1-1000的字节数据,于是主机B发给主机A的响应数据的报头当中的32位确认序号的值就会填成1001。

【传输层协议】TCP协议详解(上)_第3张图片

序号机制能否处理报文丢失的情况?

        依然以上图为例,主机A发送了三个报文给主机B,其中每个报文的有效载荷都是1000字节,这三个报文的32位序号分别是1、1001、2001。

        如果这三个报文在网络传输过程中出现了丢包,最终只有序号为1和2001的报文被主机B收到了,那么当主机B在对报文进行顺序重排的时候,就会发现只收到了1-1000和2001-3000的字节数据。此时主机B在对主机A进行响应时,其响应报头当中的32位确认序号填的就是1001,主机A就直到序号为1001的报文丢失了,需要重传。

为什么需要两套序号?

        根据前面的例子,确认序号用于告诉对端,当前已经收到了哪些数据,下次发送应该从什么位置继续发送。那好像不需要确认序号呀,发送方和接收方公用一套序号不就行了吗?问题在于,我们刚刚讨论的都是主机A给主机B发送数据的场景,而TCP作为支持全双工通信的协议,当然也要支持主机B给主机A发消息啦,这时候又该如何保证主机B发送数据的可靠性呢?

        所以在TCP通信时,双方都需要确认应答机制,因此一套序号无法满足需求,所以TCP才有了序号+确认序号。

认识16位窗口大小

        在学习16位窗口大小的作用之前,需要给大家补充一个知识,TCP本身会维护发送缓冲区和接收缓冲区,分别用于暂时存放尚未发送的数据和接收到的数据,这两个缓冲区是在传输层实现的,用户并不知道,这就意味着:

  • 当上层调用write/send这样的系统调用接口时,实际不是将数据直接发送到了网络当中,而是将数据从应用层拷贝到了TCP的发送缓冲区当中。
  • 当上层调用read/recv这样的系统调用接口时,实际也不是直接从网络当中读取数据,而是将数据从TCP的接收缓冲区拷贝到了应用层而已。

        也就是说,write/send函数将数据拷贝到TCP的发送缓冲区后,任务就结束了,接下来缓冲区中的数据什么时候发送,如何发送将由TCP协议负责处理。所以TCP之所以能被称为传输控制协议,正是因为从数据的发送,到传输数据出现的错误,再到数据的接收,整个传输过程TCP都会负责,上层用户只需要拷贝数据到缓冲区或从缓冲区拷贝数据即可。

发送缓冲区和接收缓冲区的意义?

        发送缓冲区暂时保存发送过的数据,能保证当传输失误时能够进行数据重传;由于应用层处理数据的速度是有限的,接收缓冲区能防止上层来不及处理的数据被迫丢弃,并且接收缓冲区可以作为数据重排的场所。

        接收缓冲区和发送缓冲区都可以视作生产者消费者模型:

  • 对于发送缓冲区,应用层向发送缓冲区发送数据,网络层从发送缓冲区当中取出数据进一步封装。此时上层应用扮演的就是生产者的角色,下层网络层扮演的就是消费者的角色,而发送缓冲区对应的就是“交易场所”。
  • 对于接收缓冲区,应用层从接收缓冲区中取出数据,网络层向接收缓冲区发送数据。此时上层应用扮演的就是消费者的角色,下层网络层扮演的就是生产者的角色,而接收缓冲区对应的就是“交易场所”。

因此引入发送缓冲区和接收缓冲区相当于引入了两个生产者消费者模型,该将上层应用与底层通信细节进行了解耦,并且能削峰填谷,支持忙闲不均。

窗口大小的作用?

        现在我们知道了TCP在传输层为我们维护了发送缓冲区和接收缓冲区,那么我们可以把数据从发送端到接收端的过程理解为,数据从发送缓冲区被拷贝到了接收缓冲区。由于缓冲区必然有大小限制,如果发送数据的速度超过了接收处理数据的速度,缓冲区将会溢出,之后到达的数据都只能被迫丢弃,这绝不是我们希望见到的。

        所以TCP考虑到了这一点,在报头中分配了16位的窗口大小,用于告知对方,自己当前接收缓冲区的剩余大小是多少,对方在发送数据时就可以根据对方的接收能力决定发送速度。

认识六个标志位

        我们要知道,TCP报文的类型多种多样,除了正常通信时的普通报文,还有建立连接时的请求建立连接的报文,以及断开连接时的告知断开连接的报文等等。为了对各种报文进行区分,从而接收方传输层能对不同的报文做不同处理,TCP报头分配了6个标志位用于标识类型,为0表示假,为1表示真,接下来让我们逐一学习这几个标志位的作用。

SYN

        报头的SYN被设置为1,则该报文是连接建立的请求报文。

ACK

        报头的ACK被设置为1,表明该报文可以对收到的报文进行确认。

FIN

        报头的FIN被设置为1,表明该报文是一个连接断开的请求报文。

URG

        通过前面的学习,我们知道了序号和确认序号机制可以保证我们接收到的数据是有序的,所以接收方读取接收缓冲区的数据也理应是按序的,但有时候我们可能有些紧急数据需要上层优先处理,这时可以将URG置为1,启用16位紧急指针(表示紧急数据在报文中的偏移量),通过启用紧急指针,就能优先处理最多1字节的紧急数据。

PSH

        当发送方设置PSH=1时,接收方的TCP协议栈会立即将接收缓冲区中的数据交付给应用层,无需等待后续数据填充缓冲区。

RST

        RST(Reset)标志用于异常终止连接,其核心作用是立即释放资源并通知对方连接已非正常关闭。与通过FIN标志的优雅关闭不同,RST提供了一种强制中断机制,适用于处理错误或紧急情况。

常见RST使用场景:

  • 连接未建立时接收数据,当一方(如服务器)未在监听端口时,收到客户端的数据报文(如SYN外的其他报文),会回复RST拒绝连接。
  • 主动强制关闭连接,程序崩溃、资源不足或需立即终止连接时。
  • 处理半开连接,一方意外崩溃(如断电),另一方仍认为连接有效。当存活方发送数据时,崩溃方重启后收到无效报文,回复RST。
  • 收到无效报文,收到序列号不在接收窗口内的报文(如已确认的数据重传)

你可能感兴趣的:(Linux网络,tcp/ip,网络,服务器)