让小白也能理解TCP协议(完结)
2026/5/17 1:33:32 网站建设 项目流程

一:引言

这次我们将会把TCP所有的内容讲完,包括拥塞控制,延迟应答,捎带应答,面向字节流;并且进行总结,包括一些常见的关于TCP协议的一些面试问题,例如粘包问题,TCP连接异常的几种情况。

二:拥塞控制

虽然TCP有了滑动窗口这个大杀器,能够高效可靠的发送大量的数据。但是如果在刚开始阶段就发送大量的数据,仍然可能引发问题。

因为网络上有很多的计算机,可能当前的网络状态就已经比较拥堵。在不清楚当前网络状态下,贸然发送大量的数据,是很有可能引起雪上加霜的。

我们来举个例子:

如果10000个数据包 ---------> 丢失了1,2个数据包 --------->重传,

如果10000个数据包 ---------> 丢失了5000个数据包 ----------->不会进行重传。

因为正常的丢包是非常一小部分,但是如果出现了大量的丢包,那其实是网络出现的堵塞,虽然我这里有点夸张,那是便于理解。

实际上%1~%1.5认为是正常丢包,%3就已经是网络堵塞,%5就是网络严重堵塞。

那拥塞控制是怎么做的呢?(前提是网络已经堵塞了)

这里我们引出两个新的概念:

慢启动机制:先发少量的数据,探探路,摸清当前的⽹络拥堵状态,再决定按照多⼤的速度传输数据;

拥塞窗口:一个由发送端维护的状态变量,用于限制一次可以发送到网络中的、尚未确认的数据量上限。

通俗理解
拥塞窗口就像一个“水龙头”,根据网络的拥堵程度动态调节水的流量。网络不拥堵时,窗口会尝试增大(多发数据);网络出现拥堵(如丢包)时,窗口就会立即减小(少发数据),从而避免让网络“堵死”。

  • 发送开始的时候,定义拥塞窗口大小为1;
  • 每次收到⼀个ACK应答,拥塞窗⼝加1;
  • 每次发送数据包的时候,将拥塞窗口和接收端主机反馈的窗口大小做比较,取较小的值作为实际发送的窗口,因为我们首先要保证网络不拥堵的情况下,再尽量满足接收端的最大接收能力,所以取拥塞窗口和接收端窗口大小的最小值是这样来的。

注意:实际发送数据出去的还是靠滑动窗口,而拥塞窗口和窗口大小取最小值只是决定滑动窗口的大小。

像上面这样的拥堵窗口增长速度,是指数级别的,"慢启动"只是指初使时慢,但是增长速度非常快。通过这样的慢启动,从一开始发送少量数据确认网络是否拥堵,到后面又能迅速的回到一个客观的发送速度,找到了可靠性和效率的平衡点。

但是指数增长肯定是不能一直维持的,指数增长一直有一个爆炸式增长的名号,所以有了以下的一些改变,很好的权衡了后期无法控制的指数增长的数据。

我们来分析一下这张图:

  • 此处我们引入一个叫做慢启动的阈值。
  • 当拥塞窗口超过这个阈值的时候,不再按照指数方式增长,而是按照线性方式增长。
  • 直到再次发生了大量丢包情况,说明又网络堵塞了,所以会再次进行慢启动。
  • 但是慢启动的阈值变成了上次网络堵塞时窗口大小的一半,这样是为了能够指数快速增长到一个几乎能保证不会网络拥堵的一个大小。
  • 接着再次线性增加,直到再次遇到网络堵塞,又会进行再一次的慢启动,以此循环。

滑动窗口是衡量接收端接收数据能力的指标。

拥塞窗口是衡量网络堵塞的指标,也就是网络接收能力的指标。

大家想一想为什么要这样不断自启动循环呢?

因为网络是大家共享的,网络是不断变化的,所以接收能力是不断变化的,不断地自启动循环可以及时得知网络的接收能力。

拥塞控制的过程就像是热恋中的感觉,刚开始的时候大家都很拘谨,慢慢的互相了解,但是过后熟悉之后就会有极大的发展。

拥塞控制,归根结底是TCP协议想尽可能快的把数据传输给对方,但是又要避免给⽹络造成太大压力的折中方案。

三:延迟应答

延迟应答其实就是表面的意思----------收到数据后,等待一会儿在进行ACK应答,或者说收到了第一个数据包不进行ACK应答,收到第二个数据包再进行ACK应答,因为确定序号的含义是收到了该序号之前的数据,所以如图回复了2001,可以不用回复前面的1001。

