
确认应答机制确认应答ACKAcknowledge是 TCP 实现可靠传输最基础的机制。 发送方发出数据后接收方收到数据返回ACK 确认报文告诉发送方哪些数据我已经成功收到了。TCP 是面向字节流的不是按报文确认而是按字节序号确认。 ACK 报文中的确认序号含义确认序号 N表示【序号 N 之前的所有字节接收方全部收到了】下一次我期望收到序号 N 开始的数据。确认应答机制保证了历史报文的可靠性当比如丢是3001的ack报文,但是收到4001时,也是确认之前的报文全部都收到了,提高了效率超时重传机制基于前面的确认应答机制有一个关键问题如果发送出去的数据一直收不到对方返回的 ACK 确认报文该怎么处理我们不能无限期原地等待。如果一直等不到应答传输就卡住了所以 TCP 设计了超时重传机制来解决丢包问题。触发超时重传的两种场景收不到 ACK 有两种可能性发送方是无法区分到底是哪一种客户端发出的数据报文在网络传输途中丢失服务端根本没有收到数据自然不会返回 ACK。数据报文已经成功到达服务端但是服务端回复的 ACK 确认报文在半路丢失了。不管是数据丢包还是 ACK 丢包发送方看到的现象都是超时时间内没有收到 ACK。 于是 TCP 会判定本次传输失败重新发送这份报文并且重新启动超时计时器再次等待 ACK。如果反复多次重传之后依旧收不到任何确认应答TCP 就判定网络完全不通主动发送 RST 复位报文强制关闭这条 TCP 连接。超时时间 RTO 该如何设定超时时间不能随便写死这里存在权衡如果超时时间设置太短网络只是轻微延迟ACK 还在路上没回来发送方就提前重发报文产生大量多余重复数据包浪费网络带宽。如果超时时间设置太长报文真的丢包了发送方需要等待很久才会重传降低传输效率。所以 TCP 不会使用固定的超时时间超时时间是动态计算出来的。 TCP 会持续采集报文往返耗时 RTTRound-Trip Time报文发出到收到 ACK 的时间根据采样到的 RTT 不断平滑更新动态调整重传超时时间 RTO适配当前网络状态。网络波动大的时候 RTO 自动变大网络稳定时 RTO 适当缩小。连接管理机制在正常情况下, TCP要经过三次握手建立连接, 四次挥手断开连接三次握手TCP 是全双工通信客户端和服务器双方都可以同时收发数据。 想要建立连接本质上需要确认两件核心事情双方有通信意愿愿意建立 TCP 连接双方收发通道正常客户端能发能收、服务端也能发能收。理论上完成这两次确认需要四次报文交互但是服务端收到连接请求后可以把「SYn 同步请求」和「ACK 确认应答合并到同一个报文所以最终只需要三次报文交互也就是三次握手。三次握手确认双方收发能力与通信意愿服务端把 SYN 和 ACK 合并发送由四次简化为三次两次握手无法验证服务端发送能力还会产生无效连接。TCP 建立连接时客户端与服务端的 TCP 内核状态会随报文交互不断切换。当两端状态都变成ESTABLISHED代表 TCP 连接已经在内核中建立完成。 注意三次握手由操作系统内核完成accept 只是应用层接口作用是从内核的全连接队列取出已建立好的连接。就算不调用 accept内核依然可以完成三次握手。listen 函数的第二个参数 backlog 代表全连接队列的长度用来存放已经完成三次握手的连接。在操作系统实际实现中允许排队的连接数量会略大于 backlog。没有accept也可以建立连接接收端维持的没有被accept接收的连接(全连接队列)个数是有上限的,一般是listen的第二个参数1,----保证了服务器的满载率四次挥手四次挥手是可以由任意一方断开连接,但通常是client先关闭连接TCP 是全双工通信连接可以看作两条独立的单向通道一条客户端→服务端一条服务端→客户端读写是分开的。FIN报文的含义我这边不再发送新数据关闭我方的写通道但读通道依然保持打开还能继续接收对方发来的数据。这就是四次挥手不能合并成三次的根本原因双方可以独立关闭自己的写方向。调用close就会将连接关闭但是我们也有其他的系统调用shotdownshutdown专门用来关闭单向通道可以选择只关闭写端保留读端完美实现 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 位窗口大小字段该字段承载的值就是接收窗口 rwndReceive Window。 接收方在回复的 ACK 报文中将当前内核接收缓冲区剩余可用字节数填入该字段告知发送方我当前最多还能接收多少字节的数据。核心规则窗口大小数值越大代表接收端空闲缓冲区空间越多通信的吞吐量越高。当接收端监测到自身接收缓冲区快要被填满时会在 ACK 报文中填入更小的窗口值通知发送方降低发送速率。发送方收到更新后的窗口值主动减慢数据发送速度防止接收缓冲区溢出丢包。若接收端缓冲区完全占满则将窗口大小置为0发送方收到rwnd0后停止发送新的数据报文但发送方需要定期发送窗口探测报文段持续询问接收端最新窗口大小。探测报文的意义如果接收端后续窗口恢复的 ACK 报文在网络中丢失发送方会永久阻塞等待。窗口探测用来避免这种通信死锁。滑动窗口滑动窗口本质是发送缓冲区里的一段区间它代表当前可以直接发送、不需要等待前面每一段确认应答的数据范围。绿色已确认数据数据已经发送出去并且收到了对方的 ACK 确认。这部分数据使命完成可以直接从缓冲区清除释放空间。红色滑动窗口窗口内待发送 / 已发送未确认这就是滑动窗口覆盖的区域。窗口内的数据可以直接发送出去不需要等前面每一条报文的 ACK。一部分可能已经发出去正在等待 ACK一部分还在缓冲区随时可以发送。蓝色待发送数据窗口外在滑动窗口外面暂时不能发送。只有当收到 ACK窗口向右滑动、包含这部分数据之后才允许发送。start_win 1001end_win 5001窗口大小end_win - start_win 4000字节。 窗口内序号范围[1001, 5000]这一段数据可以一次性连续发送不用等每一段 ACK。 主机 A 把窗口内的数据分成 4 段依次发送给主机 B。收到 ACK 报文主机 B 收到1001~2000的数据之后回复 ACK 报文确认序号2001含义序号 2001 之前的数据全部收到期望下一次收到序号 2001 开始的数据ACK-winrwnd 接收窗口4000代表主机 B 还能接收 4000 字节更新窗口边界公式新的start_win ACK确认序号新的end_win 新start_win ACK-win滑动窗口的大小是会发生变化的滑动窗口会向左滑动吗????不可能!!!确认序号只会越来越大所以start_win只能不断向右序号增大方向移动不可能回退、向左移动。快重传机制TCP 超时重传有一个缺点需要等待超时计时器到期之后才会重传丢失报文。一旦超时时间比较长传输就会卡顿降低吞吐量。快重传快速重传就是用来优化这个场景不用等待超时提前触发重传。我们可以思考一下这个过程中对方的确认序号是什么????如果发送端主机连续三次收到了同样一个 4001 这样的应答, 就会将对应的数据 4001-5000 重 新发送; • 这个时候接收端收到了 4001 之后, 再次返回的ACK就是7001了(因为2001 - 7000)接收端其实之前 就已经收到了, 被放到了接收端操作系统内核的接收缓冲区中;快重传核心规则触发条件发送方收到3 个重复 ACK冗余 ACK直接判定报文丢失立即重传丢失报文不等待超时重传定时器。原理接收方收到乱序报文时会持续发送重复 ACK告诉发送方缺少哪一段数据。多个重复 ACK 代表这个丢包概率很高。只重传丢失的那一段报文不需要重传滑动窗口内所有数据。TCP 接收方是按序接收字节流只有补齐当前缺失的最前面一段才能继续确认后面的数据。哪怕后面的报文已经到达接收缓冲区只要前面一段空缺就不会产生更大的确认序号只会不断重复 “我缺当前窗口最开头这一段”。 所以窗口内任意位置丢包都会等价于当前窗口起点处丢包只需要重传缺失的这一段不需要重传整个窗口全部数据。两种重传触发方式快重传收到 3 个相同的重复 ACK立刻重传丢失报文不等待超时。超时重传兜底方案迟迟收不到足够重复 ACK定时器到期后重传。拥塞控制我们前面学习的确认应答、超时重传、滑动窗口、快重传都是客户端与服务端两端之间的策略解决的是「发送方和接收方」之间的传输问题考虑的是接收端缓冲区满没满流量控制。但报文丢失还有另外一种可能性丢包不是接收端的问题而是中间网络链路、路由器过载导致的。流量控制怕接收方收不下 拥塞控制怕整个网络堵了。拥塞控制能做什么、不能做什么适用场景短暂、临时性的网络拥塞路由器负载过高排队溢出丢包。拥塞控制可以调节发送速率缓解拥堵。无法处理硬件级故障例如大面积断电、路由器硬件损坏、链路彻底断开这类无法快速自愈的严重故障拥塞控制对此无能为力。怎么判断是网络拥塞造成丢包TCP 没有直接的网络探测信号只能通过现象推断 发送大量报文之后丢包数量急剧变多。 举例一次性发出 1000 个报文只丢 2 个大概率是偶然丢包 一次性发出 1000 个报文丢了 998 个几乎全部丢失基本可以判定网络发生拥塞。假设网络已经严重拥堵路由器排队队列已经满了。 如果此时我们还继续大量重传报文只会把更多数据包塞进已经拥堵的网络进一步加重拥塞形成恶性循环。 类比道路已经堵车还不断往里面开进新车只会越堵越严重。网络不是只有 A 进程和 B 进程在通信还有 C、D、E、F 等大量连接同时共享同一条链路。 路由器的带宽、缓存是所有连接共用的资源。拥塞控制是所有 TCP 连接共同遵守的一套 “礼让规则”防止某一条连接把整个网络资源占满。延时应答捎带应答