目录
-
- [2.2 Web和HTTP](#2.2 Web和HTTP)
-
- [2.2.1 HTTP概况](#2.2.1 HTTP概况)
- [2.2.2 非持续连接和持续连接](#2.2.2 非持续连接和持续连接)
-
- [2.2.2.1 非持续链接](#2.2.2.1 非持续链接)
- [2.2.2.2 持续连接](#2.2.2.2 持续连接)
- [2.2.3 HTTP报文格式](#2.2.3 HTTP报文格式)
-
- [2.2.3.1 请求行](#2.2.3.1 请求行)
- [2.2.3.2 首部行](#2.2.3.2 首部行)
- [2.2.3.3 实体体](#2.2.3.3 实体体)
- [2.2.3.4 状态行](#2.2.3.4 状态行)
- [2.2.3.5 响应首部行](#2.2.3.5 响应首部行)
- [2.2.3.6 实体体](#2.2.3.6 实体体)
- [2.2.4 用户与服务器的交互:cookie](#2.2.4 用户与服务器的交互:cookie)
- [2.2.5 Web缓存](#2.2.5 Web缓存)
- [2.2.6 条件GET方法](#2.2.6 条件GET方法)
2.2 Web和HTTP
2.2.1 HTTP概况
因特网上最重要、最常用的应用之一就是 Web ,
而支撑 Web 的应用层协议叫 HTTP。
所以这一节主要干两件事:
- 先用几段话告诉你:Web 为啥这么重要、它有什么特点;
- 然后正式进入 HTTP:
- Web 页面是什么;
- URL 是什么;
- 浏览器、Web 服务器分别干嘛;
- HTTP 怎么在 TCP 上跑;
- HTTP 是"无状态协议"是什么意思。
回顾一下历史:
- 20 世纪 90 年代之前 ,上网的主要是科研人员、学者、大学生。
- 他们主要做的事:
- 远程登录主机
- 传文件
- 看新闻、收发邮件
这些应用很有用,但圈子比较小,普通人基本接触不到"互联网"这个概念。
- 他们主要做的事:
- 90 年代初 ,一个新的应用"万维网(World Wide Web,简称 Web)"出现了,
它一下子把因特网从"科研圈玩具"变成了"全民应用"。
书里说:
Web 把因特网从只是很多数据库之一的地位提升为"仅有的一个数据库网"。
意思是:
以前上网像是"到很多分散的资料室查资料";
Web 出现之后,大家越来越把"上网 = 上 Web",
感觉好像"所有信息都在 Web 上"。
对普通用户来说,Web 最吸引人的几点:
- "按需点播"而不是"被动收听"
- 传统广播、电视:台里播什么你就看什么;
- Web:你有需求时,自己打开浏览器,想看什么就搜什么、点什么。
- 超链接 + 搜索引擎,让你"乱逛"信息海洋很方便
- 网页里的蓝色下划线就是超链接:
- 一点就跳到另一个页面。
- 搜索引擎帮你在全网里找东西,比如百度、Google。
- 网页里的蓝色下划线就是超链接:
- 任何人都可以很低成本当"出版人"
- 以前要出版书、办杂志门槛很高;
- Web 上你弄个网站、写点博客、发视频,就能"对全世界讲话"。
- 网页里可以嵌各种东西
- 文本、图片、视频、音乐;
- 后来又有 JavaScript、Java 小程序等,可以在网页里做交互;
- 再后来 Web+协议,发展出 YouTube、基于 Web 的邮箱(Gmail)、地图(Google 地图)等等。
一句话:Web 把"信息展示、交互、应用"全揉到浏览器里了 ,
所以成为因特网上最醒目、最典型的应用。
HTTP 概况:Web 的"说话规则"
1. HTTP 是什么?
书里说:
Web 的应用层协议是 超文本传输协议 HTTP(HyperText Transfer Protocol)。
- 这是一个应用层协议,定义了:
- 浏览器(客户端程序)
- Web 服务器(服务器程序)
之间 怎么互相发 HTTP 报文。
- RFC 1945、RFC 2616 是早期 HTTP 规范的文档,你现在知道有这么回事就行。
核心:
HTTP 就是规定"浏览器怎样向服务器要网页、服务器怎样把网页回给浏览器"的一套规则。
Web 页面到底是什么东西?
书里先讲"Web 文档 / Web 页面(Web page)"。
直觉上你觉得一个"网页"就是你看到的一屏东西,但从协议角度看:
一个 Web 页面由 很多对象(object) 组成。
- 对象 object :
→ 在这里就是"一个文件":- 一个 HTML 文件
- 一张 JPEG 图片
- 一个 Java 小程序
- 一段视频......
- 每个对象都有自己的 URL 地址(下面讲 URL)。
举个书中的例子:
某页面由"1 个 HTML 文本 + 5 张 JPEG 图片"构成,
那这个页面其实就包含 6 个对象。
通常我们会说:
- 页面里有一个 "HTML 基本文件(base HTML file)" :
- 它是"骨架":里面写了文字、布局;
- 同时"引用"其他对象(图片、视频等)。
你可以把 HTML 页面想象成:
一篇 Word 文档,正文里插入了好几张图片和视频。
浏览器要显示这个网页,就得:
- 先拿到 HTML 基本文件;
- 从里面读出所有被引用对象的 URL;
- 再去一个个把这些对象下载回来;
- 最后按照 HTML 指定的位置组合在一起显示。
URL 是什么?
URL = Uniform Resource Locator,统一资源定位符。
你可以理解为"网页上每个资源的完整地址"。
格式大概是:
http://www.someSchool.edu/someDepartment/picture.gif
拆开来看:
http→ 使用的协议(这里是 HTTP)www.someSchool.edu→ 主机名(域名),说明在哪台服务器上/someDepartment/picture.gif→ 在服务器上文件的路径和名字
所以:
- 主机名 + 路径 就唯一确定服务器上的一个对象文件。
浏览器拿到这个 URL,就知道:
"我要用 HTTP 协议,去连
www.someSchool.edu这台主机,然后向它请求
/someDepartment/picture.gif这个对象。"
浏览器 & Web 服务器分别干嘛?
-
Web 浏览器(Web browser):
- 比如 Chrome、Firefox、Edge 等;
- 是 HTTP 的客户端程序 :
- 帮你发 HTTP 请求报文;
- 收到 HTTP 响应报文,解析里边的内容,渲染成网页。
所以我们经常把"浏览器 = 客户端"这两个词当成同义词。
-
Web 服务器(Web server):
- 比如 Apache、微软 IIS 等;
- 是 HTTP 的服务器程序 :
- 在服务器主机上打开端口(通常 80)等待请求;
- 收到请求后,根据 URL 找到相应的文件或生成内容;
- 把对象打包成 HTTP 响应报文发回去。
HTTP 定义的,就是"浏览器怎么请求,Web 服务器怎么响应"的报文格式与顺序。
HTTP 跑在 TCP 上:请求--响应过程(图 2-6)

书接着说了一件很重要的事:
HTTP 使用 TCP 作为它的支撑运输协议(不是 UDP)。
也就是说:
- 浏览器要获取某个网页时,先用 TCP 建立连接;
- 建立好 TCP 连接后,才在这条连接上用 HTTP 语言来对话。
1. 建立 TCP 连接
- 客户端(浏览器进程)先向服务器发起 TCP 连接请求;
- 服务器同意后,一条 TCP 连接就建立起来了;
- 这个连接是"两个套接字之间的虚拟管道":
- 一端是浏览器的套接字;
- 一端是 Web 服务器的套接字。
连接建立完毕之后:
客户端就可以通过这个套接字,把 HTTP 请求报文写进去;
TCP 帮它可靠地发到服务器那一端的套接字;
服务器再从套接字读出来处理。
2. 请求--响应的具体流程(配合图 2-6)

图 2-6 的意思是:
-
用户在浏览器里输入网址、或点击一个超链接;
-
浏览器如果还没和这个服务器建立 TCP 连接,就先建立一个;
-
浏览器通过 TCP 连接,发送一个 HTTP 请求报文:
- 请求中包含"我要哪个对象"的信息(URL 路径等)。
-
服务器的 HTTP 进程收到请求后:
"HTTP 进程",指的是 "实现了 HTTP 协议的那个应用进程"
在客户端:浏览器进程
- 它里面有代码实现了"构造 HTTP 请求、解析 HTTP 响应"的功能;
在服务器:Web 服务器进程(如 Apache、Nginx、IIS 等)
- 它里面的代码实现了"接收 HTTP 请求、根据 URL 找文件、构造 HTTP 响应"的功能。
✅ "实现 HTTP 的程序 以进程的形式在主机上跑,我们口头上说'HTTP 客户端进程 / HTTP 服务器进程'。"
- 根据报文里的路径找到相应文件;
- 把文件内容打包成 HTTP 响应报文;
- 通过 TCP 连接发回客户端。
-
浏览器收到响应报文:
- 把里边的 HTML、图片、脚本等提取出来;
- 渲染显示给用户。
对于页面里的多个对象(图片等),浏览器会发多个 HTTP 请求(是不是每个都用新的连接,后面会区分"非持久连接 / 持久连接")。
在这一节,书先告诉你大致流程,细节后面再展开。
HTTP 是"无状态协议(stateless protocol)"是什么意思?
最后这一段非常关键:"HTTP 服务器不记得你是谁。"
书里说:
服务器向客户端发送对象之后,不保存关于这个客户端的任何状态信息。
你可以这么理解:
- 你访问一个普通网站:
- 第一次请求一个页面;
- 第二次请求另一张图片;
- 对服务器来说,这两次请求彼此没有记忆 :
- 它不会"自然地知道"这两个请求来自同一个用户。
- 如果你隔几分钟又发请求,服务器也不会"想起你":
"哦,这个就是刚才那位小张" ------ 默认不会,除非通过 Cookie 等机制(后面会讲)。
为什么要设计成无状态?
-
简单、可扩展:
- 服务器不用给每个用户维护一堆"会话记录";
- 当有成千上万、上百万个浏览器在访问时,服务器压力不会爆炸。
-
它只需做一件事:
"收到一个请求 → 返回对应对象 → 结束"。
当然,现实网站为了"记住你登录了谁、购物车里有什么",
会在 HTTP 之上用 Cookie、session 等机制 人为加"状态",
那是后一小节的内容。
在这一页里你先记住概念:
从 HTTP 协议本身的角度看,它就是一个无状态协议:
每个请求都是独立的,服务器默认不记得之前发生了什么。
因此,从你点开一个网页,到服务器响应,大致是这样:
- 你在浏览器地址栏敲:
http://www.example.com/index.html - 浏览器进程(HTTP 客户端)做的事:
- 解析 URL(协议 http、主机名、路径)
- 通过 DNS 查到服务器的 IP
- 用 TCP 建立连接(这是传输层干的事)
- 按 HTTP 规则 拼出一个请求报文(里面就包含路径、Host 等 → 对应 URL)
- 把这段报文写进 TCP 套接字
- 操作系统里的 TCP 协议栈:
- 把这段数据可靠地送到服务器那边的 TCP 套接字
- 服务器上的 Web 服务器进程(HTTP 服务器进程):
- 从套接字里读出这段数据(一个 HTTP 请求报文)
- 按 HTTP 规则解析出请求方法、路径等
- 找到
/index.html这个资源 - 再按 HTTP 规则构造一个响应报文
- 把响应报文写回 TCP 套接字
- 浏览器进程收到响应:
- 从 TCP 套接字读出 HTTP 响应报文
- 解析其中的 HTML、图片等
- 绘制成你看到的网页
在这整个过程中:
- 协议: HTTP(规则)
- 报文: HTTP 请求报文 / 响应报文(具体数据)
- 进程:
- 浏览器进程:HTTP 客户端
- Web 服务器进程:HTTP 服务器
2.2.2 非持续连接和持续连接
1. 典型场景
一个 Web 页面通常是这样:
- 1 个 HTML 文件(骨架)
- 10 张图片(JPEG)
- 可能还有 JS、CSS、视频等等
浏览器想显示这个页面,就得拿到所有这些对象。
问题来了:
这些对象是从同一台服务器来的,那 TCP 连接要怎么用?
有两种设计方式:
2. 非持续连接(non-persistent connection)
规则:每条 TCP 连接只服务"一对请求/响应",用完就关。
打电话类比:
- 你问一句"在吗?" → 对方回"在" → 挂电话;
- 再问"明天一起吃饭吗?" → 重新拨电话 → 对方回 → 再挂......
也就是:
每问一个问题,都要新打一个电话。
在 HTTP 里就是:
每拿一个对象(HTML 或图片等),都要新建一个 TCP 连接。
3. 持续连接(persistent connection)
规则:一条 TCP 连接可以在一段时间内被多次使用,连续发送多个请求、得到多个响应后再关。
打电话类比:
- 一次电话里:
- 问"在吗?"
- 问"吃饭吗?"
- 问"地点定哪?"
- 中间不挂电话,聊完一起挂。
HTTP 里就是:
建立一个 TCP 连接后,可以在这条连接上依次请求:
HTML、图片1、图片2......直到用完再关闭。
2.2.2.1 非持续链接
采用"非持续连接"的 HTTP:一步步走一遍
书这一节先分析的是 "非持续连接的 HTTP"(HTTP/1.0 默认就是这样)。
先设定一个场景(跟书一样):
- 某个页面有:
- 1 个 HTML 基本文件 (
home.index) - 10 张 JPEG 图片
- 1 个 HTML 基本文件 (
- 这 11 个对象全在同一台服务器
www.someSchool.edu上。
步骤 1:浏览器建立第一个 TCP 连接(为了 HTML)
- 客户端 HTTP 进程 (浏览器)
发起一个到www.someSchool.edu的 TCP 连接,请求端口号 80(HTTP 默认端口)。- 这一步是 TCP 做的"三次握手",稍后讲 RTT 时会提到。
- 连接建立后,客户端通过这个连接的套接字 发送一个 HTTP 请求报文 ,里面写着:
- "我要
/someDepartment/home.index这个 HTML 文件"。
- "我要
- 服务器 HTTP 进程 从自己的套接字里读到这个请求报文,
在它的存储(内存或磁盘)中找到对应的文件home.index,
把文件内容封装成 HTTP 响应报文,通过同一个 TCP 连接发回客户端。 - 服务器接着就通知 TCP:可以断开这条连接了 。
注意:得等确认客户端已经完整收到了响应报文,TCP 才真正关闭。 - 客户端浏览器收到响应、把 HTML 取出来 → 然后也关闭自己这端的 TCP 连接。
到这里为止:
第一条 TCP 连接只干了一件事:
传输 1 个 HTML 文件(1 个请求 + 1 个响应)。
步骤 2:浏览器解析 HTML,发现还有 10 张图片要拿
- 浏览器开始解析刚拿到的 HTML 文件,
在里面看到了 10 个<img src="...">这样的引用:
也就是 10 个 JPEG 图片的 URL。
接下来的动作就是:
对这 10 张 JPEG,每一个都重复上面的 1~5 步骤。
- 对每张图片:
- 新建一个 TCP 连接;
- 发 HTTP 请求"我要图片 X";
- 服务器回图片 X 的响应;
- 服务器关闭连接;
- 浏览器收完也关闭连接。
如果完全串行(一个一个来),那就是:
一共 11 次 TCP 连接(1 个 HTML + 10 个 JPEG),
每次连接只传 1 个对象。
书里后面说:
- "每个 TCP 连接只传输一个请求报文和一个响应报文 ,
这就是非持久连接的典型用法。"
浏览器也可以"开多条并行的非持久连接"
书中后面提到:
- 实际浏览器一般会开多个并行 TCP 连接 来加速:
- 比如最多同时开 5~10 条;
- 每条连接仍然"一个对象用完就关"。
这就好比:
你要问对方 10 个问题,不是 1 个 1 个打电话,
而是派 5 个人同时分别打 2 通电话,各问其中一些问题。
理解"非持续连接"的缺点 & RTT
书后面对非持续连接的缺点总结了两点:
-
连接数量太多,服务器和客户端压力大
- 每次请求对象都要:
- TCP 三次握手建连接;
- 传输完再四次挥手关连接;
- 为这个连接分配缓冲区、状态、变量......
- 对一台热门 Web 服务器来说,成千上万用户,每个页面几十个对象 → 连接数量爆炸。
- 每次请求对象都要:
-
每个对象都要付出多次 RTT 的时延,页面显示慢
这就要说明什么是 RTT(往返时间 Round-Trip Time):
- RTT = 一个小分组从客户端到服务器再回来的时间:
- 包括线路传播时间
- 路由器排队、处理时间
- 比如国内某网站 RTT 可能几十毫秒,国外几百毫秒。
- RTT = 一个小分组从客户端到服务器再回来的时间:
对一个 HTML 文件,在非持续连接下,总耗时大约是多少?
书里简单估算(图 2-7):

- 先建立 TCP 连接 :
- 需要一次 RTT(因为三次握手,前两步就占了一个来回时间)。
- 在已经建立的连接上,发送 HTTP 请求 + 收到响应的第一个字节 :
- 又是一个 RTT。
- 再把整个 HTML 文件传完 :
- 这段时间和文件大小 / 链路带宽有关。
所以粗略估算:
获取这个 HTML 文件的时间 ≈ 2×RTT + 文件真正传输时间
对于那 10 张图片,每一个都要再付一次"2×RTT + 传输时间"(除非做并行)。
所以非持久连接在时延上就比较吃亏。
2.2.2.2 持续连接
采用"持续连接"的 HTTP:怎么改进?
看到非持续连接的问题后,自然就想到:
"既然一台服务器要给同一个浏览器发一堆对象,
干嘛不只建一次 TCP 连接、然后在里面多次对话呢?"
这就是 持续连接(persistent connection) ,
HTTP/1.1 的默认模式就是持续连接。
持续连接的基本思路
在 HTTP/1.1 的持续连接下:
- 服务器在发送完响应后,不会立刻关闭 TCP 连接;
- 同一个客户端后续的 HTTP 请求、响应会复用这条连接。
对应到刚才的例子:
- 客户端建立 1 条 TCP 连接;
- 先在这条连接上请求 HTML,拿到 HTML;
- 解析 HTML 找到 10 张图片;
- 再在同一条连接上 依次发送 10 个"我要图片 X"的 HTTP 请求,
服务器依次返回 10 个响应; - 所有对象传完后,双方再关闭连接。
整个页面(1 个 HTML + 10 张 JPEG)
只需要 1 条 TCP 连接 就搞定。
好处:
- 连接建立 / 关闭的开销大大减少
- 客户端和服务器都不用为每个对象维护新的 TCP 状态;
- 服务器不会被大量短命连接压垮。
- 时延减少
- 只需为整个会话付出一次"建连接的 RTT",
- 后面多个请求可以一个接一个发。
书里还提到一个更进一步的优化:管道化(pipelining)(在 HTTP/1.1 / HTTP/2 里):
- 客户端可以在还没收到前一个响应时,就把后面的请求也发出去;
- 服务器按顺序把多个响应排队发回去;
- 这样就不用每个请求都再额外等待一个 RTT。
对服务器来说的优势
- 对同一个客户端,一个持久 TCP 连接可以服务这个客户端的多个页面请求;
- 多个网页的对象可以排着队在这条连接上来回;
- 比非持久连接那种"每个对象都单独建连接"要节省很多资源。
| 特性 | 非持续连接 HTTP(HTTP/1.0 默认) | 持续连接 HTTP(HTTP/1.1 默认) |
|---|---|---|
| TCP 连接数量 | 每个对象一个连接 → 1 页面 = N 个连接 | 多个对象共享连接 → 1 页面可只用 1 个连接 |
| 建连 / 关连开销 | 大:每个对象都要三次握手 + 四次挥手 | 小:只在整个会话开始 / 结束时做一次 |
| 时延 | 每个对象至少花 2 RTT + 传输时间 | 整个页面共用建立连接的那 1 个 RTT,后续更快 |
| 服务器压力 | 连接数量多,状态多,内存占用大 | 连接数量少,状态少,开销小 |
| 实际应用 | 早期 HTTP/1.0 广泛使用 | 现在浏览器基本默认用持久连接(HTTP/1.1、HTTP/2) |
一句话记住:
非持久连接:一问一挂电话;
持久连接:一次电话聊完很多事。
2.2.3 HTTP报文格式
HTTP 报文 = 一封"纯文本格式的信"
书一上来先说两点:
- HTTP 报文其实就是 普通的 ASCII 文本
→ 也就是"纯文本",你用记事本看得懂的那种。 - 报文是由 一行一行 组成的
- 每行的结尾有一个"回车 + 换行"(书里写
cr lf),你就把它理解成"换行"就行。 - 最后有一行空行,专门作为"分界线"。
- 每行的结尾有一个"回车 + 换行"(书里写
所以你可以把 HTTP 报文想象成:
一封格式非常严格的纯文本信,
上面有"第一行概况(抬头)+ 很多头部字段 + 空行 + 正文"。
HTTP 请求报文:浏览器发给服务器的"信"
书上给的例子(典型请求报文):
GET /somedir/page.html HTTP/1.1
Host: www.someschool.edu
Connection: close
User-agent: Mozilla/5.0
Accept-language: fr
整体分成四部分
配合图 2-8 来看,一个 HTTP 请求报文一般是:

- 请求行(request line)
- 零行或多行首部行(header lines)
- 一个空行 (只有
cr lf,什么字也没有) - (可选)实体体(entity body):真正的"数据内容"
在这个例子中:
- 只有请求行 + 4 行首部行,没有实体体(因为是 GET 请求)。
2.2.3.1 请求行
请求行:最重要的一行
例子中第一行:
GET /somedir/page.html HTTP/1.1
请求行里有 3 个"单词"(中间用空格 sp 分隔):
- 方法(method) :
GET - URL 字段(准确说是路径那部分) :
/somedir/page.html - HTTP 版本号 :
HTTP/1.1
常见方法有哪些?
GET:最常见,意思是"我要取资源"POST:提交数据用(尤其是表单)HEAD:只要头部,不要正文PUT:上传 / 覆盖指定路径上的资源(一般给工具或 API 用)DELETE:让服务器删除某个资源
现在你先记:浏览网页 90% 以上用的是 GET 和 POST,剩下那几个更多用在调试、接口、工具里。
2.2.3.2 首部行
首部行(Header lines):一堆"额外信息"
例子里的 4 行首部:
Host: www.someschool.edu
Connection: close
User-agent: Mozilla/5.0
Accept-language: fr
注意格式都是:
首部字段名: 空格 值
一个一个看:
(1) Host: www.someschool.edu
- 表示:这次请求要访问的主机名。
- 为什么需要?
- 因为很多服务器是一台机器上挂好多站点(虚拟主机),
同一个 IP 可能对应a.com、b.com等; - 有了 Host,服务器才能判断你要访问哪一个站点。
- 因为很多服务器是一台机器上挂好多站点(虚拟主机),
(2) Connection: close
- 告诉服务器:"这次传完就关 TCP 连接,不要保持持续连接。"
- 如果用的是持续连接(HTTP/1.1 默认),就可以不写
close,或写keep-alive等。
(3) User-agent: Mozilla/5.0
- 表示客户端软件的类型 → 浏览器标识
- 比如 Firefox、Chrome、Edge、手机浏览器等等;
- 服务器可以根据这个决定返回不同版本页面(比如给手机的简化版)。
(4) Accept-language: fr
- 表示:希望服务器给我返回"法语(French)版本"的资源";
- 如果服务器有多语言版本,就可以按这里的偏好来选;
- 如果没有,就按默认语言返回。
你现在可以把"首部行"理解成:
一封信里面贴的小标签:谁寄的、希望用什么语言、用什么方式传......
不是必须都有,但很多都很常见、很有用。
2.2.3.3 实体体
实体体(entity body):真正要"装东西"的地方

在图 2-8 中,你能看到在空行之后还有一个"实体体"。
对于请求报文:
- GET 请求:一般没有实体体;
- POST 请求 :常常有实体体,比如:
- 你在网页上提交表单(用户名、搜索关键字等),
这些表单数据就被放在请求报文的实体体里发给服务器。
- 你在网页上提交表单(用户名、搜索关键字等),
举个小例子(简化版 POST):
POST /submit HTTP/1.1
Host: www.example.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 27
username=tom&pwd=123456
空行下面这句 username=tom&pwd=123456 就是实体体。
书里还提到:
HEAD:像 GET,但服务器只返回"响应头部",不带真正内容
→ 常用于测试链接是否有效、获取对象的属性而不下载全文。PUT:客户端给服务器上传一个对象,放到某个指定路径
→ 适合"发布工具"或 API 使用,一般用户浏览网页不会直接用到。DELETE:让服务器删除某个路径上的对象
→ 也多用于 API,而不是普通网页浏览。
你当前阶段先牢牢记住:GET / POST / HEAD,以后再细看 PUT/DELETE 就够用。
HTTP 响应报文:服务器回给浏览器的"信"
书里的响应报文例子:
HTTP/1.1 200 OK
Connection: close
Date: Tue, 18 Aug 2015 15:44:04 GMT
Server: Apache/2.2.3 (CentOS)
Last-Modified: Tue, 18 Aug 2015 15:11:03 GMT
Content-Length: 6821
Content-Type: text/html
(data data data data data ...)
整体结构(配合图 2-9)

跟请求报文很像,也分 3 大块:
- 状态行(status line):第一行
- 若干个首部行(header lines)
- 一个空行 + 实体体(entity body):对象本体数据
2.2.3.4 状态行
状态行:告诉你结果咋样了
HTTP/1.1 200 OK
三个部分:
- 版本号 :
HTTP/1.1 - 状态码 :
200 - 状态短语 :
OK(对人看的简单说明)
状态码 + 短语 = 告诉浏览器:"这次请求的结果如何"。
书里列了几个常见的,你平时在浏览器里也经常见到:
- 200 OK
→ 请求成功,一切正常,数据在响应体里。 - 301 Moved Permanently
→ 请求的对象已经永久搬家了;
服务器会在Location:首部里告诉你新的 URL;
浏览器就会自动重定向到新地址。 - 400 Bad Request
→ "请求有问题",比如报文格式乱七八糟,服务器看不懂。 - 404 Not Found
→ 最有名 :不存在的网页;
URL 指向的对象在服务器上没找到。 - 505 HTTP Version Not Supported
→ 客户端用的 HTTP 版本,服务器不支持。
以后你在浏览器 F12 开发者工具里,也能看到这些状态码。
2.2.3.5 响应首部行
响应首部行:各种说明信息
Connection: close
Date: Tue, 18 Aug 2015 15:44:04 GMT
Server: Apache/2.2.3 (CentOS)
Last-Modified: Tue, 18 Aug 2015 15:11:03 GMT
Content-Length: 6821
Content-Type: text/html
我们挨个解释:
(1) Connection: close
- 告诉客户端:"这次发完我就要关掉 TCP 连接了";
- 配合前面的请求说的
Connection: close一起使用。
(2) Date: ...
- 表示 服务器"产生这条响应"的时间(不是文件的创建时间);
- 方便浏览器和缓存系统判断新旧。
(3) Server: Apache/2.2.3 (CentOS)
- 表示运行在服务器上的 Web 服务器软件:
- 比如 Apache、Nginx、IIS 等;
- 有时也会带版本和操作系统。
(4) Last-Modified: ...
- 表示这个对象在服务器上"最后被修改"的时间;
- Web 缓存(如浏览器缓存、代理缓存)会用这个时间判断:
- 本地缓存是否落后于服务器版本;
- 如果没变,就可以不重新下载,节省流量。
(5) Content-Length: 6821
- 表示接下来的实体体有多少 字节;
- 浏览器根据这个值知道什么时候"读完了"。
(6) Content-Type: text/html
- 告诉浏览器:实体体里的数据是什么类型 :
text/html→ HTML 文本image/jpeg→ JPEG 图片video/mp4→ MP4 视频application/json→ JSON 数据
(后面你都会见到)
浏览器就靠 Content-Type 决定:
- 这是网页 → 就按 HTML 渲染
- 这是图片 → 就以图片方式显示
- 这是视频 → 播放器处理
2.2.3.6 实体体
例子最后:
(data data data data data ...)
这里就是真正的 HTML 文件内容(或者图片、视频等)。
- 对 请求报文:实体体一般只有在 POST 等方法时出现;
- 对 响应报文 :实体体是最常见的,几乎所有"有内容的响应"都有。
Telnet 小例子:说明 HTTP 就是"纯文本对话"
书最后给了一个小技巧:
你可以不用浏览器,而是用 telnet 工具 手打 HTTP 请求报文 来看响应。
书上的例子(简化):
telnet gaia.cs.umass.edu 80
GET /kurose_ross/interactive/index.php HTTP/1.1
Host: gaia.cs.umass.edu
telnet gaia.cs.umass.edu 80
→ 建立到服务器 80 端口(HTTP 端口)的 TCP 连接;- 然后你自己敲两行请求(按两次回车结束):
- 第一行:请求行
- 第二行:Host 头
- 最后一行空行(就是只按回车)
服务端就会按照 HTTP 协议给你回一个完整的响应报文,你能直接在终端里看到。
这个例子想说明:
HTTP 报文本质上就是"纯文本 + 换行组成"的协议 ,只要你照着格式写,人肉也能跟服务器"聊 HTTP"。
www.example.com我们把它"剁开":
wwwexamplecom这三个加在一起,叫做一个 完整的域名 / 主机名(专业叫 FQDN)。
域名(domain name) 是一个"名字空间",用来划分网络中的区域,就像:
com:顶级域(像"某个大国")example.com:在com下面划出来的一个"地盘",可以理解成"一个公司/组织的地盘"通常我们说"我注册了一个域名 example.com",
指的是 这个整体
example.com,你可以在它下面再分很多名字:
www.example.commail.example.comapi.example.comblog.example.com- ...
这些统统都属于
example.com这个域下面。简单比喻:
com像"国家";example.com像"这个国家里的一个城市";www.example.com像"这个城市里的某栋楼"。什么是「主机名(hostname)」?
主机名 可以理解为:
"域名里面某一台具体主机(服务器)的名字"。
在
www.example.com里:
- 整串
www.example.com常被叫作"主机名 / 主机的域名";- 有时也把最左边这段
www看作"主机名这部分",
右边的example.com叫"所属的域"。所以很多教材里会这么说(大概的意思):
- 域 :
example.com- 主机名 :
www- 完整主机名 :
www.example.com不过在实际 Web 开发里,为了省事,大家经常把"域名"和"主机名"混着叫:
- "访问这个域名 www.baidu.com"
- "Host 头里写域名 www.baidu.com"
严格一点说:
- 域名 更偏向 "名字空间(example.com)";
- 主机名 更偏向 "具体机器的名字(www.example.com 这一整串)"。
但你可以这样记:
对于 HTTP 头里的
Host:,
你就把它理解成:把"浏览器地址栏里那段域名字符串照抄进去" ,比如
Host: www.example.com。总结:
主机名和域名不完全一样,但关系很近:
- 域名:
example.com(以及它下面的整体名字空间);- 主机名:域名里的某台主机,例如
www.example.com。在很多 Web 场景中 ,大家说"域名""主机名"经常指同一串东西(比如
www.xxx.com),所以你看到书上、代码里混着用,不用太纠结------知道它们都指"那个用来访问网站的名字"就可以。
真正对网络设备来说,最关键的是:
- DNS 把域名 / 主机名 → 解析成 IP;
- 浏览器在 HTTP 请求里把这个名字写到
Host:头里,
服务器凭这个判断"你要的是哪个站点"。
2.2.4 用户与服务器的交互:cookie
为什么要有 cookie?------因为 HTTP"失忆"
-
HTTP 协议有个特点:无状态(stateless)
意思是:
服务器只把每一个 HTTP 请求当成"全新的陌生人",处理完就忘了,不记得你之前来过。
-
现实网站需要什么?
- 记住你加过哪些商品到购物车
- 记住你已经登录了,不用每个页面都重新输账号密码
- 给你"猜你喜欢""历史浏览记录"
......
这些都需要记住"你就是上一次那个用户"。
-
矛盾点:
- HTTP:我只会收发请求和响应,我不记人
- 网站:我必须记住用户的状态
→ 于是就想了个"外挂记忆方法"------cookie。
你可以把 cookie 理解成:
"网站发给你的一张身份小纸条(编号),浏览器帮你保管,下次再来时,你把这张纸条带给网站,让它知道:'还是我'。"
cookie 的四个组成部分(书里那句"4 个组件")
书里说 cookie 技术有 4 个组件,对照着说人话:
-
在 HTTP 响应报文中的一个 cookie 首部行
-
服务器第一次见到你,会在回给你的 HTTP 响应里加一行类似:
Set-Cookie: 1678 -
这个叫 Set-Cookie 首部,意思是:
"喂,浏览器,请帮我把这个编号 1678 存起来,之后你再来找我,就带上它。"
-
-
在 HTTP 请求报文中的一个 cookie 首部行
-
以后你再访问这个网站,浏览器会在发出去的 HTTP 请求里自动加一行:
Cookie: 1678 -
这叫 Cookie 首部,就相当于你进门先递上"会员卡"。
-
-
在用户端系统中保留的一个 cookie 文件,由浏览器进行管理
- 浏览器把服务器给的 cookie(比如 1678)存到你电脑本地的一个文件里。
- 文件里会记:哪个网站、给了什么 cookie、什么时候过期等等。
- 所以后面你关机、重启浏览器,只要没过期,浏览器还是能记得。
-
位于 Web 站点后端数据库中的一个表项
- 服务器那边也有一个 数据库表 ,里边有很多行:
- 用户 ID(比如 1678)
- 这个 ID 对应用户看过的页面
- 购物车里有哪些商品
- (如果你注册了)姓名、地址、邮箱、信用卡号......
- 每次服务器收到
Cookie: 1678,就去这个表里查"ID=1678"这行,知道你是谁、之前干过什么。
- 服务器那边也有一个 数据库表 ,里边有很多行:
这四个一起配合,就实现了:
"浏览器和服务器通过一个小小的编号,在无状态的 HTTP 上,自己搞出了'有记忆的会话'。"
书上图 2-10 用的是 Susan 访问 eBay 和 Amazon,我们按时间线说:

1)第一次上网:访问 eBay(和 cookie 没太大关系)
- Susan 在家用 PC(浏览器是 IE)上网;
- 先访问 eBay:浏览器发出普通的 HTTP 请求报文;
- eBay 返回一个普通的 HTTP 响应报文(书里没细讲 cookie,主要是铺垫)。
2)第一次访问 Amazon:服务器创建 cookie
现在 Susan 访问 Amazon:
-
Susan 的浏览器发出 HTTP 请求 → Amazon 服务器
- 这是她第一次访问 Amazon,所以请求里还没有 cookie。
-
Amazon 服务器给她分配一个唯一编号
- 比如:1678(书里的例子)
- 同时在自己后台数据库里新建一行:
- 用户 ID = 1678
- 其他信息先空着(以后慢慢填:她看过什么、加过什么商品等)
-
Amazon 在响应报文里加上 Set-Cookie
-
返回的 HTTP 响应多了一行首部:
Set-Cookie: 1678
-
-
浏览器收到响应 → 把 cookie 写进本地文件
- IE 浏览器看到
Set-Cookie: 1678这一行,就会在它管理的 cookie 文件里加一条记录:- 网站:amazon
- cookie 值:1678
- 如果她以前访问过 eBay,那文件里已经有 eBay 的 cookie 表项了,现在只是多了一条 Amazon 的。
- IE 浏览器看到
小结一下:
- 服务器:"这个叫 1678 的人,以后你再来报这个号码。"
- 浏览器:"好,我帮你把 1678 记着。"
一段时间后再访问 Amazon:cookie 发挥作用
假设一周之后,Susan 又来 Amazon:
-
浏览器再次向 Amazon 发 HTTP 请求
-
在发请求前,浏览器会先查本地 cookie 文件:
"有给 amazon.com 的 cookie 吗?"
有 → 1678
-
所以请求报文里自动加上这一行:
Cookie: 1678
-
-
Amazon 服务器收到请求,看到 Cookie: 1678
-
服务器就知道:
"哦,这不是新访客,这是上次的那位 1678 用户。"
-
然后服务器去后端数据库 查
ID=1678这一行,知道:- 她之前看过哪些商品
- 购物车里有没有东西
- 她的偏好是什么
-
-
"cookie 特定动作"是啥?
- 书上图里右边写的是"cookie 特定动作",意思是:
- 根据这个用户 ID 做一些个性化操作,比如:
- 展示"你可能还喜欢..."
- 把上次放在购物车里的商品继续显示出来
- 记录她这次又访问了哪些页面
- 根据这个用户 ID 做一些个性化操作,比如:
- 书上图里右边写的是"cookie 特定动作",意思是:
-
如果她在 Amazon 注册了账号
-
她填:姓名、电子邮箱、邮寄地址、信用卡号等等;
-
服务器会把这些信息写到数据库里
ID=1678的那一行; -
以后只要看到
Cookie: 1678,-
就知道这是 "Susan",地址是 XXX,信用卡是 XXX;
-
于是出现书里说的一键购物(one-click shopping):
再买东西时不用重新输姓名、卡号、地址。
-
-
所以:
虽然 Amazon 不一定知道她真名叫 Susan(除非她注册时填了),
但 只要看到 1678,就能知道"这个人过去都干了啥"。
cookie 用来"标识用户"和"建立会话"
书里最后一段讲的是 cookie 的用途,可以归纳成两大类:
1)标识用户(知道"你是谁")
- 每个用户第一次来,服务器给一个唯一 ID(就像会员编号)。
- 以后所有请求都带这个 ID,服务器就可以"认出"你,并根据数据库信息提供个性化服务:
- 推荐系统
- 历史浏览
- 购物车
- 语言偏好(总给你中文版界面)......
2)在无状态 HTTP 上建立"会话"(session)
-
假设一个 Web 系统:例如 Web 邮箱(早期 Hotmail、现在的 QQ 邮箱、Gmail 等)。
-
用户登录时:
-
输入账号密码;
-
服务器验证通过后,生成一个专属于这次登录的 cookie(比如 session id),在响应中:
Set-Cookie: session_id=xyz123 -
浏览器保存这个 cookie。
-
后面的所有请求(收邮件、发邮件、打开设置页面等),浏览器都会自动加上:
Cookie: session_id=xyz123 -
服务器看到这个 session_id,就知道:
"你已经登录了,是某某账号",所以不再让你每一步都输入密码。
-
-
这就叫:
"在无状态的 HTTP 之上,构建一个有状态的用户会话"。
最后一点:隐私与争议
书的最后一小段在提醒:cookie 也有问题:
- 结合 cookie 和你主动填写的账号信息(姓名、地址、电话等),
网站可以知道你:- 常买什么
- 常访问哪些页面
- 活动时间习惯......
- 如果这些信息被卖给第三方公司,就可能用来:
- 精准广告投放
- 甚至更严重的隐私泄露
所以:
- 一方面,cookie 让上网体验更方便(无需反复登录、购物车不会丢、个性化推荐);
- 另一方面,它又 带来隐私风险,所以才会有各种争议、法律规定、浏览器设置(比如清除 cookie、禁止第三方 cookie 等)。
2.2.5 Web缓存
Web 缓存是啥?(先有个直观概念)
书上说:Web 缓存(Web cache)也叫代理服务器(proxy server)。
你可以把它想象成:
学校门口的"小卖部仓库",专门帮你存放一些常用的零食,这样你就不用每次都跑到很远的总仓库去拿。
在网络里:
- 初始服务器:真正放网页、图片的那个网站服务器(比如 www.someschool.edu)。
- Web 缓存 :离你比较近的一台机器,提前把热门的网页副本存到自己的硬盘上。
- 客户端:你的电脑、手机,浏览器就是客户端程序。
工作目标只有两个字:加速、省流量。
通过 Web 缓存访问网页的流程(图 2-11)

书上举的例子:浏览器要访问
http://www.someschool.edu/campus.gif
这是一张图片。
如果有 Web 缓存,流程变成这样(对应书里 1~4 步):
第 1 步:浏览器先连 Web 缓存,而不是连真正的网站
- 原来:
你上网 → 浏览器直接连 原始服务器(www.someschool.edu),发 HTTP 请求。 - 现在:
配置好之后,浏览器会 先把请求发给 Web 缓存。
类比:你本来是自己跑去总仓库拿东西,现在改成:
先问"学校门口仓库"有没有。
第 2 步:Web 缓存先看自己这里有没有这个对象
- Web 缓存收到请求后,会检查:
- "我硬盘里有没有
campus.gif这张图的副本?"
- "我硬盘里有没有
- 如果有,说明以前别人访问过它,缓存已经存了一份。
- 那么:缓存直接给你返回 HTTP 响应报文,把图片发给你。
- 整个过程就结束了,根本不用访问远处的原始服务器。
这就叫 命中(hit) :
"你要的东西我这儿正好有。"
第 3 步:缓存里没有,就再去找原始服务器
- 如果缓存里没找到这张图,那叫 未命中(miss)。
- 这时候,缓存自己扮演"客户端"的角色:
- 它再去和 原始服务器 www.someschool.edu 建 TCP 连接;
- 向它发 HTTP 请求:"给我 campus.gif"。
类比:学校小仓库没有,就帮你打电话去总仓库要货。
第 4 步:拿到对象后,缓存一边存,一边转交给浏览器
- 原始服务器把 campus.gif 发给 Web 缓存;
- Web 缓存做两件事:
- 把图片存到自己的磁盘(这样以后别人再要,就可以命中);
- 同时把图片回给最开始的浏览器。
之后别的同学再访问同一个图片时,缓存就能直接给他们,速度就非常快。
Web 缓存到底算客户端还是服务器?
书上有一句话:
Web 缓存既是服务器又是客户端。
怎么理解?
- 对 浏览器 来说:
它就是一个"服务器"------浏览器给它发 HTTP 请求,它回 HTTP 响应。 - 对 原始服务器 来说:
它又是一个"客户端"------它再去给真正的网站发 HTTP 请求。
所以它夹在中间:"对内像服务器,对外像客户端"。
为什么要在因特网上部署 Web 缓存?(两个原因)
书上说了 两个主要原因:
1)减少用户等待时间(响应时间)
- 如果客户端和 Web 缓存之间是一个高速局域网(很常见:你在学校、公司内网,缓存也在本单位机房);
- 而客户端到原始服务器之间是比较慢的链路(穿越公网,甚至跨国);
那只要缓存里有你想要的对象,它就可以:
在本地高速网上,用很短的时间把内容给你 ------ 这比跨越大半个地球去原始服务器快多了。
2)减少"跨公网"的通信量,省钱
- 很多单位(学校、公司)接外网的链路是要花大钱租的;
- 所有访问外部网站的流量都挤在这根"因特网接入链路"上;
- 如果某些资源可以在 单位内部的缓存 就搞定,就不用把所有请求都发到公网去。
好处:
- 外网链路上的流量减少了;
- 单位就不用急着花钱升级带宽;
- 整体费用下降。
小结:
Web 缓存 = 让常见请求"就近解决",又快又省钱。
带数值的例子(图 2-12):看清楚瓶颈在哪
书上画了一个场景:
有一个**机构网络(内网)**通过一条 15Mbps 的接入链路接到因特网,因特网上有很多初始服务器。
局域网是 100Mbps,很快;接入链路只有 15Mbps,相对慢很多,所以是瓶颈。

假设条件(书里给的数字)
- 一个对象(网页、图片)的平均大小:1Mb(注意是 Mbit,不是 MByte)。
- 机构内所有用户访问这些初始服务器的平均速率:15 个请求/秒。
- 从"机构网络出口路由器"把一个 HTTP 请求报文发到外面,到收到 HTTP 响应报文为止(走公网那一段)平均要 2 秒 。
- 书里把这叫因特网时延:大致 2s(包括跨公网的排队、传输等)。
1)先看内部局域网的压力
书用"流量强度"这个概念(可以理解成"忙碌程度",越接近 1 越拥堵)。
局域网流量强度:
流量强度 = 到达速率 × 平均业务量 链路速率 \text{流量强度} = \frac{\text{到达速率} \times \text{平均业务量}}{\text{链路速率}} 流量强度=链路速率到达速率×平均业务量
代入数字:
- 到达速率:15 请求/秒;
- 每个请求平均 1Mb;
- 局域网带宽:100 Mbps;
所以:
ρ 局域网 = 15 × 1 100 = 15 × 0.01 = 0.15 \rho_{\text{局域网}} = 15 \times \frac{1}{100} = 15 \times 0.01 = 0.15 ρ局域网=15×1001=15×0.01=0.15
0.15 远小于 1,说明局域网其实比较空,排队时延很小,可以忽略。
2)看看接入链路的压力(真正的瓶颈)
接入链路带宽:15 Mbps,所以:
ρ 接入链路 = 15 × 1 15 = 15 × 0.066 6 ‾ ≈ 1 \rho_{\text{接入链路}} = 15 \times \frac{1}{15} = 15 \times 0.066\overline{6} \approx 1 ρ接入链路=15×151=15×0.0666≈1
也就是 流量强度接近 1,意味着:
- 这条链路几乎一直满负荷地在跑;
- 一旦稍微再多几个人访问,就会出现 排队时间疯狂变长 的情况;
- 书里说:可能导致某些请求的总延迟达到几十秒,非常难受。
所以,现在的响应时间 ≈
- 局域网延迟(很小,可以忽略),再加上
- 接入链路 + 公网的延迟(平均算成约 2 秒,甚至可能更高)
方案一:升级接入链路带宽(图 2-12 分析后半)
想法:把 15Mbps 升级到 100Mbps。
新的流量强度:
ρ = 15 × 1 100 = 0.15 \rho = 15 \times \frac{1}{100} = 0.15 ρ=15×1001=0.15
不再接近 1,等待排队时间就很小了。这样:
- 整体响应时间就主要由那 2 秒因特网时延决定;
- 用户平均等待时间 ≈ 2 秒;
问题在于:
这意味着机构必须花很多钱,把对外的接入链路从 15Mbps 升级到 100Mbps ------ 很贵。
所以书里说:这是"代价很高的方案"。
方案二:不升级带宽,改用 Web 缓存(图 2-13)

现在考虑另一种解法:在机构网络里安装一个 Web 缓存。
假设:
- Web 缓存的 命中率 (hit rate)是 0.4。
意思是:40% 的请求,直接在缓存里就找到了,不用出外网。 - 剩下 60% 的请求还是要去外面的原始服务器。
1)命中时的延迟
- 客户端和缓存之间,是 100Mbps 的局域网;
- 书里假设:在这种条件下,从发出请求到收到响应,延迟约 0.01 秒 = 10ms;
- 这非常快,几乎秒开。
2)未命中时的延迟
- 60% 的请求要去原始服务器走公网;
- 此时时延 ≈
- 局域网那一小点(0.01 秒),加上
- 因特网上 2 秒左右的延迟;
- 所以约为:2.01 秒。
3)平均响应时间怎么算?
有两类请求:
- 40% 命中:每次 0.01 秒;
- 60% 未命中:每次 2.01 秒。
平均时间:
T avg = 0.4 × 0.01 + 0.6 × 2.01 T_{\text{avg}} = 0.4 \times 0.01 + 0.6 \times 2.01 Tavg=0.4×0.01+0.6×2.01
我们算一下:
- 0.4 × 0.01:
- 4 × 1 = 4;
- 小数点后 3 位 → 0.004 秒。
- 0.6 × 2.01:
- 2.01 × 6 = 12.06;
- 再除以 10 → 1.206 秒。
加在一起:
T avg = 0.004 + 1.206 = 1.210 秒 T_{\text{avg}} = 0.004 + 1.206 = 1.210 \text{ 秒} Tavg=0.004+1.206=1.210 秒
差不多是 1.2 秒多一点。
书里说:"略大于 1.2 秒"。
4)对比两个方案
- 方案一:花大钱把接入链路从 15Mbps 升到 100Mbps
→ 响应时间 ≈ 2 秒。 - 方案二:不加带宽,只在机构内部加一个 Web 缓存
→ 响应时间 ≈ 1.2 秒,甚至比方案一还快 ,而且:- 对外链路的流量也减小了(只有 60% 请求会出外网);
- 单位不必升级昂贵的接入链路。
所以书里说:第二种方案在时延和成本上都更优。
再往前走一步:从单个缓存到 CDN
最后一段提到:内容分发网络(CDN, Content Distribution Network)
意思是:
不只是某个单位自己弄一台缓存,而是专门的 CDN 公司在全球各地布满很多缓存服务器,把热门内容(视频、图片、网页)提前分发到各地。
这样用户访问时:
- 实际上是从最近的 CDN 节点拿数据;
- 不是每次都跑到"源站"(原始服务器)去;
- 典型例子:听歌的、看视频的(书里举了像 Akamai、Netflix 的专用 CDN 等)。
CDN 就是把 "Web 缓存" 这件事做成了一个大规模的商业服务。
1. "公网"是啥?
而客户端到原始服务器之间是比较慢的链路(穿越公网,甚至跨国)
这里的 公网 就是指 整个 Internet(因特网):
- 所有人都能连的那张全球大网;
- 网站服务器、别人的电脑、国外的机房都在这张网里;
- 路由器一跳一跳地把你的数据包送过去。
你在家打开淘宝、B 站,其实就是通过运营商,把数据发到 公网 里,再转到网站服务器那边。
2. "外网"是什么意思?
很多单位(学校、公司)接外网的链路是要花大钱租的
这里的 外网 = "外面的网络" = Internet 公网,只是站在单位的角度说的:
- 学校/公司 内部 的网络叫"内网"(下面讲);
- 内网要想访问全世界的网站,就得通过一条"接到外面 Internet 的线";
- 这条线(接入链路)通常是向电信/移动/联通等运营商 花钱租来的专线带宽;
- 带宽越大越贵,所以书里说"要花大钱租"。
简单记:
站在单位角度:
- 内网 = 我们自己单位里面的那张局域网
- 外网 = 单位外面的 Internet 公网
3. "内网"是什么?
有一个机构网络(内网)通过一条 15Mbps 的接入链路接到因特网
内网(机构网络)就是:
- 某个单位内部自己搭的网络:学校、公司、医院......
- 通常是一个 局域网(LAN):交换机、路由器把办公室、机房、教室里的电脑连在一起;
- 内网里机器之间走的是 内部链路(比如 100Mbps 的局域网),很快;
- 但从内网出去到 Internet,要经过那条 接入链路(这里是 15Mbps),这条线就是瓶颈。
你可以这样想:
- 内网:学校里的教学楼 + 实验室之间的网线 / Wi-Fi;
- 接入链路:学校出口连运营商机房的那根粗网线;
- 外网/公网:全世界的网站所在的大网。
"局域网流量强度"里的"流量"是啥?
先把几个词分开:
4.1 流量 = 每秒有多少数据在这条线里走
- 比如 1 秒钟里,传了 10 Mb 的数据,就可以说这 1 秒的"流量"是 10 Mb;
- 往往用 Mb/s、Gb/s 这类单位。
在书里,它是这么算的(大概意思就行):
"每秒来了多少个请求" × "每个请求平均多大" = 每秒总数据量
再除以"链路带宽" → 看这条线被占用了多少比例
4.2 流量强度(负载程度)
书里用的公式(你不用死记,理解就好):
流量强度 = 到达速率 × 平均业务量 链路速率 \text{流量强度} = \frac{\text{到达速率} \times \text{平均业务量}}{\text{链路速率}} 流量强度=链路速率到达速率×平均业务量
- 分子 = 每秒要"处理的工作量";
- 分母 = 这条线每秒最多能处理多少;
- 结果是 0~1 之间的数,越接近 1,说明越"挤",排队越厉害,延迟越大。
在那段例子里:
- 内部局域网:算出来是 0.15 → 很空,延迟可以忽略;
- 接入链路:算出来很接近 1 → 很挤,是瓶颈。
你可以粗暴理解:
流量强度 = "这条线有多忙"
- 0.1:很闲
- 0.5:一般
- 0.9:爆满,随时堵车
2.2.6 条件GET方法
咱们接着 Web 缓存往下看"条件 GET 方法"。其实就是解决一个很现实的小问题:
缓存里的东西会不会变旧?如果旧了怎么办?
我分成 4 块讲:
- 为什么需要"条件 GET"
- 什么叫"条件 GET"(If-Modified-Since)
- 书上的完整例子一步一步走
- 304 状态码表示什么意思、有什么好处
为什么需要"条件 GET"?
前面说过:
- 有了 Web 缓存,很多对象(网页、图片)可以直接在缓存里拿,速度很快。
- 但有一个风险:缓存里的副本也许已经过时 。
- 比如原始服务器上的图片被改了,缓存里还是老版本。
于是就有两个需求:
- 尽量用缓存(快、省流量)
- 又要确认缓存里的东西是不是最新的
HTTP 给的解决办法就是:条件 GET 。
这个机制允许缓存去问原始服务器:
"这段时间你有没有改过这个文件?
如果改过,就把最新的发给我;
如果没改过,就不用浪费带宽再传一遍。"
什么叫"条件 GET"?
书上给了一个严格的定义:
只要满足:
- 请求方法是 GET
- 首部里有 If-Modified-Since: ......
那这个请求就是一个 条件 GET 请求。
理解成大白话就是:
"如果(If)在某个时间之后被修改过(Modified-Since),
你才给我真正的数据;
如果从那之后没改过,就告诉我'没变',就行。"
这里有两个关键首部要配合使用:
-
Last-Modified:
-
服务器在响应里告诉你:
"这个文件最后一次修改的时间是 XXX。"
-
-
If-Modified-Since:
- 之后缓存再去问的时候,会把这个时间带回去:"如果从这个时间之后改过,就给我新版本。"
你可以把它想成:
- 第一次见面:服务器说"我这份文件是 9月9日 09:23:24 修改的"。
- 后面再问:缓存说"如果从 9月9日 09:23:24 之后你改过,就告诉我并给我新版;否则就说没改过。"
我们来按时间顺序走一遍书上的例子。
第一次:缓存向原始服务器要 kiwi.gif
"一个代理缓存(Web cache)代表浏览器向 Web 服务器发送请求报文"
请求:
GET /fruit/kiwi.gif HTTP/1.1
Host: www.exotiquecuisine.com
含义:
- 请求方法:GET
- 要的资源:
/fruit/kiwi.gif - 主机:
www.exotiquecuisine.com
服务器回复一个普通的 200 OK 响应:
HTTP/1.1 200 OK
Date: Sat, 3 Oct 2015 15:39:29
Server: Apache/1.3.0 (Unix)
Last-Modified: Wed, 9 Sep 2015 09:23:24
Content-Type: image/gif
(data data data ...)
重点看两个时间字段:
- Date :这次响应发出的时间
→ "我现在回复你的时间是 10 月 3 日 15:39:29" - Last-Modified :对象最后一次被修改的时间
→ "这张 kiwi.gif 最后改动是在 9 月 9 日 09:23:24"
接下来缓存会做两件事:
- 把图片数据转发给自己的浏览器客户端;
- 在本地存一份副本 ,同时把这张图片的 Last-Modified 时间也记下来。
一周后:另一个用户通过同一个缓存再访问 kiwi.gif
这时缓存里已经有 kiwi.gif 的副本,但不确定这一个星期里原始服务器有没有改过这张图。
于是缓存不会直接把老副本给用户,而是先对原始服务器发一个 条件 GET 来确认是否需要更新。
请求变成:
GET /fruit/kiwi.gif HTTP/1.1
Host: www.exotiquecuisine.com
If-Modified-Since: Wed, 9 Sep 2015 09:23:24
注意这里:
If-Modified-Since的值 正好等于前一次响应的Last-Modified值 。
也就是:"如果你在 9 月 9 日 09:23:24 之后改过它,就发给我新的对象。"
服务器收到后会做判断:
- 看看 kiwi.gif 是不是在这个时间之后被改过。
书里假设:从 9 月 9 日到现在都没有改过 。
所以服务器会返回一个特殊的响应:
HTTP/1.1 304 Not Modified
Date: Sat, 10 Oct 2015 15:39:29
Server: Apache/1.3.0 (Unix)
(empty entity body)
这里有几个关键点:
- 状态码是 304 Not Modified
- 意思是:"没改过,你本地那份还可以用。"
- 没有实体主体(empty entity body)
- 也就是说,这个响应里 不再发送图片数据本身;
- 只发了响应行和一些首部,数据量非常小。
缓存一看:
- 哦,304 → 懂了,服务器说"没改过";
- 那我就继续使用自己缓存盘里的那份 kiwi.gif;
- 并把这份数据转发给这次发起请求的浏览器。
整个过程结束,用户拿到图片,而且:
- 没有重复下载完整的图片(节省带宽);
- 延迟也更小(header 很短,传得快)。
如果服务器发现"已经改过"会怎样?
很简单:它就像第一次那样再发一次 200 OK + 新数据 + 新的 Last-Modified ,
缓存更新本地副本,再转发给浏览器。
1)解决的问题
- 只用缓存:可能用到的是过期的老内容;
- 不用缓存、每次都重新下载:浪费带宽、慢。
条件 GET 提供了一个折中方案:
- 每次访问前先问一下:"最近有没有改过?"
- 如果没改,就直接用缓存;
- 如果改了再下载新的。
这样:
- 保证用户看到的是最新的内容;
- 又尽量避免重复传输大文件。
2)对网络的好处
特别是当对象"很大"的时候(例如视频文件、高清图片):
- 如果每次都完整从服务器取一遍,浪费巨量带宽;
- 有了条件 GET,大多数情况下只需一次轻量级的头部响应(304),就可以确认没变。
所以书上说:
"包含该对象本会浪费带宽,并增加用户感受到的响应时间,特别是如果该对象很大时更是如此。"