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

🔥个人主页:Cx330🌸

❄️个人专栏:《C语言》《LeetCode刷题集》《数据结构-初阶》《C++知识分享》

《优选算法指南-必刷经典100题》《Linux操作系统》:从入门到入魔

《Git深度解析》:版本管理实战全解 《Qt 极境架构》MySQL 核心技术与实战

🌟心向往之行必能


🎥Cx330🌸的简介:


目录

前言

[一. 滑动窗口](#一. 滑动窗口)

[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 延迟应答的实现机制)

[四. 捎带应答](#四. 捎带应答)

[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实现可靠传输?

[十. 经典面试题: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,Transmission Control Protocol)无疑是支撑整个互联网运行的基石之一。无论是网页浏览、文件传输,还是 API 调用,TCP 都在底层默默地保障着数据的可靠传输。

然而,TCP 的"可靠性"并不是凭空出现的,其背后包含了极其复杂的机制设计------从保证吞吐量的滑动窗口,到避免网络崩溃的拥塞控制,再到面向字节流的特性与粘包处理。本文将全面梳理 TCP 的核心机制、异常处理逻辑以及高频面试考点,带你一站式理清 TCP 协议的全貌。


一. 滑动窗口

1.1 为什么需要滑动窗口

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

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

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

1.2 滑动窗口的工作原理

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

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

  • 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 确认。


四. 捎带应答

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 是可靠的,是不是 TCP 一定优于 UDP?答案是否定的。TCP 和 UDP 各有优缺点,适用于不同的场景。

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

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


九、如何基于UDP实现可靠传输?

参考答案 :UDP本身不可靠,但可以在应用层模仿TCP的机制来实现可靠传输:

  • 序列号:为每个数据包编号,解决乱序问题

  • 确认应答(ACK):接收方收到后回复确认

  • 超时重传:发送方未收到ACK则重发

  • 校验和:检测数据是否损坏

  • 滑动窗口:流量控制

  • 拥塞控制:动态调整发送速率

典型实现:QUIC协议(Google开发,基于UDP实现可靠传输)。


十. 经典面试题: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 协议的典型应用场景。

可以记一个非常适合面试的结论:

DNS 查询优先使用 UDP 53;当响应过大、需要可靠传输,或者进行区域传送时使用 TCP 53。

为什么 DNS 默认使用 UDP?

DNS 查询通常具有两个特点:

  • 请求报文比较小
  • 一问一答,请求 → 响应即可

例如:

复制代码
客户端                         DNS服务器
  |                               |
  | ---- UDP 53:查询 www.baidu.com ----> |
  |                               |
  | <--- UDP 53:返回 IP 地址 ----------- |

UDP 无需建立连接,所以速度快、开销小,非常适合 DNS 这种短请求。

什么情况下 DNS 会使用 TCP?

主要记住下面 3 种情况

① DNS 响应太大

这是最经典的情况。

传统 DNS over UDP 存在报文大小限制。

如果 DNS 服务器返回的数据太大,UDP 响应可能无法完整传输。

早期 DNS 中,如果响应超过 UDP 能承载的大小,服务器会在 DNS 报文中设置:

复制代码
TC = 1

也就是:

Truncated,响应被截断

客户端发现:

复制代码
TC = 1

就会重新通过 TCP 53 查询。

复制代码
客户端
  |
  | UDP DNS Query
  ↓
DNS服务器
  |
  | UDP Response
  | TC = 1(数据太大,被截断)
  ↓
客户端
  |
  | TCP 连接
  ↓
DNS服务器
  |
  | 完整 DNS Response
  ↓
客户端

不过现在有了 EDNS(0) ,UDP DNS 报文可以协商更大的最大报文尺寸,因此并不是只要超过 512 字节就一定使用 TCP

② DNS 区域传送(Zone Transfer)

这个非常重要。

DNS 服务器之间进行区域传送时,例如:

复制代码
主DNS服务器
     |
     | 传送整个 DNS Zone
     ↓
从DNS服务器

涉及大量 DNS 记录:

复制代码
example.com
├── www
├── mail
├── ftp
├── api
├── ...
└── 大量其他记录

这种数据量比较大,而且要求可靠、有序传输。

因此:

DNS 区域传送 AXFR / IXFR 使用 TCP。

常见端口仍然是:

复制代码
TCP 53

③ 某些需要可靠传输的 DNS 场景

TCP 本身提供:

  • 可靠传输
  • 按序传输
  • 重传
  • 流量控制
  • 拥塞控制

所以在 DNS 数据比较大,或者需要可靠传输的场景下,会使用 TCP。

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 请求报文由以下四个部分组成:

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

复制代码
GET /index.html HTTP/1.1

请求头:包含一系列键值对,向服务器传递客户端的信息

复制代码
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
  1. 空行:用于分隔请求头和请求体
  2. 请求体:POST 请求会携带请求体,GET 请求没有请求体

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

10.2.5 服务器处理请求

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

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

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

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

复制代码
HTTP/1.1 200 OK

响应头:包含一系列键值对,向客户端传递服务器的信息

复制代码
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

空行:用于分隔响应头和响应体

响应体 :包含请求的资源内容,这里就是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 面试常考细节

DNS 解析为什么使用 UDP 协议?

  1. UDP 协议开销小,速度快
  2. DNS 查询通常只需要一个请求和一个响应,不需要建立连接
  3. 如果 DNS 查询失败,可以通过超时重传机制来保证可靠性

TCP 三次握手为什么不能改成两次?

  1. 防止已失效的连接请求报文段突然又传送到了服务器,导致服务器错误地建立连接
  2. 保证双方都知道对方的接收和发送能力正常

TCP 四次挥手为什么不能改成三次?

  1. 因为 TCP 是全双工的,双方都需要独立地关闭连接
  2. 服务器收到客户端的 FIN 报文后,可能还有数据没有发送完,不能立即关闭连接

HTTP 长连接和短连接的区别?

  1. 短连接:每次 HTTP 请求都需要建立一个新的 TCP 连接,请求完成后立即关闭连接
  2. 长连接:多个 HTTP 请求可以复用同一个 TCP 连接,减少了建立和关闭连接的开销

HTTPS 和 HTTP 的区别?

  1. HTTPS 在 HTTP 和 TCP 之间加入了 SSL/TLS 层,提供了数据加密、身份认证和数据完整性保护
  2. HTTPS 默认使用 443 端口,HTTP 默认使用 80 端口
  3. HTTPS 需要申请 SSL 证书,HTTP 不需要

总结:

本文系统剖析TCP核心机制:滑动窗口解决传输效率,拥塞控制动态适应网络,延迟与捎带应答提升吞吐;深入字节流特性导致的粘包问题及三种解法,详析异常场景处理,对比TCP/UDP适用场景,并给出基于UDP实现可靠传输的要点。最后拆解HTTP获取网页全流程,涵盖DNS解析(UDP/TCP选择)、TCP连接管理、HTTP报文结构与浏览器渲染,串联网络核心知识架构。

相关推荐
酒神dnspup1 小时前
DNSPup 免费体验监控指南:证书、DNS、IP、端口和网页一站式监控
网络协议·tcp/ip·iphone·网站监控
自动化监测Learner1 小时前
自动化监测数据传不出廊道?大坝通信组网选型实战:RS485、光纤、4G、LoRa、北斗短报文一篇讲透
运维·网络·自动化
代码村新手1 小时前
Linux-将普通用户添加到系统信任文件(sudoers file)白名单中
linux
chuntian_tester1 小时前
AI自动化第1步【系统探索】
人工智能·测试工具·ai·自动化
“AI国潮设计-小江”1 小时前
【Python实战】SDXL精准控制“普宁英歌舞×星空蛋糕”IP落地,附核心Prompt与商用授权思路
开发语言·人工智能·python·prompt·aigc
aodunsoft1 小时前
安全月报 | 傲盾DDoS攻击防御2026年8月简报
网络·安全·ddos
撑伞的鱼99371 小时前
小白安装使用AI编程助手教程:文心快码从下载到跑通首个任务
青少年编程·教程·ai编程·ai工具
k4m7v2pz1 小时前
从 Python 搬到 Rust:pyglet MIDI DAW 变成 egui 鬼畜采样器的迁移复盘
开发语言·python·rust
C++ 老炮儿的技术栈1 小时前
MFC CPtrArray的用法
开发语言·数据结构·c++·算法·mfc·c