【Linux网络】深入理解TCP协议:滑动窗口、拥塞控制与性能优化全解析


🔥草莓熊Lotso: 个人主页
❄️个人专栏: 《C++知识分享》 《Linux 入门到实践:零基础也能懂》
✨生活是默默的坚持,毅力是永久的享受!


🎬 博主简介:


文章目录

  • 前言
  • [一. 滑动窗口:TCP 性能的基石](#一. 滑动窗口:TCP 性能的基石)
    • [1.1 为什么需要滑动窗口](#1.1 为什么需要滑动窗口)
    • [1.2 滑动窗口的工作原理](#1.2 滑动窗口的工作原理)
    • [1.3 滑动窗口的核心细节](#1.3 滑动窗口的核心细节)
      • [1.3.1 窗口大小的决定因素](#1.3.1 窗口大小的决定因素)
      • [1.3.2 窗口的动态变化](#1.3.2 窗口的动态变化)
      • [1.3.3 缓冲区的环形抽象](#1.3.3 缓冲区的环形抽象)
    • [1.4 丢包处理:快重传与超时重传](#1.4 丢包处理:快重传与超时重传)
      • [1.4.1 ACK 丢失](#1.4.1 ACK 丢失)
      • [1.4.2 数据包丢失](#1.4.2 数据包丢失)
  • [二. 拥塞控制:网络的 "交通警察"](#二. 拥塞控制:网络的 "交通警察")
    • [2.1 为什么需要拥塞控制](#2.1 为什么需要拥塞控制)
    • [2.2 拥塞控制的核心机制](#2.2 拥塞控制的核心机制)
      • [2.2.1 慢启动 (Slow Start)](#2.2.1 慢启动 (Slow Start))
      • [2.2.2 拥塞避免 (Congestion Avoidance)](#2.2.2 拥塞避免 (Congestion Avoidance))
      • [2.2.3 拥塞发生时的处理](#2.2.3 拥塞发生时的处理)
    • [2.3 拥塞控制的完整过程](#2.3 拥塞控制的完整过程)
  • [三. 延迟应答:让窗口更大一点](#三. 延迟应答:让窗口更大一点)
    • [3.1 延迟应答的原理](#3.1 延迟应答的原理)
    • [3.2 延迟应答的实现机制](#3.2 延迟应答的实现机制)
  • [四. 捎带应答:ACK 搭个顺风车](#四. 捎带应答:ACK 搭个顺风车)
    • [4.1 捎带应答的原理](#4.1 捎带应答的原理)
    • [4.2 捎带应答的优势](#4.2 捎带应答的优势)
  • [五. 面向字节流:TCP 的本质特性](#五. 面向字节流:TCP 的本质特性)
    • [5.1 TCP 缓冲区机制](#5.1 TCP 缓冲区机制)
    • [5.2 全双工通信](#5.2 全双工通信)
    • [5.3 读写不匹配的特性](#5.3 读写不匹配的特性)
  • [六. 粘包问题:面向字节流的 "副作用"](#六. 粘包问题:面向字节流的 "副作用")
    • [6.1 什么是粘包问题](#6.1 什么是粘包问题)
    • [6.2 为什么会出现粘包问题](#6.2 为什么会出现粘包问题)
    • [6.3 如何解决粘包问题](#6.3 如何解决粘包问题)
    • [6.4 UDP 为什么没有粘包问题](#6.4 UDP 为什么没有粘包问题)
  • [七. TCP 异常情况处理](#七. TCP 异常情况处理)
    • [7.1 进程终止](#7.1 进程终止)
    • [7.2 机器重启](#7.2 机器重启)
    • [7.3 机器掉电 / 网线断开](#7.3 机器掉电 / 网线断开)
  • [八. TCP vs UDP:没有最好,只有最合适](#八. TCP vs UDP:没有最好,只有最合适)
  • [九. 经典面试题:用 UDP 实现可靠传输](#九. 经典面试题:用 UDP 实现可靠传输)
  • [十. 经典面试题:HTTP获取网页的完整过程](#十. 经典面试题:HTTP获取网页的完整过程)
    • [10.1 整体流程概览](#10.1 整体流程概览)
    • [10.2 详细步骤拆解](#10.2 详细步骤拆解)
      • [10.2.1 URL 解析](#10.2.1 URL 解析)
      • [10.2.2 DNS 域名解析](#10.2.2 DNS 域名解析)
      • [10.2.3 TCP 连接建立](#10.2.3 TCP 连接建立)
      • [10.2.4 HTTP 请求发送](#10.2.4 HTTP 请求发送)
      • [10.2.5 服务器处理请求](#10.2.5 服务器处理请求)
      • [10.2.6 HTTP 响应接收](#10.2.6 HTTP 响应接收)
      • [10.2.7 TCP 连接关闭](#10.2.7 TCP 连接关闭)
      • [10.2.8 浏览器渲染页面](#10.2.8 浏览器渲染页面)
    • [10.3 面试常考细节](#10.3 面试常考细节)
  • [十一. 源码解读:TCP 头部结构](#十一. 源码解读:TCP 头部结构)
  • 结尾

前言

TCP 作为互联网最核心的传输层协议,其设计哲学可以用一句话概括:在保证数据可靠传输的前提下,尽可能地提高网络传输效率。为了实现这一目标,TCP 引入了一系列复杂而精妙的机制。在上一篇文章中,我们详细讲解了 TCP 的连接管理、确认应答和超时重传等基础可靠性机制。今天,我们将深入探讨 TCP 的性能优化核心 ------ 滑动窗口、拥塞控制,以及在此基础上衍生的延迟应答、捎带应答等高级特性,同时分析 TCP 面向字节流的特性带来的粘包问题及解决方案。本文将严格按照 TCP 协议的设计逻辑展开,从底层原理到实际应用,结合 Linux 内核源码片段进行解读,帮助你彻底掌握 TCP 协议的精髓,从容应对各类技术面试。


一. 滑动窗口:TCP 性能的基石

1.1 为什么需要滑动窗口

在介绍滑动窗口之前,我们先回顾一下最基础的确认应答机制:发送方每发送一个数据段,就必须等待接收方的 ACK 确认,收到确认后才能发送下一个数据段。这种 "一发一收" 的模式虽然简单可靠,但存在一个致命的缺点 ------性能极差,尤其是在网络往返时间 (RTT) 较长的情况下。

举个例子:假设 RTT 为 100ms,每个数据段大小为 1KB,那么这种模式下的传输速率最多只有 10KB/s,这在今天的千兆网络环境下显然是不可接受的。

解决方案:一次发送多个数据段,将多个段的等待时间重叠在一起。这就是滑动窗口机制的核心思想。

1.2 滑动窗口的工作原理

滑动窗口本质上是发送缓冲区中的一个可动态调整的区间,这个区间内的数据可以无需等待 ACK 确认而连续发送。窗口大小指的是无需等待确认应答而可以继续发送数据的最大值。

我们可以将发送缓冲区想象成一个线性的字符数组,通过两个指针 (win_left 和 win_right) 来界定滑动窗口的范围:

  • win_left:指向已发送但未收到确认的第一个字节的序号
  • win_right:指向可以发送的最后一个字节的下一个序号

滑动窗口将发送缓冲区分为三个部分:

  1. 已发送已确认:数据已经被接收方确认,可以从缓冲区中删除
  2. 已发送未确认:数据已经发送,但还没有收到接收方的确认
  3. 待发送:数据还没有发送,但可以在窗口滑动后发送

滑动的本质:当收到接收方的 ACK 确认时,win_left 指针会向右移动到确认序号的位置,同时 win_right 指针也会根据接收方通告的窗口大小相应地向右移动,这样窗口就整体向右滑动了。

1.3 滑动窗口的核心细节

1.3.1 窗口大小的决定因素

滑动窗口的大小不是固定不变的,它由两个因素共同决定:

  • 接收方通告窗口 (ACK Win):接收方在 ACK 报头中携带的自己接收缓冲区的剩余空间大小
  • 拥塞窗口 (Congestion Window):发送方根据网络拥塞情况估算的可以发送的数据量

实际发送窗口大小 = min (接收方通告窗口,拥塞窗口)

这一设计完美地兼顾了接收方的处理能力网络的承载能力,实现了 TCP 的流量控制功能。

1.3.2 窗口的动态变化

滑动窗口的大小可以动态调整:

  • 当接收方应用程序读取数据的速度很快时,接收缓冲区剩余空间变大,ACK Win 增大,滑动窗口也随之变大
  • 当接收方应用程序读取数据的速度很慢时,接收缓冲区逐渐被填满,ACK Win 减小,滑动窗口也随之变小
  • 当接收缓冲区完全被填满时,ACK Win 变为 0,此时发送方将停止发送数据,直到收到接收方的窗口更新通知

1.3.3 缓冲区的环形抽象

如果滑动窗口一直向右滑动,会不会超出线性缓冲区的边界?答案是不会。TCP 内核会将发送缓冲区逻辑上抽象成一个环形空间,通过模运算来处理指针的越界问题。这样,即使窗口一直滑动,也不会出现缓冲区越界的情况。

1.4 丢包处理:快重传与超时重传

滑动窗口机制下的丢包处理是 TCP 可靠性的关键。我们分两种情况讨论:

1.4.1 ACK 丢失

如果数据包已经成功到达接收方,但 ACK 确认报文在传输过程中丢失了,这种情况其实并不可怕。因为 TCP 的确认应答具有累积确认的特性:后续的 ACK 会自动确认之前所有的数据。

例如:发送方发送了 1-1000、1001-2000、2001-3000 三个数据段,前两个 ACK 丢失了,但第三个 ACK (确认序号为 3001) 成功到达,那么发送方就知道前三个数据段都已经被接收方成功接收了。

1.4.2 数据包丢失

如果数据包本身在传输过程中丢失了,TCP 会通过快重传机制来处理:

  1. 接收方收到了后续的数据段,但发现中间有一个数据段丢失了
  2. 接收方会连续发送三个相同的 ACK,确认序号指向丢失数据段的起始序号
  3. 发送方收到三个相同的 ACK 后,会立即重传丢失的数据段,而不需要等待超时定时器到期

快重传的优势:大大减少了重传的等待时间,提高了传输效率。

超时重传的作用:作为兜底机制,当快重传无法触发时 (例如丢失的是最后一个数据段,没有后续的数据段来触发重复 ACK),超时重传会保证数据最终能够被重传。

快重传与超时重传的关系:两者是互补关系,快重传保证了传输效率的上限,超时重传保证了传输可靠性的下限。


二. 拥塞控制:网络的 "交通警察"

2.1 为什么需要拥塞控制

滑动窗口机制解决了接收方处理能力的问题,但没有考虑网络本身的承载能力。如果在网络已经拥塞的情况下,发送方仍然大量发送数据,会导致更多的数据包丢失,进而触发更多的重传,形成恶性循环,最终导致网络瘫痪。

TCP 的拥塞控制机制就是为了解决这个问题而设计的。它的核心思想是:发送方需要动态探测网络的拥塞情况,根据网络的实际承载能力调整发送速率

2.2 拥塞控制的核心机制

TCP 的拥塞控制主要由四个算法组成:慢启动拥塞避免快重传快恢复。我们重点讲解前两个。

2.2.1 慢启动 (Slow Start)

慢启动的 "慢" 并不是指增长速度慢,而是指初始发送速率慢。TCP 连接刚建立时,拥塞窗口 (cwnd) 的初始值为 1 个 MSS (最大报文段大小)。

慢启动的增长规则:

  • 每收到一个 ACK 确认,拥塞窗口加 1
  • 每经过一个 RTT,拥塞窗口翻倍 (指数增长)

这种指数级的增长速度非常快,能够迅速探测网络的可用带宽。但为了避免增长过快导致网络拥塞,TCP 引入了慢启动阈值 (ssthresh)

2.2.2 拥塞避免 (Congestion Avoidance)

当拥塞窗口增长到慢启动阈值时,TCP 进入拥塞避免阶段。在这个阶段,拥塞窗口不再指数增长,而是线性增长:每经过一个 RTT,拥塞窗口增加 1 个 MSS。

这种线性增长的方式能够更加平稳地探测网络的剩余带宽,避免突然发送大量数据导致网络拥塞。

2.2.3 拥塞发生时的处理

当网络发生拥塞 (检测到丢包) 时,TCP 会执行以下操作:

  1. 将慢启动阈值 (ssthresh) 设置为当前拥塞窗口的一半
  2. 将拥塞窗口 (cwnd) 重置为 1 个 MSS
  3. 重新进入慢启动阶段

这个过程被称为乘法减小。通过这种方式,TCP 能够迅速降低发送速率,缓解网络拥塞。

2.3 拥塞控制的完整过程

TCP 拥塞控制的完整过程可以概括为:

  1. 慢启动阶段:cwnd 指数增长,直到达到 ssthresh
  2. 拥塞避免阶段:cwnd 线性增长,直到发生拥塞
  3. 拥塞处理阶段:ssthresh 减半,cwnd 重置为 1,重新进入慢启动

这个过程会不断循环,动态地调整发送速率,以适应网络状态的变化。


三. 延迟应答:让窗口更大一点

3.1 延迟应答的原理

延迟应答是 TCP 为了提高传输效率而设计的另一个重要机制。它的基本思想是:接收方收到数据后,不立即发送 ACK 确认,而是稍微等待一段时间

为什么要这样做呢?我们来看一个例子:

  • 假设接收端缓冲区大小为 1MB
  • 一次收到了 500KB 的数据
  • 如果立刻应答,返回的窗口大小就是 500KB
  • 但如果接收端处理速度很快,10ms 之内就把 500KB 数据从缓冲区消费掉了
  • 如果等待 200ms 再应答,返回的窗口大小就是 1MB

窗口越大,网络吞吐量就越大,传输效率就越高。延迟应答通过等待一段时间,让接收方有更多的时间处理数据,从而能够通告更大的窗口,提高整体传输效率。

3.2 延迟应答的实现机制

当然,并不是所有的数据包都可以无限期地延迟应答。TCP 通过两个限制条件来保证可靠性:

  1. 数量限制:每隔 N 个包就应答一次 (一般 N 取 2)
  2. 时间限制:超过最大延迟时间就应答一次 (一般取 200ms)

这两个条件是 "或" 的关系,只要满足其中一个,接收方就会发送 ACK 确认。


四. 捎带应答:ACK 搭个顺风车

4.1 捎带应答的原理

在延迟应答的基础上,TCP 又引入了捎带应答机制。在很多情况下,客户端和服务器在应用层是 "一发一收" 的模式:客户端给服务器发送一个请求,服务器给客户端返回一个响应。

捎带应答的思想是:将 ACK 确认报文和服务器的响应数据合并在同一个 TCP 报文段中发送。这样可以减少网络中传输的报文数量,进一步提高传输效率。

4.2 捎带应答的优势

  • 减少报文数量:一个报文段既可以携带数据,又可以携带 ACK 确认
  • 降低网络开销:减少了 TCP 报头的重复传输
  • 提高传输效率:避免了单独发送 ACK 报文的额外开销

捎带应答是 TCP 中非常主流的内部策略,在实际应用中被广泛使用。


五. 面向字节流:TCP 的本质特性

5.1 TCP 缓冲区机制

创建一个 TCP socket 时,内核会同时为其创建一个发送缓冲区 和一个接收缓冲区

  • 调用 write () 时,数据会先写入发送缓冲区,然后由内核决定何时发送
  • 如果发送的数据太长,会被拆分成多个 TCP 报文段发出
  • 如果发送的数据太短,会先在缓冲区中等待,直到缓冲区长度合适或者其他合适的时机
  • 接收数据时,数据会先到达内核的接收缓冲区,然后应用程序调用 read () 从缓冲区中读取数据

5.2 全双工通信

由于 TCP 连接同时拥有发送缓冲区和接收缓冲区,因此 TCP 是全双工的:在同一个连接上,双方可以同时发送和接收数据。

5.3 读写不匹配的特性

由于缓冲区的存在,TCP 程序的读和写不需要一一匹配:

  • 写 100 个字节数据时,可以调用一次 write 写 100 个字节,也可以调用 100 次 write,每次写一个字节
  • 读 100 个字节数据时,也完全不需要考虑写的时候是怎么写的,既可以一次 read 100 个字节,也可以一次 read 一个字节,重复 100 次

这种特性就是 TCP面向字节流的本质:TCP 只负责将字节流从一端可靠地传输到另一端,而不关心这些字节流的含义和边界。


六. 粘包问题:面向字节流的 "副作用"

6.1 什么是粘包问题

首先要明确:粘包问题中的 "包",指的是应用层的数据包,而不是 TCP 的报文段。

由于 TCP 是面向字节流的,它没有报文边界的概念。站在应用层的角度,看到的只是一串连续的字节数据,不知道从哪个部分开始到哪个部分是一个完整的应用层数据包。这就是粘包问题。

6.2 为什么会出现粘包问题

粘包问题的根本原因是 TCP 的面向字节流特性:

  • 发送方可能将多个应用层数据包合并成一个 TCP 报文段发送
  • 接收方可能将多个 TCP 报文段的数据合并在一起交给应用层

6.3 如何解决粘包问题

解决粘包问题的核心思想是:在应用层明确两个包之间的边界。常见的解决方案有三种:

  1. 定长包:约定每个应用层数据包的大小是固定的,接收方每次按固定大小读取即可
  2. 长度字段:在应用层数据包的头部添加一个长度字段,指明整个数据包的长度
  3. 特殊分隔符:在包和包之间使用特殊的分隔符,只要保证分隔符不会出现在正文中即可

6.4 UDP 为什么没有粘包问题

UDP 是面向数据报的,每个 UDP 报文段都有明确的长度字段。UDP 会将完整的报文段一次性交付给应用层,要么收到完整的 UDP 报文,要么不收,不会出现 "半个" 报文的情况。因此,UDP 不存在粘包问题。


七. TCP 异常情况处理

TCP 协议考虑了各种可能的异常情况,并提供了相应的处理机制:

7.1 进程终止

进程终止会释放文件描述符,内核会自动发送 FIN 报文,开始四次挥手过程,和正常关闭连接没有区别。

7.2 机器重启

机器重启时,操作系统会自动杀掉所有进程,释放所有文件描述符,同样会自动进行四次挥手,正常关闭连接。

7.3 机器掉电 / 网线断开

这种情况比较特殊,因为掉电的一方没有机会发送 FIN 报文。此时:

  • 接收方认为连接还在
  • 一旦接收方有写入操作,会发现连接已经不存在,发送 RST 报文重置连接
  • 即使没有写入操作,TCP 内置的保活定时器会定期询问对方是否还在,如果对方不在,也会释放连接

保活机制的局限性:TCP 的保活定时器默认是分钟级或小时级的,检测周期很长。因此,在实际应用中,通常会在应用层实现自己的保活机制。


八. TCP vs UDP:没有最好,只有最合适

很多人会问:TCP 是可靠的,是不是 TCP 一定优于 UDP?答案是否定的。TCP 和 UDP 各有优缺点,适用于不同的场景。

特性 TCP UDP
可靠性 可靠,保证数据按序到达 不可靠,可能丢包、乱序
连接性 面向连接,需要三次握手建立连接 无连接,不需要建立连接
传输效率 较低,有额外的开销 较高,没有额外的开销
流量控制 支持 不支持
拥塞控制 支持 不支持
适用场景 文件传输、重要状态更新、HTTP/HTTPS 等 视频传输、语音通话、游戏、广播等

归根结底,TCP 和 UDP 都是程序员的工具,选择哪一个取决于具体的需求场景。


九. 经典面试题:用 UDP 实现可靠传输

这是网络编程中最经典的面试题之一。其实,这个问题的本质是让你解释 TCP 的可靠性机制是如何实现的。

用 UDP 实现可靠传输,就是要在应用层实现 TCP 的核心可靠性机制:

  1. 引入序列号:为每个数据包分配一个唯一的序列号,保证数据按序到达
  2. 引入确认应答:接收方收到数据包后,发送 ACK 确认
  3. 引入超时重传:发送方发送数据包后启动定时器,如果超时没有收到 ACK,就重传数据包
  4. 实现滑动窗口:提高传输效率
  5. 实现拥塞控制:避免网络拥塞

十. 经典面试题:HTTP获取网页的完整过程

这是网络编程领域最高频的面试题之一,没有之一。它综合考察了 DNS 域名系统、TCP 传输控制协议、HTTP 应用层协议以及浏览器渲染原理等多个核心知识点,能够全面反映面试者对计算机网络体系结构的理解程度。

下面我们将从用户在浏览器地址栏输入 URL 并按下回车键的那一刻开始,一步步拆解整个过程的每一个细节。

10.1 整体流程概览

HTTP 获取网页的完整过程可以分为以下 7 个核心步骤:

  1. URL 解析:浏览器解析用户输入的 URL,提取出协议、域名、端口、路径等信息
  2. DNS 域名解析:将域名转换为对应的 IP 地址
  3. TCP 连接建立:与服务器建立 TCP 连接(三次握手)
  4. HTTP 请求发送:向服务器发送 HTTP 请求报文
  5. 服务器处理请求:服务器处理请求并生成 HTTP 响应报文
  6. HTTP 响应接收:客户端接收 HTTP 响应报文
  7. TCP 连接关闭:数据传输完成后关闭 TCP 连接(四次挥手)
  8. 浏览器渲染页面:浏览器解析 HTML、CSS、JavaScript 并渲染页面

10.2 详细步骤拆解

10.2.1 URL 解析

当用户在浏览器中输入http://www.example.com/index.html并按下回车键时,浏览器首先会对这个 URL 进行解析,提取出以下关键信息:

  • 协议http(表示使用 HTTP 协议)
  • 域名www.example.com
  • 端口:默认 80 端口(HTTP 协议默认端口,HTTPS 默认 443 端口)
  • 路径/index.html(请求的资源路径)

如果 URL 中没有指定端口,浏览器会使用对应协议的默认端口。

10.2.2 DNS 域名解析

计算机网络中只能识别 IP 地址,不能直接识别域名。因此,浏览器需要将域名www.example.com转换为对应的 IP 地址,这个过程就是 DNS 域名解析。

DNS 解析采用递归查询 + 迭代查询相结合的方式:

  1. 浏览器缓存查询:浏览器首先检查自己的 DNS 缓存,看是否有该域名对应的 IP 地址
  2. 操作系统缓存查询:如果浏览器缓存中没有,检查操作系统的 DNS 缓存
  3. 本地 DNS 服务器查询:如果操作系统缓存中没有,向本地 DNS 服务器(通常由 ISP 提供)发送递归查询请求
  4. 根 DNS 服务器查询:如果本地 DNS 服务器缓存中没有,向根 DNS 服务器发送迭代查询请求
  5. 顶级域 DNS 服务器查询:根 DNS 服务器返回顶级域 DNS 服务器(.com 服务器)的地址
  6. 权威 DNS 服务器查询 :顶级域 DNS 服务器返回example.com的权威 DNS 服务器地址
  7. 获取 IP 地址 :权威 DNS 服务器返回www.example.com对应的 IP 地址

整个 DNS 解析过程是 UDP 协议的典型应用场景。

10.2.3 TCP 连接建立

获取到服务器的 IP 地址后,浏览器会与服务器的 80 端口建立 TCP 连接。这个过程就是我们之前详细讲解过的TCP 三次握手

  1. 客户端向服务器发送 SYN 报文(同步报文段),请求建立连接
  2. 服务器收到 SYN 报文后,向客户端发送 SYN+ACK 报文,确认客户端的连接请求,并同时请求与客户端建立连接
  3. 客户端收到 SYN+ACK 报文后,向服务器发送 ACK 报文,确认服务器的连接请求

三次握手完成后,TCP 连接正式建立,双方可以开始传输数据。

注意 :在 HTTP/1.1 中,默认开启了长连接 (Keep-Alive),多个 HTTP 请求可以复用同一个 TCP 连接,避免了频繁建立和关闭 TCP 连接的开销。

10.2.4 HTTP 请求发送

TCP 连接建立后,浏览器会向服务器发送 HTTP 请求报文。一个完整的 HTTP 请求报文由以下四个部分组成:

  1. 请求行 :包含请求方法、请求路径和 HTTP 版本
    1.

    Plain 复制代码
    GET /index.html HTTP/1.1
  2. 请求头 :包含一系列键值对,向服务器传递客户端的信息
    1.

    Plain 复制代码
    Host: www.example.com
    User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36
    Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
    Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3
    Accept-Encoding: gzip, deflate
    Connection: keep-alive
  3. 空行:用于分隔请求头和请求体

  4. 请求体:POST 请求会携带请求体,GET 请求没有请求体

在这个过程中,TCP 的滑动窗口确认应答超时重传等机制会保证 HTTP 请求报文可靠、高效地传输到服务器。

10.2.5 服务器处理请求

服务器收到 HTTP 请求报文后,会进行以下处理:

  1. 解析请求报文:提取请求方法、请求路径、请求头等信息
  2. 路由匹配:根据请求路径匹配对应的处理程序
  3. 处理请求:执行相应的业务逻辑,可能会查询数据库、读取文件等
  4. 生成 HTTP 响应报文:将处理结果封装成 HTTP 响应报文

一个完整的 HTTP 响应报文也由四个部分组成:

  1. 状态行 :包含 HTTP 版本、状态码和状态描述
    1.

    Plain 复制代码
    HTTP/1.1 200 OK
  2. 响应头 :包含一系列键值对,向客户端传递服务器的信息
    1.

    Plain 复制代码
    Server: nginx/1.14.0
    Date: Sat, 30 May 2026 12:00:00 GMT
    Content-Type: text/html; charset=utf-8
    Content-Length: 1234
    Connection: keep-alive
  3. 空行:用于分隔响应头和响应体

  4. 响应体 :包含请求的资源内容,这里就是index.html的 HTML 代码

10.2.6 HTTP 响应接收

客户端收到 HTTP 响应报文后,会进行以下处理:

  1. 解析响应报文:提取状态码、响应头和响应体
  2. 状态码判断 :根据状态码判断请求是否成功
    1. 200:请求成功
    2. 301/302:重定向
    3. 404:资源不存在
    4. 500:服务器内部错误
  3. 处理响应体:如果请求成功,将响应体中的 HTML 代码交给浏览器渲染引擎

同样,TCP 的各种可靠性机制会保证 HTTP 响应报文可靠地传输到客户端。

10.2.7 TCP 连接关闭

数据传输完成后,TCP 连接需要关闭。这个过程就是我们之前讲解过的TCP 四次挥手

  1. 客户端向服务器发送 FIN 报文,请求关闭连接
  2. 服务器收到 FIN 报文后,向客户端发送 ACK 报文,确认客户端的关闭请求
  3. 服务器发送完所有数据后,向客户端发送 FIN 报文,请求关闭连接
  4. 客户端收到 FIN 报文后,向服务器发送 ACK 报文,确认服务器的关闭请求

四次挥手完成后,TCP 连接正式关闭。

注意:如果使用了 HTTP 长连接,TCP 连接不会立即关闭,而是会保持一段时间,等待后续的 HTTP 请求复用这个连接。

10.2.8 浏览器渲染页面

浏览器收到 HTML 代码后,会开始渲染页面:

  1. HTML 解析:将 HTML 代码解析成 DOM 树
  2. CSS 解析:将 CSS 代码解析成 CSSOM 树
  3. 渲染树构建:将 DOM 树和 CSSOM 树结合,构建成渲染树
  4. 布局:计算渲染树中每个节点的位置和大小
  5. 绘制:将渲染树中的节点绘制到屏幕上
  6. JavaScript 执行:如果页面中有 JavaScript 代码,会在适当的时机执行

如果页面中还有图片、CSS、JavaScript 等外部资源,浏览器会再次发起 HTTP 请求获取这些资源,这个过程和上面的步骤类似。

10.3 面试常考细节

  1. DNS 解析为什么使用 UDP 协议?
    1. UDP 协议开销小,速度快
    2. DNS 查询通常只需要一个请求和一个响应,不需要建立连接
    3. 如果 DNS 查询失败,可以通过超时重传机制来保证可靠性
  2. TCP 三次握手为什么不能改成两次?
    1. 防止已失效的连接请求报文段突然又传送到了服务器,导致服务器错误地建立连接
    2. 保证双方都知道对方的接收和发送能力正常
  3. TCP 四次挥手为什么不能改成三次?
    1. 因为 TCP 是全双工的,双方都需要独立地关闭连接
    2. 服务器收到客户端的 FIN 报文后,可能还有数据没有发送完,不能立即关闭连接
  4. HTTP 长连接和短连接的区别?
    1. 短连接:每次 HTTP 请求都需要建立一个新的 TCP 连接,请求完成后立即关闭连接
    2. 长连接:多个 HTTP 请求可以复用同一个 TCP 连接,减少了建立和关闭连接的开销
  5. HTTPS 和 HTTP 的区别?
    1. HTTPS 在 HTTP 和 TCP 之间加入了 SSL/TLS 层,提供了数据加密、身份认证和数据完整性保护
    2. HTTPS 默认使用 443 端口,HTTP 默认使用 80 端口
    3. HTTPS 需要申请 SSL 证书,HTTP 不需要

总结

HTTP 获取网页的过程是一个非常复杂但又非常基础的过程,它涉及到计算机网络体系结构的各个层次。理解这个过程,不仅能够帮助你深入理解 TCP 和 HTTP 协议的工作原理,还能够帮助你在实际开发中排查网络问题,优化网站性能。在面试中,面试官不仅会问整个流程,还会针对某个具体的步骤进行深入提问,比如 TCP 三次握手的细节、DNS 解析的过程、HTTP 请求和响应报文的结构等。因此,你需要对每个步骤都有深入的理解。


十一. 源码解读:TCP 头部结构

我们来看一下 Linux 内核中 TCP 头部的定义 (位于include/linux/tcp.h):

c 复制代码
struct tcphdr {
    __be16 source;    // 源端口号
    __be16 dest;      // 目的端口号
    __be32 seq;       // 32位序号
    __be32 ack_seq;   // 32位确认序号
#if defined(__LITTLE_ENDIAN_BITFIELD)
    __u16 res1:4,     // 保留位
          doff:4,     // TCP头部长度(以32位字为单位)
          fin:1,      // FIN标志位
          syn:1,      // SYN标志位
          rst:1,      // RST标志位
          psh:1,      // PSH标志位
          ack:1,      // ACK标志位
          urg:1,      // URG标志位
          ece:1,      // ECE标志位
          cwr:1;      // CWR标志位
#elif defined(__BIG_ENDIAN_BITFIELD)
    __u16 doff:4,
          res1:4,
          cwr:1,
          ece:1,
          urg:1,
          ack:1,
          psh:1,
          rst:1,
          syn:1,
          fin:1;
#else
#error "Adjust your <asm/byteorder.h> defines"
#endif
    __be16 window;    // 16位窗口大小
    __sum16 check;    // 16位校验和
    __be16 urg_ptr;   // 16位紧急指针
};

关键字段解读

  • doff:TCP 头部长度,以 32 位字 (4 字节) 为单位。没有选项的 TCP 头部长度是 20 字节 (doff=5),最大长度是 60 字节 (doff=15)
  • 标志位:
    • ACK:确认号是否有效
    • SYN:请求建立连接
    • FIN:通知对方本端要关闭连接
    • RST:对方要求重新建立连接
    • PSH:提示接收端应用程序立刻从 TCP 缓冲区把数据读走
    • URG:紧急指针是否有效
  • window:接收方通告的窗口大小,用于流量控制

核心考点总结

  1. 滑动窗口的工作原理和窗口大小的决定因素
  2. 快重传和超时重传的区别与联系
  3. 拥塞控制的四个阶段和核心算法
  4. 延迟应答和捎带应答的原理和优势
  5. 粘包问题的产生原因和解决方案
  6. TCP 异常情况的处理机制
  7. TCP 与 UDP 的区别和适用场景
  8. 用 UDP 实现可靠传输的基本思路

结尾

html 复制代码
🍓 我是草莓熊 Lotso!若这篇技术干货帮你打通了学习中的卡点:
👀 【关注】跟我一起深耕技术领域,从基础到进阶,见证每一次成长
❤️ 【点赞】让优质内容被更多人看见,让知识传递更有力量
⭐ 【收藏】把核心知识点、实战技巧存好,需要时直接查、随时用
💬 【评论】分享你的经验或疑问(比如曾踩过的技术坑?),一起交流避坑
🗳️ 【投票】用你的选择助力社区内容方向,告诉大家哪个技术点最该重点拆解
技术之路难免有困惑,但同行的人会让前进更有方向~愿我们都能在自己专注的领域里,一步步靠近心中的技术目标!

结语:TCP 协议之所以复杂,是因为它要在不可靠的网络层之上,提供一个可靠、高效、有序的字节流传输服务。本文详细讲解了 TCP 的滑动窗口、拥塞控制、延迟应答、捎带应答等核心机制,分析了面向字节流特性带来的粘包问题及解决方案,同时介绍了 TCP 的异常处理机制和 TCP 与 UDP 的对比。掌握这些知识点,不仅能够让你深入理解 TCP 协议的工作原理,还能帮助你在网络编程中写出更加高效、稳定的代码,同时从容应对各类技术面试。

✨把这些内容吃透超牛的!放松下吧✨ ʕ˘ᴥ˘ʔ づきらど