【Linux网络】Linux 网络编程:传输层协议TCP(五)
个人主页艾莉丝努力练剑❄专栏传送门《C语言》《数据结构与算法》《C/C干货分享学习过程记录》《Linux操作系统编程详解》《笔试/面试常见算法从基础到进阶》《Python干货分享》⭐️为天地立心为生民立命为往圣继绝学为万世开太平 艾莉丝的简介文章目录思维导图导入语1 ~ 滑动窗口机制1.1 滑动窗口的基本概念1.2 窗口大小协商与滑动原理1.3 滑动窗口的变化特性与越界处理1.4 丢包问题与重传机制2 ~ 拥塞控制2.1 拥塞控制的基本概念与核心判断2.2 拥塞窗口与慢启动机制2.3 拥塞避免与拥塞处理3 ~ TCP 性能优化机制3.1 延迟应答3.2 捎带应答4 ~ 面向字节流特性与粘包问题4.1 面向字节流的本质4.2 粘包问题4.3 粘包问题的解决方案5 ~ TCP 异常情况处理5.1 进程终止5.2 机器重启5.3 机器掉电 / 网线断开5.4 RST 标志位6 ~ TCP 报头核心字段与选项6.1 固定报头核心字段6.2 常用 TCP 选项7 ~ TCP 与 UDP 对比7.1 经典面试题用 UDP 实现可靠传输7.2 还有一道面试题HTTP 获取网页的完整过程8 ~ TCP 核心机制总览与总结8.1 可靠性机制8.2 性能优化机制8.3 辅助机制本文总结结尾思维导图导入语TCP 协议是互联网传输层的基石它在 IP 协议提供的不可靠数据报服务之上构建了一套完整的可靠端到端传输体系。从浏览器打开网页到微信发送消息从文件下载到在线支付几乎所有的互联网应用都依赖于 TCP 协议的可靠传输能力。TCP 协议的复杂性源于它的双重目标既要保证数据传输的绝对可靠又要在复杂多变的网络环境下尽可能提高传输效率。为了实现这两个目标TCP 的设计者们引入了数十种精妙的机制这些机制相互配合共同构成了 TCP 协议的完整体系。本文基于 TCP 协议的核心教学内容全面系统地整理了滑动窗口、拥塞控制、性能优化、面向字节流特性、异常处理等所有关键知识点。每一个机制都从 “是什么、为什么、怎么做” 三个维度进行深入讲解同时保留了大量便于理解的实例和面试高频考点。无论是课程复习、面试准备还是网络编程实践本文都能为你提供最全面、最准确的参考。1 ~ 滑动窗口机制1.1 滑动窗口的基本概念滑动窗口是 TCP 协议实现流量控制和批量数据传输的核心机制。它本质上是发送缓冲区中的一个可变大小的发送区间这个区间内的数据可以连续发送而不需要等待每一个报文的单独应答从而将 TCP 从 “停等协议” 的低效模式中解放出来大幅提升了网络吞吐量。发送缓冲区被滑动窗口逻辑上划分为三个连续的区域已发送已确认区位于窗口左侧数据已经被接收方成功确认可以从缓冲区中安全丢弃滑动窗口区位于中间数据已经发送但尚未收到确认或者可以立即发送待发送区位于窗口右侧数据还不能发送需要等待窗口滑动后才能进入发送队列滑动窗口的实际大小由三个因素共同决定完整的核心公式为滑动窗口大小 min(接收方通告的ACK窗口大小, 拥塞窗口大小, 本地待发送数据量)1.2 窗口大小协商与滑动原理TCP 连接建立的三次握手阶段双方会在 SYN 报文中交换各自的初始窗口大小。因此当第一次发送应用数据时发送方已经知道了接收方的初始接收能力不需要再进行额外的探测。滑动窗口的滑动通过两个逻辑指针精确控制win_left窗口左边界严格等于接收方返回的最新确认序号 (ACK Seq)。确认序号的含义是 “该序号之前的所有字节都已经被成功接收”。win_right窗口右边界等于win_left 当前窗口大小。当发送方收到一个确认序号为 2001 的 ACK 时win_left会立即移动到 2001 的位置同时win_right会根据新的窗口大小重新计算。整个窗口向右滑动将已确认的数据移出窗口同时将新的待发送数据纳入窗口。1.3 滑动窗口的变化特性与越界处理滑动窗口具有以下不可违背的特性只能整体向右滑动由于确认序号是严格单调递增的win_left永远不会向左移动因此滑动窗口整体只能向右滑动不可能回退。大小动态可变变大当接收方应用层快速消费数据接收缓冲区剩余空间增加时ACK 窗口会变大滑动窗口随之扩大变小当接收方应用层处理速度跟不上数据到达速度接收缓冲区剩余空间减少时ACK 窗口会变小滑动窗口随之缩小不变当发送方发送的数据量恰好等于接收方应用层消费的数据量时ACK 窗口大小保持不变窗口整体右移但大小不变为 0当接收方应用层完全停止读取数据接收缓冲区被填满时ACK 窗口变为 0发送方必须停止发送数据只能定期发送窗口探测报文线性空间越界处理由于 TCP 序号是 32 位的循环计数器发送缓冲区在逻辑上被抽象为一个环形空间。通过模运算和算法映射解决了滑动窗口一直向右滑动导致的线性空间越界问题。1.4 丢包问题与重传机制在滑动窗口机制下所有的丢包问题最终都可以归结为 **“左侧丢失”** 这一种本质情况。无论丢失的是窗口最左侧、中间还是最右侧的报文接收方都只能返回确认序号为窗口最左侧未收到报文起始序号的 ACK。例如如果 1001-2000 的报文丢失了即使接收方已经成功收到了 2001-7000 的所有后续报文它也只能连续返回确认序号为 1001 的 ACK。这是 TCP 协议最天才的设计之一它用最简单的逻辑解决了复杂的乱序和丢包问题。TCP 提供了两种互补的重传机制快重传机制当发送方连续收到 3 个相同的确认序号时立即重传对应的数据段。这种机制不需要等待超时定时器触发能够在几十毫秒内恢复丢失的报文是 TCP 高性能的关键。超时重传机制当发送方在超时时间内没有收到对应报文的 ACK 时触发超时重传。超时时间会根据 RTT (往返时间) 动态调整。这种机制是 TCP 可靠性的最后一道防线确保即使在网络极端恶劣的情况下数据也能最终到达。快重传和超时重传是完美的互补关系快重传保证了传输的性能上限能够快速处理 99% 以上的丢包情况超时重传保证了传输的可靠性下限能够处理所有可能的极端情况。2 ~ 拥塞控制2.1 拥塞控制的基本概念与核心判断滑动窗口机制完美解决了发送方和接收方之间的流量控制问题但它没有考虑网络中间路径的承载能力。在实际的互联网中丢包不一定是因为接收方来不及接收更常见的原因是网络中间的路由器因为报文积压过多而主动丢弃报文这种情况就是网络拥塞。TCP 拥塞控制的核心思想是网络是所有主机共享的公共资源当网络出现拥塞时所有主机都必须主动降低发送速率避免拥塞进一步恶化。如果所有主机在拥塞时都继续重传数据只会形成 “拥塞→重传→更严重拥塞” 的恶性循环最终导致整个网络瘫痪。TCP 通过丢包率来判断网络是否发生拥塞少量丢包 (1%~1.5%)属于网络正常波动只需要重传丢失的报文即可大量丢包 (≥3%)判定为网络拥塞必须立即降低发送速率2.2 拥塞窗口与慢启动机制为了实现拥塞控制TCP 引入了 ** 拥塞窗口 (cwnd)** 的概念。拥塞窗口是发送方维护的一个整型变量它表示在当前网络状态下发送方最多可以连续发送的数据量。TCP 采用慢启动机制来安全地探测网络的承载能力连接建立时拥塞窗口的初始值为 1 个 MSS (最大段大小通常为 1460 字节)每收到一个 ACK 应答拥塞窗口加 1 个 MSS这样在每个 RTT (往返时间) 内拥塞窗口就会翻倍呈现2^n 的指数级增长慢启动机制的设计极具智慧前期慢刚开始只发送极少量的数据探路避免在不清楚网络状态的情况下突然发送大量数据导致拥塞后期快一旦确认网络没有拥塞就以指数级速度快速增大发送量尽快利用网络的全部带宽2.3 拥塞避免与拥塞处理为了防止拥塞窗口无限指数增长导致网络拥塞TCP 引入了慢启动阈值 (ssthresh)的概念。当拥塞窗口的大小超过慢启动阈值时就不再按照指数方式增长而是切换为线性增长模式每个 RTT 只增加 1 个 MSS这个阶段叫做拥塞避免。当网络发生拥塞 (触发超时重传) 时TCP 会执行以下拥塞处理操作将慢启动阈值 (ssthresh) 设置为当前拥塞窗口大小的一半将拥塞窗口 (cwnd) 重置为 1 个 MSS重新进入慢启动阶段拥塞控制的完整生命周期可以总结为指数增长(慢启动) → 线性增长(拥塞避免) → 乘法减小(网络拥塞) → 指数增长(慢启动)这是一个永不停歇的动态探测过程。理论上如果网络永远不发生拥塞拥塞窗口会一直增长直到达到发送方网卡的物理带宽极限或者接收方的最大窗口大小。但在实际网络中拥塞总是会周期性地发生拥塞窗口也会随之周期性地升降。3 ~ TCP 性能优化机制3.1 延迟应答延迟应答是 TCP 为了最大化网络吞吐量而设计的基础优化机制。如果接收方收到数据后立即返回 ACK那么返回的窗口大小只能反映当前接收缓冲区的剩余空间。但如果接收方稍微等待一会儿应用层可能已经消费了部分数据接收缓冲区的剩余空间会更大此时返回的窗口大小也会更大。例如接收缓冲区总大小为 1M一次收到了 500K 的数据如果立即应答返回的窗口大小为 500K如果等待 200ms应用层已经消费了这 500K 数据返回的窗口大小为 1M窗口越大网络吞吐量就越大传输效率就越高。延迟应答的目标就是在保证网络不拥塞的情况下尽可能增大接收窗口。为了避免过度延迟导致发送方超时重传TCP 设置了两个严格的限制条件数量限制每隔 N 个数据包就必须应答一次主流操作系统中 N2时间限制超过最大延迟时间就必须应答一次主流操作系统中最大延迟为 200ms3.2 捎带应答捎带应答是在延迟应答基础上的进一步优化它充分利用了 TCP 全双工通信的特性。在大多数客户端 - 服务器交互场景中通信模式是 “一发一收” 的客户端发送一个请求服务器处理后返回一个响应。在这种模式下服务器不需要单独发送一个 ACK 报文来确认客户端的请求而是可以将 ACK 标志位和确认序号捎带在服务器的响应数据报文中一起发送给客户端。这样就减少了网络中一半的独立 ACK 报文数量显著降低了网络开销。延迟应答和捎带应答是 TCP 最核心的两个性能优化机制它们的组合使用能够将 TCP 的传输效率提升数倍。4 ~ 面向字节流特性与粘包问题4.1 面向字节流的本质TCP 是面向字节流的协议这是 TCP 与 UDP 最本质的区别之一。面向字节流的核心含义是TCP 只负责将字节流从一端可靠地传输到另一端不维护任何应用层的数据边界。当创建一个 TCP socket 时Linux 内核会为该 socket 同时分配一个发送缓冲区和一个接收缓冲区调用write()系统调用时数据只是从用户空间拷贝到内核的发送缓冲区然后立即返回。数据什么时候发送、发送多少完全由内核的 TCP 协议栈决定。接收数据时数据首先从网卡驱动程序拷贝到内核的接收缓冲区然后应用程序通过read()系统调用从接收缓冲区读取数据。由于内核缓冲区的存在TCP 程序的读操作和写操作是完全异步的读写次数不需要一一匹配你可以调用 1 次write()发送 100 个字节也可以调用 100 次write()每次发送 1 个字节对方可以调用 1 次read()读取 100 个字节也可以调用 100 次read()每次读取 1 个字节这种特性就是面向字节流的本质。除了 TCP 之外Unix 管道、C 语言的标准输入输出 (stdin/stdout) 也都是面向字节流的设计。需要特别注意的是UDP 协议只有发送缓冲区没有真正意义上的接收缓冲区。UDP 收到数据后如果应用层没有及时读取数据会被直接丢弃。4.2 粘包问题面向字节流的特性带来了一个不可避免的问题粘包问题。这里的 “包” 特指应用层的数据包而不是 TCP 的报文段。站在传输层的角度TCP 确实是一个一个报文段传输的每个报文段都有自己的序号。但站在应用层的角度看到的只是一串连续的、没有任何边界的字节流。应用程序无法知道从哪个字节开始到哪个字节结束是一个完整的应用层数据包。例如应用层连续发送了两个数据包“Hello” 和 “World”TCP 可能将它们合并成一个 TCP 报文段发送“HelloWorld”也可能将它们拆分成两个 TCP 报文段发送“Hel” 和 “loWorld”还可能拆分成更多的 TCP 报文段发送无论 TCP 如何拆分和合并应用层收到的都只是一串连续的字节流无法区分原来的两个数据包边界。UDP 协议不存在粘包问题。因为 UDP 报头中有一个 16 位的长度字段明确标识了每个 UDP 报文的数据长度。UDP 是一个一个完整地将报文交付给应用层的要么收到完整的 UDP 报文要么收不到永远不会出现 “半个” 报文的情况。4.3 粘包问题的解决方案解决粘包问题的核心思想非常简单在应用层明确约定数据包的边界。TCP 协议本身不提供这个功能必须由应用层协议自己实现。主流的解决方案有三种定长包约定所有应用层数据包的长度都是固定的。例如约定每个数据包都是 128 字节。接收方每次从缓冲区中读取 128 字节就是一个完整的数据包。这种方式最简单但灵活性最差会浪费大量带宽。包头长度字段在每个数据包的开头用固定长度的字段 (通常是 2 字节或 4 字节) 表示整个数据包的总长度。接收方先读取长度字段然后根据长度读取后续的数据包内容。这是最常用、最高效的解决方案HTTP 协议的Content-Length字段就是这种方式的典型应用。特殊分隔符在数据包之间插入一个特殊的分隔符这个分隔符不会出现在数据包的正文中。接收方通过查找分隔符来区分不同的数据包。例如FTP 协议使用回车换行符 (\r\n) 作为命令的分隔符。这种方式实现简单但需要扫描整个字节流查找分隔符效率较低。5 ~ TCP 异常情况处理TCP 协议提供了完善的异常处理机制能够应对各种可能的网络和系统异常5.1 进程终止当通信的一方进程正常终止或异常崩溃时操作系统会回收该进程的所有文件描述符。当 TCP 连接对应的文件描述符被释放时内核会自动向对方发送一个 FIN 报文然后进行正常的四次挥手过程关闭连接。这种情况和应用层主动调用close()关闭连接没有任何区别属于正常的连接关闭流程。5.2 机器重启当机器正常重启时操作系统会在关机前依次终止所有正在运行的进程。和进程终止的情况一样每个 TCP 连接都会收到 FIN 报文进行正常的四次挥手关闭。这就是为什么当你打开很多网络应用时关机速度会变慢的原因之一操作系统需要花费时间关闭所有的 TCP 连接。5.3 机器掉电 / 网线断开当机器突然掉电或者网线被意外断开时操作系统没有任何机会发送 FIN 报文给对方。此时对方主机完全不知道连接已经断开会一直认为连接处于正常状态。TCP 提供了两种机制来处理这种半开连接问题写入操作触发 RST如果对方主机尝试向半开连接写入数据会收到 ICMP 不可达报文内核会立即发送 RST 报文重置连接。TCP 保活机制TCP 内置了一个保活定时器当连接空闲一段时间后会自动向对方发送一个不携带任何数据的探测报文。如果连续多次探测都没有收到响应内核会认为对方已经不可达然后发送 RST 报文重置连接。需要特别注意的是TCP 的保活机制在实际应用中很少使用。因为它的默认超时时间太长 (通常是 2 小时)无法满足大多数应用的实时性要求。在实际工程中几乎所有的应用都会在应用层实现自己的保活机制例如 HTTP 长连接的心跳机制、QQ 的断线重连机制、王者荣耀的挂机检测机制等。5.4 RST 标志位RST 是 TCP 报头中的一个重要标志位它的作用是强制重置连接。当 TCP 收到一个 RST 报文时会立即释放连接的所有资源不需要进行四次挥手过程。RST 报文通常在以下情况下发送连接请求到达一个不存在的端口向已经关闭的连接发送数据检测到半开连接应用层需要强制关闭连接6 ~ TCP 报头核心字段与选项TCP 报头分为固定部分和可变选项部分固定部分长度为 20 字节选项部分最长为 40 字节因此 TCP 报头的最大长度为 60 字节。6.1 固定报头核心字段16 位源端口号 / 16 位目的端口号标识数据从哪个进程来到哪个进程去32 位序号本报文段所携带数据的第一个字节的序号32 位确认号期望收到对方下一个报文段的第一个字节的序号确认号之前的所有字节都已经被成功接收4 位首部长度表示 TCP 报头有多少个 32 位字因此最大报头长度为 15×460 字节6 位标志位URG紧急指针是否有效ACK确认号是否有效PSH接收方应立即将数据交付给应用层RST强制重置连接SYN同步序号用于建立连接FIN释放连接16 位窗口大小接收方通告的接收窗口大小用于流量控制16 位检验和检测 TCP 报文段在传输过程中是否发生错误16 位紧急指针当 URG 标志位有效时标识紧急数据的结束位置6.2 常用 TCP 选项TCP 报头的选项部分用于提供各种扩展功能以下是最常用的 TCP 选项类型选项名称功能简介关键说明0EOL (选项列表结束)标识选项列表的结束单字节选项用于填充选项末尾1NOP (无操作)选项之间的填充符单字节用于 32 位边界对齐2MSS (最大段大小)通告本端能接收的最大 TCP 数据段大小仅在 SYN 包中发送防止 IP 分片3Window Scale (窗口扩大因子)将 16 位窗口大小左移指定位数仅在 SYN 包中协商最大支持 1GB 窗口4SACK Permitted (允许选择性确认)告知对端本端支持 SACK 机制仅在 SYN 包中协商5SACK (选择性确认)告知发送方已收到的非连续数据块列表支持只重传丢失的报文而非全部8Timestamp (时间戳)携带发送方时间戳和回显时间戳用于精确计算 RTT、防止序号回绕 (PAWS)14User Timeout (用户超时)指示本端等待 ACK 的最大时间超时则自动断开连接参见 RFC 548219/20/29TCP-AO MD5提供数据完整性校验和防伪造保护MD5 为 RFC 2385TCP-AO 为替代方案 RFC 592530MPTCP (多路径 TCP)允许单条连接同时利用多条网络路径提升带宽利用率与连接冗余性其中时间戳选项是现代 TCP 最重要的选项之一。它不仅可以用于精确计算 RTT从而动态调整超时重传时间还可以解决 32 位序号回绕问题 (PAWS)。当序号增长到最大值后回绕到 0 时通过比较时间戳可以轻松区分是新的报文还是旧的重复报文。7 ~ TCP 与 UDP 对比TCP 和 UDP 是传输层最重要的两个协议它们分别代表了两种完全不同的设计哲学特性TCPUDP连接性面向连接需要三次握手建立连接无连接不需要建立连接可靠性可靠保证数据按序到达、不丢失、不重复不可靠不保证数据一定到达不保证顺序开销大固定报头 20 字节最多 60 字节小固定报头 8 字节流量控制有滑动窗口机制无拥塞控制有慢启动、拥塞避免等机制无传输模式面向字节流无数据边界面向数据报有明确数据边界双工性全双工同一连接同时收发全双工适用场景文件传输、HTTP/HTTPS、邮件、支付等对可靠性要求高的场景视频直播、在线游戏、实时通信、DNS、广播等对性能要求高的场景需要特别强调的是不能简单地说 TCP 优于 UDP 或者 UDP 优于 TCP。它们是为不同的应用场景设计的不同工具。TCP 将复杂性隐藏在协议栈内部为应用层提供了简单可靠的传输服务UDP 则将复杂性交给了应用层为应用层提供了最大的灵活性和最高的性能。7.1 经典面试题用 UDP 实现可靠传输这是网络编程岗位最经典的面试题之一其实质是考察对 TCP 可靠性机制的理解程度。要在 UDP 之上实现可靠传输本质上就是在应用层重新实现 TCP 的核心可靠性机制引入序列号机制为每个 UDP 数据包分配一个唯一的、连续的序列号。接收方通过序列号判断数据是否重复、是否乱序并将乱序的数据重新排序。引入确认应答机制接收方收到一个数据包后向发送方返回一个包含该数据包序列号的 ACK 确认报文。引入超时重传机制发送方发送一个数据包后启动一个定时器。如果在超时时间内没有收到对应的 ACK 确认报文就重传该数据包。可选扩展可以进一步实现滑动窗口流量控制、拥塞控制、选择性确认 (SACK) 等机制从而在 UDP 之上实现一个完整的、高性能的可靠传输协议。7.2 还有一道面试题HTTP 获取网页的完整过程这道面试题艾莉丝单独整理了一篇博客。8 ~ TCP 核心机制总览与总结TCP 协议之所以能够成为互联网的基石是因为它在可靠性和性能之间找到了一个完美的平衡点。我们可以将 TCP 的所有机制分为三大类8.1 可靠性机制这是 TCP 协议的核心它保证了数据能够在不可靠的网络环境下可靠地传输校验和检测数据在传输过程中的比特错误序列号保证数据按序到达处理重复报文确认应答确保对端已经收到了数据超时重传丢失的数据会被自动重传连接管理通过三次握手建立可靠连接通过四次挥手安全释放连接流量控制防止发送方发送过快导致接收方缓冲区溢出拥塞控制防止发送方发送过快导致网络拥塞8.2 性能优化机制在保证可靠性的前提下TCP 引入了大量机制来尽可能提高传输效率滑动窗口实现批量数据传输将 TCP 从停等协议的低效模式中解放出来快速重传不需要等待超时快速恢复丢失的报文延迟应答尽可能增大接收窗口提高网络吞吐量捎带应答将 ACK 与反向数据合并发送减少网络报文数量8.3 辅助机制各类定时器超时重传定时器、保活定时器、TIME_WAIT 定时器等报头扩展选项提供 MSS、窗口扩大、选择性确认、时间戳等各种扩展功能本文总结TCP 协议是计算机网络领域最伟大的发明之一。它用软件的方法解决了硬件网络不可靠的问题为上层应用提供了一个简单、可靠、高效的端到端传输服务。TCP 协议的设计充满了智慧它的很多思想和方法如滑动窗口、拥塞控制、超时重传等已经被广泛应用于其他分布式系统的设计中。深入理解 TCP 协议不仅是学习计算机网络的必经之路也是成为一名优秀后端工程师的必备技能。希望本文能够帮助你全面、系统地掌握 TCP 协议的核心知识点为你的学习和工作提供有力的支持。结尾uu们本文的内容到这里就全部结束了艾莉丝在这里再次感谢您的阅读艾莉丝努力练剑C/C Linux 底层探索者 | 一个正在努力练剑的技术博主【关注】跟随我一起深耕技术领域见证每一次成长。❤️【点赞】让优质内容被更多人看见让知识传递更有力量。⭐【收藏】把核心知识点存好在需要时随时查、随时用。【评论】分享你的经验或疑问评论区一起交流避坑不要忘记给博主“一键四连”哦“今日练剑达成”“技术之路难免有困惑但同行的人会让前进更有方向。”结语希望对学习Linux相关内容的uu有所帮助不要忘记给博主“一键四连”哦往期回顾【Linux网络】Linux 网络编程传输层TCP四博主在这里放了一只小狗大家看完了摸摸小狗放松一下吧૮₍ ˶ ˊ ᴥ ˋ˶₎ა