目录
[1. TCP协议特点(剩余)](#1. TCP协议特点(剩余))
[1.1 滑动窗口](#1.1 滑动窗口)
[1.1.1 确认应答报文丢失](#1.1.1 确认应答报文丢失)
[1.1.2 数据报丢失](#1.1.2 数据报丢失)
[1.2 流量控制](#1.2 流量控制)
[1.3. 拥塞控制](#1.3. 拥塞控制)
[1.4. 延迟应答](#1.4. 延迟应答)
[1.5. 捎带应答](#1.5. 捎带应答)
[1.6. 面向字节流](#1.6. 面向字节流)
[1.7. 粘包问题](#1.7. 粘包问题)
[2. 总结](#2. 总结)
1. TCP协议特点(剩余)
1.1 滑动窗口
前面我们了解了确认应答机制,发送端每发送一条数据报,就会等待接收方返回一个ACK应答报文。但在实际的网络通信中,一条一条发送,一条一条确认往往会造成数据传输效率较低,那么滑动窗口机制就是为了解决这一问题而出现的,通过发送多条数据,累计应答的方式提高TCP数据传输的效率,如图:

需要注意的是:
- 🔷窗口大小指的是无需等待确认应答而可以继续发送数据的最大值. 上图的窗口大小就是4000个字节(四个段)。
- 🔷发送前四个报文段时,无需等待确认应答。
- 🔷当数据报的确认应答已经被接收方收到后,数据缓冲区中就会去掉该数据,同时继续发送下一个数据报,此为" 滑动 "。
**❓问题:**如果发送过程中出现丢包,滑动窗口机制是如何处理的?
答:此问题分为两种情况:确认应答报文丢失 和 数据报丢失。下面详细说明:
1.1.1 确认应答报文丢失

前面部分数据报的应答报文丢失,这种情况下不要紧,因为可以通过后续的ACK进行确认。需要注意的是,滑动窗口的应答报文ACK是指前面所有的数据都已经成功到达,所以只要后面收到了ACK,就可以不用在意前面的数据是否收到了。
1.1.2 数据报丢失

- 🔷当数据包丢失时,如图,数据包(1001~2000)丢失,发送方(主机A)就会一直收到同一个ACK报文,就像在说" 我想要的是1001 "一样。
- 🔷当接收方连续收到3个重复的ACK,就会立即触发重传机制,不用等待超时时间到了再重传,这就是快速重传机制。
- 🔷当接收端收到1001~2000数据之后,再次返回的ACK就是5001了,因为在1001还没重传之前,这些数据就已经到达了,放在就收缓冲区中。
1.2 流量控制
由于接收端处理数据的速度是有限的,所以如果发送端发送数据的速度过快,就会导致接收端的接收缓冲区很快就满了,此时发送方继续发送,就会发送数据丢包等一系列问题。
因此TCP支持根据接收方的处理能力,来决定发送方的发送速度。这就是流量控制。
📌过程特点如下:
🔷首先接收方将自己的缓冲区大小放在TCP报文首部的 " 窗口大小" 的字段中,然后通过ACK报文告诉发送方。
🔷窗口大小越大,网络的吞吐量就越高。
🔷接收端一旦发现自己的接收缓冲区快满了,就会设置一个更小的窗口值发送给发送端。
🔷发送方发现接收端的缓冲区变小了,就会降低自己的发送速度。
🔷如果接收方缓冲区满了,窗口值就为0。此时发送方就不再发送数据了,但是需要设置一个窗口探测数据段,方便接收端把窗口大小告诉发送端。
❓问题:TCP报文首部中窗口大小为16位,于是窗口的最大值就是65535 (2的16次方) 吗?
答:不是,在TCP首部中还有一个选项的字段,其中可以包含窗口扩展因子M,表示窗口大小值向左移多少位,于是就扩展了窗口大小。
1.3. 拥塞控制
虽然TCP有了滑动窗口机制这个 " 帮手 ",能够高效的发送大量数据,但是如果在刚建立连接就发送大量数据,如果遇到网络堵塞严重的情况下,可能就会引发问题,得不偿失。
TCP使用 " 慢启动 " 机制,先发少量数据探探路,摸清当前网络的拥堵状态,再决定用多大的速度传输数据。
- 🔶引入一个概念称为拥塞窗口。发送开始的时候,拥塞窗口设置成1,每收到一个ACK应答,拥塞窗口+1。
- 🔶每次发送数据包的时候,将拥塞窗口和接收端主机反馈的窗口大小作比较,取较小的作为实际发生的窗口。
- 🔶为了不增长的太快,引入一个概念为慢启动的阈值,TCP刚开始启动的时候,此阈值的的大小和窗口大小一致,当拥塞窗口的大小超过该阈值后,不再按照指数增长,而是按照线性增长。
- 🔶在每次超时重发的时候, 慢启动阈值会变成原来的⼀半, 同时拥塞窗口置回1;少量的丢包, 我们仅仅是触发超时重传; 大量的丢包, 我们就认为网络拥塞;

1.4. 延迟应答
如果接收数据的主机立刻返回 ACK 应答,这时候返回的窗口可能比较小。
- 🔶假设接收端缓冲区为 1M。一次收到了 500K 的数据;如果立刻应答,返回的窗口就是 500K;
- 🔶但实际上可能处理端处理的速度很快,10ms 之内就把 500K 数据从缓冲区消费掉了;
- 🔶在这种情况下,接收端处理还远没有达到自己的极限,即使窗口再放大一些,也能处理过来;
- 🔶如果接收端稍微等一会再应答,比如等待 200ms 再应答,那么这个时候返回的窗口大小就是 1M;
一定要记得,窗口越大,网络吞吐量就越大,传输效率就越高。我们的目标是在保证网络不拥塞的情况下尽量提高传输效率;
❓问题:那么所有的包都可以延迟应答么?肯定也不是;
- 数量限制:每隔 N 个包就应答一次;
- 时间限制:超过最大延迟时间就应答一次;
具体的数量和超时时间,依操作系统不同也有差异;一般 N 取 2,超时时间取 200ms;
1.5. 捎带应答
收到对方数据后,不单独发 ACK;趁自己发数据的时候,把 ACK 塞在同一个包里一起发,少发一个报文。举个例子:
在延迟应答的基础上,我们发现,很多情况下,客户端服务器在应用层也是 "一发一收" 的,意味着客户端给服务器说了 "How are you",服务器也会给客户端回一个 "Fine, thank you";那么这个时候 ACK 就可以搭顺风车,和服务器回应的 "Fine, thank you" 一起回给客户端
1.6. 面向字节流
创建一个 TCP 的 socket, 同时在内核中创建一个发送缓冲区和一个接收缓冲区;
🔶调用 write 时,数据会先写入发送缓冲区中;
🔶如果发送的字节数太长,会被拆分成多个 TCP 的数据包发出;
🔶如果发送的字节数太短,就会先在缓冲区里等待,等到缓冲区长度差不多了,或者其他合适的时机发送出去;
🔶接收数据的时候,数据也是从网卡驱动程序到达内核的接收缓冲区;
🔶然后应用程序可以调用 read 从接收缓冲区拿数据;
🔶另一方面,TCP 的一个连接,既有发送缓冲区,也有接收缓冲区,那么对于这一个连接,既可以读数据,也可以写数据。这个概念叫做全双工
由于缓冲区的存在,TCP 程序的读和写不需要一一匹配,例如:
- 写 100 个字节数据时,可以调用一次 write 写 100 个字节,也可以调用 100 次 write, 每次写一个字节;
- 读 100 个字节数据时,也完全不需要考虑写的时候是怎么写的,既可以一次 read 100 个字节,也可以一次 read 一个字节,重复 100 次;
1.7. 粘包问题
- 首先要明确,粘包问题中的 "包",是指的应用层的数据包。
- 在 TCP 的协议头中,没有如同 UDP 一样的 "报文长度" 这样的字段,但是有一个序号这样的字段。
- 站在传输层的角度,TCP 是一个一个报文过来的。按照序号排好序放在缓冲区中。
- 站在应用层的角度,看到的只是一串连续的字节数据。
- 那么应用程序看到了这么一连串的字节数据,就不知道从哪个部分开始到哪个部分,是一个完整的应用层数据包。
那么如何避免粘包问题呢?归根结底就是一句话,明确两个包之间的边界。
- 对于定长的包,保证每次都按固定大小读取即可;例如上面的 Request 结构,是固定大小的,那么就从缓冲区从头开始按 sizeof (Request) 依次读取即可;
- 对于变长的包,可以在包头的位置,约定一个包总长度的字段,从而就知道了包的结束位置;
- 对于变长的包,还可以在包和包之间使用明确的分隔符 (应用层协议,是程序猿自己来定的,只要保证分隔符不和正文冲突即可);
2. 总结
TCP 是传输层面向连接、可靠、面向字节流、全双工协议,依靠一系列机制兼顾传输可靠性与传输效率。
- 连接管理:通过三次握手建立连接,验证双方收发能力、协商通信参数;通过四次挥手断开连接,适配 TCP 全双工通信特性。
- 可靠传输保障 :依靠确认应答 告知发送方接收进度;通过超时重传、快速重传解决丢包问题;搭配校验和校验数据完整性。
- 高效传输优化 :基于滑动窗口 实现批量发送,提升吞吐量;结合流量控制 防止接收缓冲区溢出;通过**拥塞控制(慢启动)**避免网络过载。
- 报文优化手段 :延迟应答 等待时机争取更大接收窗口;捎带应答把 ACK 搭载在响应报文中,减少独立 ACK 报文数量。
- 核心特性衍生问题 :TCP 面向字节流,内核缓冲区不区分应用层数据包边界,因此产生粘包问题,需要应用层自行约定包边界(定长包头、长度字段、分隔符)解决。
关于TCP协议相关的只是到这里就结束了哦,觉得对自己有帮助的小伙伴可以点赞关注一下哦。