确认应答机制
确认应答(ACK,Acknowledge)是 TCP 实现可靠传输最基础的机制。 发送方发出数据后,接收方收到数据,返回ACK 确认报文,告诉发送方:哪些数据我已经成功收到了。
TCP 是面向字节流的,不是按报文确认,而是按字节序号确认。 ACK 报文中的确认序号含义:确认序号 N,表示【序号 N 之前的所有字节,接收方全部收到了】,下一次我期望收到序号 N 开始的数据。
确认应答机制保证了历史报文的可靠性

当比如丢是3001的ack报文,但是收到4001时,也是确认之前的报文全部都收到了,提高了效率
超时重传机制
基于前面的确认应答机制,有一个关键问题:如果发送出去的数据,一直收不到对方返回的 ACK 确认报文,该怎么处理?
我们不能无限期原地等待。如果一直等不到应答,传输就卡住了,所以 TCP 设计了超时重传机制来解决丢包问题。
触发超时重传的两种场景
收不到 ACK 有两种可能性,发送方是无法区分到底是哪一种:
不管是数据丢包还是 ACK 丢包,发送方看到的现象都是:超时时间内没有收到 ACK。 于是 TCP 会判定本次传输失败,重新发送这份报文,并且重新启动超时计时器,再次等待 ACK。
如果反复多次重传之后,依旧收不到任何确认应答,TCP 就判定网络完全不通,主动发送 RST 复位报文,强制关闭这条 TCP 连接。
超时时间 RTO 该如何设定?
超时时间不能随便写死,这里存在权衡:
- 如果超时时间设置太短:网络只是轻微延迟,ACK 还在路上没回来,发送方就提前重发报文,产生大量多余重复数据包,浪费网络带宽。
- 如果超时时间设置太长:报文真的丢包了,发送方需要等待很久才会重传,降低传输效率。
所以 TCP 不会使用固定的超时时间,超时时间是动态计算出来的。 TCP 会持续采集报文往返耗时 RTT(Round-Trip Time,报文发出到收到 ACK 的时间),根据采样到的 RTT 不断平滑更新,动态调整重传超时时间 RTO,适配当前网络状态。网络波动大的时候 RTO 自动变大,网络稳定时 RTO 适当缩小。
连接管理机制
在正常情况下, TCP要经过三次握手建立连接, 四次挥手断开连接
三次握手
TCP 是全双工通信,客户端和服务器双方都可以同时收发数据。 想要建立连接,本质上需要确认两件核心事情:
理论上完成这两次确认需要四次报文交互,但是服务端收到连接请求后,可以把「SYn 同步请求」和「ACK 确认应答合并到同一个报文,所以最终只需要三次报文交互,也就是三次握手。

三次握手确认双方收发能力与通信意愿;服务端把 SYN 和 ACK 合并发送,由四次简化为三次;两次握手无法验证服务端发送能力,还会产生无效连接。

TCP 建立连接时,客户端与服务端的 TCP 内核状态会随报文交互不断切换。当两端状态都变成ESTABLISHED,代表 TCP 连接已经在内核中建立完成。 注意:三次握手由操作系统内核完成,accept 只是应用层接口,作用是从内核的全连接队列取出已建立好的连接。就算不调用 accept,内核依然可以完成三次握手。 listen 函数的第二个参数 backlog 代表全连接队列的长度,用来存放已经完成三次握手的连接。在操作系统实际实现中,允许排队的连接数量会略大于 backlog。
没有accept也可以建立连接
接收端维持的没有被accept接收的连接(全连接队列)个数是有上限的,一般是listen的第二个参数+1,—->保证了服务器的满载率
四次挥手
四次挥手是可以由任意一方断开连接,但通常是client先关闭连接

TCP 是全双工通信,连接可以看作两条独立的单向通道(一条客户端→服务端,一条服务端→客户端),读写是分开的。 FIN报文的含义:我这边不再发送新数据,关闭我方的写通道;但读通道依然保持打开,还能继续接收对方发来的数据。
这就是四次挥手不能合并成三次的根本原因:双方可以独立关闭自己的写方向。

调用close就会将连接关闭
但是我们也有其他的系统调用shotdown
shutdown:专门用来关闭单向通道,可以选择只关闭写端,保留读端,完美实现 TCP 半关闭。不受文件描述符引用计数影响,直接操作内核 TCP 连接。

为什么会没有立即关闭???

