TCP(Transmission Control Protocol,传输控制协议)是TCP/IP协议栈中的核心协议,作为互联网通信的基石,承担着确保数据可靠传输的重要职责。
接下来我将分两篇文章,从四个部分带大家学习一些与TCP相关的基本概念和机制,首先我将带大家认识一下TCP报头字段的含义,然后了解TCP保证可靠性的一些机制,接下来是TCP进行效率优化的机制,最后是TCP与应用层相关的概念。本篇文章将带大家初步认识TCP协议。
以下是TCP的报头:
如果大家是第一次见这个报头,可能会觉得太复杂了,根本记不住,但是没关系,接下来我将解释每个字段的含义:
接下来,让我们看看报头里的这几个标记位,它们置0表示无效,置1表示有效
我们要知道,任何一个协议,都必须解决三个问题:如何封装数据?如何将报头与有效载荷进行分离?如何将有效载荷交付给上层协议?
TCP如何封装数据?
TCP在内核中其实就是定义出来的一个位段类型,给数据封装TCP报头时,实际上就是用该位段类型定义一个变量,然后填充TCP报头当中的各个属性字段,最后将这个TCP报头拷贝到数据的首部,至此便完成了TCP报头的封装。
TCP如何分离报头与有效载荷?
在收到一份TCP报文后,我们需要能明确报头和数据的边界。我们先读取报文前20字节的固定长度,其中包含4位首部长度。所以可以按如下步骤分离报头与有效载荷:
这时候比较细心的同学可能就会有疑问了,4位的首部长度是如何表示20字节以上的数据长度的呢?这是因为首部长度字段的基本单位是4字节,因为4位能表示0-15,所以这个首部长度字段最多能够表示60字节的长度。
TCP如何决定将有效载荷交付给上层的哪一个协议?
我们要知道,网络通信本质上可以理解为是进程间通信,TCP报头包含了16位目的端口,所以TCP报文在到达目的主机后,在目的主机的传输层,可以根据目的端口,找到应用层进程,将有效载荷交付给这个目的进程。
补充知识: 内核中用哈希的方式维护了端口号与进程ID之间的映射关系,因此传输层可以通过端口号快速找到其对应的进程ID,进而找到对应的应用层进程。
我们前面提到过,TCP是能够保证数据可靠传输的一个协议,那么我们如何理解可靠性呢?
什么是可靠?
由于网络通信过程中,发送方发送数据之后,并不能保证成功被对端接收,这是因为数据传输过程中可能会出现各种错误,但是当收到对端主机发送的响应时,发送方就能够确定上次发送的数据被对端可靠地收到了,我们认为,这就是可靠。而这种通过收到对方应答消息来确认消息可靠地完成了传输的机制,就称为确认应答机制。
为什么需要32位序号?
根据前面提到的确认应答机制,为了保证可靠传输,发送方发送的数据需要收到对端的响应后才能视作传输成功,为了提高通信的效率,双方数据通信时,肯定不会等待收到上一次响应后再发送新的数据,而是连续发送多个报文,只要保证每个报文都收到对应的响应消息,就认为这些报文都确实被对方接收了。
但是这就带来了一个问题,发送方连续发送的多个报文在网络传输时选择的路径可能是不同的,所以报文的到达顺序和发送顺序往往是不一致的,这就导致报文乱序到达,所以我们可以给发送报文的首字节进行编号,根据编号就能对收到的报文进行排序,重新得到有序的报文,这个编号就是TCP报头字段中的32位序号。
32位确认序号的作用?
32位确认序号的作用是,告诉对端,当前已经收到了哪些数据,下次发送应该从什么位置继续发送。
以上图为例,当主机B收到主机A发送过来的32位序号为1的报文时,由于该报文当中包含1000字节的数据,因此主机B已经收到序列号为1-1000的字节数据,于是主机B发给主机A的响应数据的报头当中的32位确认序号的值就会填成1001。
序号机制能否处理报文丢失的情况?
依然以上图为例,主机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位窗口大小的作用之前,需要给大家补充一个知识,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使用场景: