【Linux第二十一章】http

前言 🚀

学完 socket 之后,再看 HTTP,很多细节会突然变得顺起来:为什么 HTTP 一定建立在 TCP 之上,为什么请求报文和响应报文都要强调边界,为什么浏览器明明只是在"打开网页",底层却是在按协议交换结构化文本,为什么 GET、POST、状态码、Cookie、Session 这些概念会一起出现。

HTTP 并不是一个只能传网页的协议,它本质上是一套应用层通信约定。只要客户端和服务端都遵守同一套报文格式,双方就能基于网络传输结构化数据,并在此基础上完成文件访问、表单提交、用户认证和资源跳转等一系列上层业务。

这篇文章就把你这份笔记里的主线重新串起来:先看 HTTP 为什么要存在,再看它的报文结构、请求方法、资源与 Content-Type 的关系,最后再落到状态、Cookie、Session 与认证机制上。


一. HTTP 本质上是在约定结构化数据怎么传 🧠

协议本质上是一种约定。只要通信双方都遵守同一套格式,就能把"我发出去的内容"准确还原成"你理解到的内容"。

在网络通信中,直接把内存里的结构体原样发送出去,通常并不可靠。原因并不只是"不同 Linux 机器有差异",更关键的是:

  • 不同机器可能存在字节序差异
  • 结构体可能存在内存对齐差异
  • 指针字段本身没有可跨进程、跨主机传输的意义
  • 同一种语言或编译器下的内存布局也未必长期稳定

因此,真正可用于网络传输的,不是"内存布局本身",而是经过协议定义后的字节流。这就引出了两个很重要的概念:

1.1 序列化与反序列化

序列化,就是把业务中的结构化数据,按照协议约定,转换成一段可以在网络上传输的字节流。

反序列化,就是把收到的字节流,按照同样的协议规则重新解析出来,恢复为上层可以继续使用的字段结构。

从这个角度看,HTTP 请求报文和响应报文,本质上也可以视为一种"文本协议形式的序列化结果"。它把方法、路径、版本、首部、正文这些字段按约定组织成报文,让浏览器和服务器都能正确理解。

1.2 HTTP 底层依赖 TCP

HTTP 是应用层协议,它本身不负责丢包重传、按序交付、流量控制这些底层可靠性问题。它把这些能力交给传输层的 TCP 去完成,因此:

HTTP 底层建立在 TCP 之上。

这也是为什么 HTTP 通信时,write、send 只是把数据拷贝到发送缓冲区;至于数据什么时候真正发出去、一次发多少、出错后如何补偿,本质上是由内核里的 TCP 协议栈决定的。

💡 避坑指南:
HTTP 不是"网页专属协议",而是一套建立在 TCP 之上的应用层报文约定。网页只是它最典型的应用场景之一。


二. 为什么 HTTP 一定要强调报文边界 🧱

理解 HTTP,一个特别关键的前提是:TCP 是面向字节流的,不保留应用层消息边界。

这意味着客户端即使连续发出两个请求,服务端在接收时也不一定恰好"一次读到一个完整请求"。它可能出现:

  • 一次只读到半个请求
  • 一次读到一个半请求
  • 一次读到多个请求粘在一起

这就是应用层必须自己解决"报文边界"的原因。

2.1 为什么 UDP 不会天然遇到同样的问题

UDP 面向数据报,一次发送就是一个完整报文,一次接收通常也是一个完整报文,因此边界天然存在。

但 HTTP 基于 TCP,所以必须自己定义边界规则,否则服务端根本无法判断"当前这段字节流,到底哪里才算一个完整请求"。

2.2 HTTP 是如何确定请求边界的

HTTP 报文之所以容易解析,是因为它把报文结构设计得很清晰:

  • 请求行 / 状态行
  • 多行首部字段
  • 一个空行
  • 可选正文

其中,空行 是请求头与请求体之间最关键的分隔符。也就是说,服务端通常可以先持续读取,直到读到 \r\n\r\n,从而确认请求头已经结束。

接下来如果首部里还带有 Content-Length,就能继续按长度把正文读完。于是,一个完整的 HTTP 报文边界就被确定下来了。


三. HTTP 请求和响应长什么样 🗺️

3.1 请求报文的组成

一个典型的 HTTP 请求报文可以拆成四个部分:

  1. 请求行
  2. 若干请求头
  3. 一个空行
  4. 可选请求正文

请求行一般长这样:

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

它包含三部分:

  • 方法:如 GET、POST
  • 资源路径:如 /index.html
  • 协议版本:如 HTTP/1.1

3.2 响应报文的组成

服务端返回的响应报文结构与请求报文类似,也可以分成四部分:

  1. 状态行
  2. 若干响应头
  3. 一个空行
  4. 响应正文

状态行一般长这样:

http 复制代码
HTTP/1.1 200 OK

它包含三部分:

  • 协议版本
  • 状态码
  • 状态描述短语

3.3 版本号到底表示什么

报文里出现的版本号,表示这次通信使用或声明支持的 HTTP 协议版本。实际协商和行为还要结合客户端与服务端的实现能力去看,但至少在报文本身,它承担的是协议语义说明作用。

从学习角度看,可以先抓住三个常见版本:

  • HTTP/1.0:通常默认短连接
  • HTTP/1.1:默认支持持久连接
  • HTTP/2:在连接复用和传输效率上进一步增强

四. 浏览器看到的"网页",本质上只是服务器返回的资源 🔍

很多初学者会下意识觉得:浏览器访问网页,就是服务器"显示页面给我看"。

更准确地说,服务器做的事情其实是:

把某个资源按 HTTP 响应报文的方式返回给浏览器。

至于最终把返回内容解释成网页、图片、样式、脚本还是别的东西,是浏览器根据收到的数据类型去完成的。

4.1 HTTP 不只传网页

HTTP 被称为"超文本传输协议",但它能传输的远不止文本。只要服务端告诉浏览器"这份正文是什么类型",浏览器就能按对应方式解释:

  • text/html:按网页解释
  • text/css:按样式解释
  • application/javascript:按脚本解释
  • image/png:按图片解释
  • application/json:按结构化数据解释

因此,HTTP 更准确的理解方式是:

它负责传输资源;资源的含义由 Content-Type 和浏览器解释能力共同决定。

4.2 后缀与 Content-Type 的关系

笔记里提到"后缀决定文件类型",这个说法在教学上是方便理解的,但更准确地说:

  • 文件后缀通常是资源类型的一个线索
  • 服务端会根据映射关系设置 Content-Type
  • 浏览器再根据 Content-Type 去决定如何解释正文

也就是说,真正参与协议层语义表达的是 Content-Type 响应头。后缀常常只是服务端生成这个头部时的参考依据之一。

4.3 为什么图片不能随便用 getline 读取

图片这类资源通常是二进制数据,而 getline 适合按文本行读取。若用文本方式读取二进制内容,可能会:

  • 提前遇到特殊字节
  • 改变原始内容
  • 读取得不完整

一旦二进制正文被破坏,浏览器自然无法正确渲染图片。因此,处理静态资源时必须区分:

  • 文本资源可以按文本逻辑处理
  • 二进制资源要按二进制方式读取和发送

💡 避坑指南:

浏览器显示网页,并不是服务器"帮你渲染好了页面"。
服务器返回的是资源,浏览器负责解释和渲染。


五. GET 和 POST:都是请求方法,但语义和传参位置不同 🧩

5.1 GET 一般通过 URL 携带参数

GET 最常见的使用方式,是把参数拼在 URL 后面,通过查询串传递,例如:

http 复制代码
GET /search?wd=http HTTP/1.1

浏览器里的表单如果不显式写 method,默认也是 GET。用户在表单里输入的内容,最终会被浏览器编码后追加到 URL 上。

5.2 POST 一般通过请求正文传参

POST 则更常见于把参数放进请求正文里。请求头里会通过 Content-Length、Content-Type 等字段告诉服务端:

  • 正文有多长
  • 正文采用什么编码格式

5.3 POST 比 GET 更安全吗

笔记里提到"GET 私密性较差,POST 更好,但都容易抓取,增强安全性需要加密",这个结论方向是对的,但需要说得更准确一些。

GET 和 POST 的核心差异首先是语义与参数承载位置 ,而不是安全性本身。POST 把参数放在正文里,确实不会直接暴露在地址栏中,但:

  • 明文 HTTP 下,正文和 URL 一样都能被抓包看到
  • 真正提高安全性,依赖的是 HTTPS/TLS 加密
  • 是否敏感,还与日志、浏览器历史、代理缓存等场景有关

所以更严谨的说法是:

POST 只是比 GET 更不容易把参数直接暴露在地址栏,不等于天然安全。真正的安全保障依赖加密。

5.4 /s、表单提交与服务处理

表单提交到某个路径,例如 /s,本质上是浏览器向服务器请求某个资源路径或服务入口。服务端收到请求后,再把参数交给对应业务逻辑处理。

从系统视角看,这不应简单说成"这就是进程间通信"。更准确地说,它是:

  • 浏览器通过 HTTP 把参数发给服务器
  • 服务器进程解析请求
  • 服务器内部再把参数交给对应业务模块或程序处理

六. 状态码不是装饰,而是响应语义的一部分 💻

状态码是 HTTP 响应里非常重要的一部分,它用来告诉客户端:"这次请求处理到了什么结果"。

常见分类如下:

类别 含义
1xx 信息状态码
2xx 成功
3xx 重定向
4xx 客户端错误
5xx 服务端错误

6.1 状态码需要规范使用

状态码本质上属于协议约定的一部分。虽然现实里可能存在"服务端返回码不规范"的情况,但从设计和实现角度看,状态码应该准确表达处理结果,否则会让浏览器、代理、中间件和前端逻辑都难以做出正确反应。

6.2 重定向为什么一定要配合 Location

当服务端返回 3xx 状态码时,通常还要带上 Location 响应头,告诉客户端"接下来应该跳转到哪里"。

例如:

  • 301:永久重定向
  • 302:临时重定向

比如一个旧网站已经迁移到新地址,那么服务端就可以返回 301 + Location,通知浏览器后续访问新地址。这不仅影响当前跳转,也会影响搜索引擎和浏览器缓存对地址的记忆。


七. HTTP 的两个经典特征:无状态、但可以做会话管理 ⚠️

7.1 HTTP 为什么常被说成"无状态"

说 HTTP 无状态,指的是:

协议本身不会自动记住"上一次请求"和"下一次请求"属于同一个用户会话。

也就是说,单看协议层,每个请求都是相对独立的。服务器不能只靠 HTTP 本身天然知道"这是不是刚才那个已经登录过的用户"。

这里需要顺手澄清一个容易混淆的点:

  • HTTP 底层依赖 TCP
  • TCP 可以保持连接
  • 但有连接 不等于有状态会话

所以"HTTP 无状态"说的是应用层身份语义,不是说底层绝对没有 TCP 连接存在。

7.2 浏览器缓存和 HTTP 无状态不是同一件事

浏览器确实可能缓存历史资源,但"缓存"解决的是资源复用与性能问题;"无状态"讨论的是服务器是否天然记住用户身份。这两件事有关联,但不是同一个概念。


很多网站需要根据用户身份区分权限,例如:

  • 是否已经登录
  • 能不能查看某个页面
  • 能不能操作某项资源

但 HTTP 本身无状态,因此服务器必须借助额外机制,把"这是同一个用户"这件事维持住。Cookie 就是最基础的会话管理手段之一。

服务端在响应里通过 Set-Cookie 告诉浏览器保存一段小数据;浏览器后续访问同一站点、同一路径或满足域规则时,会自动把对应 Cookie 放回请求头中。

常见相关头部有:

  • Set-Cookie
  • Cookie

这样服务端就能根据浏览器带回来的信息,识别这个请求来自谁。

笔记里提到"cookie 是内存级的",这个说法需要修正。更准确地说:

  • 会话 Cookie:浏览器关闭后失效,通常只在当前会话内保留
  • 持久 Cookie:会根据过期时间写入磁盘,在更长时间内有效

所以 Cookie 并不天然只存在内存里,它可以是会话级,也可以是持久化的。

8.4 为什么又要有 Session

如果把用户名、密码、权限这类敏感信息直接放进 Cookie,风险会非常高。更常见的做法是:

  1. 用户登录成功后,服务器端创建一个 session
  2. 服务器为这个 session 生成唯一的 session id
  3. 把这个 session id 放进 Cookie
  4. 客户端后续请求带上该 Cookie
  5. 服务端通过 session id 找到对应会话数据,完成身份认证

这样浏览器保存的通常只是一个标识,而真正敏感的用户信息留在服务端管理。

8.5 Session 的价值与边界

Session 的价值在于:

  • 不把敏感用户数据直接暴露给客户端
  • 可以在服务端集中控制会话有效期
  • 可以结合登录地点、时间、设备异常等策略使会话失效

但也要清楚边界:

  • 如果 session id 被窃取,仍然可能发生会话劫持
  • 因此真正的安全仍要依赖 HTTPS、安全属性配置、过期策略和服务端校验

💡 避坑指南:
Cookie 不是"存用户隐私数据的地方",更常见的是存放一个会话标识。
真正的认证状态通常由服务端 Session 维护。


九. 常见 HTTP Header 应该怎么看 📌

学习 HTTP 时,不要把头字段当成一堆要死记硬背的字符串。更好的方式是把它们按职责来理解。

9.1 描述正文的头

  • Content-Type:正文数据类型
  • Content-Length:正文长度

9.2 描述目标与环境的头

  • Host:当前请求要访问哪个主机、哪个端口
  • User-Agent:客户端环境信息
  • Referer:当前请求是从哪个页面跳转过来的

9.3 参与重定向和会话管理的头

  • Location:告诉客户端下一步跳到哪里
  • Cookie:客户端携带回来的会话小数据
  • Set-Cookie:服务端下发给浏览器保存的数据

总结 📝

HTTP 真正难的地方,不在于背出几种请求方法或几个状态码,而在于把它看成一整套完整的应用层通信规则:它通过文本化、结构化的报文格式,把请求和响应组织起来,再借助 TCP 的可靠传输能力,把资源、参数、状态和认证信息稳定地在客户端与服务端之间流动。

顺着这条主线再回头看这份笔记,很多知识点其实都在解决同一个问题:

  • 为什么不能直接传结构体,而要序列化
  • 为什么 HTTP 报文必须能被正确切分
  • 为什么 GET 和 POST 不只是"写法不同"
  • 为什么状态码和 Location 要配合
  • 为什么 HTTP 无状态,却还能做登录认证
  • 为什么 Cookie 和 Session 总是一起出现

最终可以把 HTTP 归纳成一句话:

HTTP 是建立在 TCP 之上的应用层协议,它用结构化报文完成资源访问、参数传递、状态表达与会话管理。

把这个总框架搭起来之后,后面继续学习 HTTPS、CGI、静态资源服务器、前后端交互甚至浏览器缓存机制时,都会自然落到同一条理解主线上。

相关推荐
雯宝1 天前
mnt_init 函数
linux
律宏阔1 天前
WSL Docker 端口明明空闲,但就是绑不上端口
linux·windows
律宏阔1 天前
WSL 突然断网,无法 ping 内网或外网
linux·windows
穷人小水滴1 天前
用容器编译 VirtualBox 虚拟机软件 (ArchLinux, podman)
linux·容器·virtualbox
乱码三千1 天前
如何优雅地直连无公网 IP 的远程 GPU 服务器
linux·人工智能·程序员
yunwei371 天前
使用 eBPF 跟踪 Nginx 请求
linux·后端·性能优化
yunwei371 天前
使用 eBPF 跟踪 MySQL 查询
linux·后端·性能优化
yunwei371 天前
eBPF 示例教程:使用 XDP 捕获 TCP 信息
linux·后端·性能优化
judezh1 天前
api 容器一直 unhealthy?我把一个 Agent 运行时的健康检查逐条拆了,查出四个问题
运维·docker
GeW1 天前
制造业高端化,为什么必须重估Linux和数据库?
linux