原因 1
第四次挥手的 ACK 报文在网络传输时有可能丢失。 如果客户端收到 FIN 之后直接关闭连接:
- 服务端收不到 ACK,会一直重发 FIN 报文,持续占用系统资源;
- 此时客户端连接已经销毁,收到重传 FIN 只能回复 RST,导致服务端无法正常关闭。
客户端保持TIME_WAIT状态等待 2MSL: 只要服务端重发 FIN,客户端内核就可以重新回复 ACK,保证服务端正常完成关闭流程。
原因 2
网络传输存在延迟,可能存在迟到的旧数据包(陈年报文)在网络里游荡。 如果客户端立刻释放端口,马上用相同 IP + 端口新建 TCP 连接: 旧的滞留报文到达后,包头的序列号、端口刚好匹配,新连接会错误接收这份不属于自己的旧数据,造成数据错乱。
2MSL 是报文在网络中最大存活时间。等待满 2MSL,可以确保:上一轮连接所有残留报文,全部在网络中失效消失,不会干扰后续新连接。
TCP 在新建连接时,初始序列号 ISN 会使用随机值。就算有残留报文,序列号大概率对不上,进一步降低旧报文被误接收的概率,作为辅助防护手段。
应用层的 fd 已经关掉了,但是内核还要 “站岗等 2MSL”,防止报文滞留,所以端口还被内核占着,2MSL 超时后内核才彻底释放端口与 TCP 资源。
流量控制

Client 不停发消息,Server 应用层处理速度跟不上,Server 内核的 TCP 接收缓冲区被填满。 当接收缓冲区满之后,新到达的 TCP 报文段就会被丢弃,造成网络资源浪费。 👉 解决这个问题的机制就是 TCP 流量控制

TCP 提供流量控制机制:根据接收端实际的数据处理能力,动态限制发送端的数据发送速率,避免发送方发送速度过快,接收方来不及读取,最终导致内核接收缓冲区溢出、报文丢失,浪费网络资源。
TCP 报头中存在16 位窗口大小字段,该字段承载的值就是接收窗口 rwnd(Receive Window)。 接收方在回复的 ACK 报文中,将当前内核接收缓冲区剩余可用字节数填入该字段,告知发送方:我当前最多还能接收多少字节的数据。
核心规则
- 发送方收到rwnd=0后,停止发送新的数据报文;
- 但发送方需要定期发送窗口探测报文段,持续询问接收端最新窗口大小。
探测报文的意义:如果接收端后续窗口恢复的 ACK 报文在网络中丢失,发送方会永久阻塞等待。窗口探测用来避免这种通信死锁。
滑动窗口

滑动窗口本质是发送缓冲区里的一段区间,它代表当前可以直接发送、不需要等待前面每一段确认应答的数据范围。
- 一部分可能已经发出去,正在等待 ACK;
- 一部分还在缓冲区,随时可以发送。


start_win = 1001,end_win = 5001 窗口大小:end_win – start_win = 4000字节。 窗口内序号范围:[1001, 5000],这一段数据可以一次性连续发送,不用等每一段 ACK。 主机 A 把窗口内的数据分成 4 段,依次发送给主机 B。
收到 ACK 报文
主机 B 收到1001~2000的数据之后,回复 ACK 报文:
- 确认序号:2001,含义:序号 2001 之前的数据全部收到,期望下一次收到序号 2001 开始的数据
- ACK-win(rwnd 接收窗口)=4000,代表主机 B 还能接收 4000 字节
更新窗口边界公式
滑动窗口的大小是会发生变化的

滑动窗口会向左滑动吗????
不可能!!!
确认序号只会越来越大,所以start_win只能不断向右(序号增大方向)移动,不可能回退、向左移动。

快重传机制
TCP 超时重传有一个缺点:需要等待超时计时器到期之后,才会重传丢失报文。一旦超时时间比较长,传输就会卡顿,降低吞吐量。快重传(快速重传)就是用来优化这个场景,不用等待超时,提前触发重传。

我们可以思考一下这个过程中对方的确认序号是什么????

如果发送端主机连续三次收到了同样一个 "4001" 这样的应答, 就会将对应的数据 4001-5000 重 新发送; • 这个时候接收端收到了 4001 之后, 再次返回的ACK就是7001了(因为2001 – 7000)接收端其实之前 就已经收到了, 被放到了接收端操作系统内核的接收缓冲区中;
快重传核心规则

TCP 接收方是按序接收字节流,只有补齐当前缺失的最前面一段,才能继续确认后面的数据。
哪怕后面的报文已经到达接收缓冲区,只要前面一段空缺,就不会产生更大的确认序号,只会不断重复 “我缺当前窗口最开头这一段”。 所以:窗口内任意位置丢包,都会等价于当前窗口起点处丢包,只需要重传缺失的这一段,不需要重传整个窗口全部数据。
两种重传触发方式
网硕互联帮助中心



评论前必须登录!
注册