延迟应答是怎么个回事我们了解了,现在我们想一想为什么要这么做?

如果接收数据的主机立刻返回ACK应答,这时候返回的窗口大小能比较小。

  • 假设接收端缓冲区为1M,⼀次收到了500K的数据;如果立刻应答,返回的窗口就是500K;
  • 但实际上可能处理端处理的速度很快,10ms之内就把500K数据从缓冲区消费掉了;
  • 在这种情况下,接收端处理还远没有达到自己的极限,即使窗口再放⼤⼀些,也能处理过来;
  • 如果接收端稍微等⼀会再应答,比如等待200ms再应答,那么这个时候返回的窗口大小就是1M;

⼀定要记得,窗口越大,网络吞吐量就越大,传输效率就越高,我们的⽬标是在保证网络不拥塞的情况下尽量提高传输效率;

那么,所有的包都能够延迟应答吗?

那当然不是,有以下两条限制:

  • 数量限制:每隔N个包就应答⼀次;
  • 时间限制:超过最大延迟时间就应答⼀次;

具体的数量和超时时间,依操作系统不同也有差异;⼀般N取2,超时时间取200ms;

捎带应答,在前面的博客中讲过,这里我就补充一点:

在三次握手中,前两次握手不能携带任何数据,最后一次ACK可以携带数据,这也是一个捎带应答。

四:面对字节流带来的粘包问题

TCP 对应用程序提交的数据,只看作一串连续的、无结构的字节序列,它不维护数据块之间的边界,如何划分这条流,完全由应用层协议决定

  • 首先要明确,粘包问题中的"包",是指的应用层的数据包。
  • 在TCP的协议头中,没有如同UDP⼀样的"报文长度"这样的字段,但是有⼀个序号这样的字段。
  • 应用程序看到了这么⼀连串的字节数据,就不知道从哪个部分开始到哪个部分是⼀个完整的 应用层数据包。

那么如何避免粘包问题呢?归根结底就是⼀句话,明确两个包之间的边界

大概就是以下三种方案:

  1. 固定发送数据的长度,不足部分填补。举个例子:如果固定长度为6,但是你发送了长度为5的数据,那么就需要填补一个约定好的符号,比如说空格也行。
  2. 在数据前面添加一个的整数,这个整数记录的就是数据的长度。
  3. 数据包和数据包之间有特殊符号分隔,比如说一些不常用的符号,记得千万不能和正文符号一样。

思考:对于UDP协议来说,是否也存在"粘包问题"呢?

这是udp缓冲区的图片,因为udp缓冲区底层的实现就是将边界保留的,所以udp是不会有粘包问题的。

五:TCP连接异常

1.进程终止

进程终止会释放⽂件描述符,底层仍然可以发送FIN,完成四次挥手,和正常关闭没有什么区别。

2.电脑重启

其实这种情况和进程终止是一样的,为什么这么说呢?

平常我们关机电脑的时候,如果电脑上还有程序没有关闭,会提示我们还有程序正在进行,是否要继续关机,所以我们关机前,电脑会先把程序终止了,再去关闭电脑,所以和进程终止是一样的。

3.机器掉电/网线断开

这个就比较有意思,我们以发送端突然断网为例,其实都是一样的。

如果突然发送端断网,那么发送端的操作系统会检查到网卡出现异常变化,就会释放掉所有的连接,但是接收端不知道发送端断网了,还没有进行四次挥手,接收端就会无法正常关闭连接。

所以我们引出一个新的概念叫做保活定时器,也就是说,如果说一定时间内,接收端没有收到发送端的数据包,便会向发送端进行询问是否还在,如果对方不在,也会把连接断开这种定期的询问也可以叫做心跳机制。

另外,应用层的某些协议,也有⼀些这样的检测机制,例如HTTP长连接中,也会定期检测对方的状态,例如QQ,在QQ断线之后,也会定期尝试重新连接。

六:总结

为什么TCP这么复杂?因为要保证可靠性,同时又尽可能的提高性能。

可靠性:

  • 校验和
  • 序列号(按序到达)
  • 确认应答
  • 超时重发
  • 连接管理
  • 流量控制
  • 拥塞控制

提高性能:

  • 滑动窗⼝
  • 快速重传
  • 延迟应答
  • 捎带应答

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询