Linux 网络:HTTP(一)基础与报文结构

Linux 网络学习:HTTP(一)基础与报文结构

引言

TCP 回显程序已经可以完成"客户端发送字符串,服务器再把字符串返回"的过程。但浏览器连接服务器后,需要的远不止回显:它要告诉服务器自己想访问哪个页面,还要判断返回的是网页、图片,还是一段错误信息。

这些含义由应用层协议约定。前面网络计算器使用 JSON 表示计算请求,HTTP 则提供了一套通用的请求与响应规则。浏览器和服务器遵守相同规则,就能理解彼此发送的字节。

一、HTTP 在网络通信中的位置

HTTP(Hypertext Transfer Protocol,超文本传输协议)是一种应用层协议,围绕资源的请求与响应组织通信。"超文本"可以通过链接关联其他资源,实际传输的数据也可以是图片、音频、文件和接口结果。

在 HTTP/1.1 的常见场景中,可以把通信过程理解为:

text 复制代码
浏览器:生成 HTTP 请求 → TCP 发送字节
                              ↓
服务器:TCP 接收字节 → 解析请求 → 处理资源
                              ↓
浏览器:解析并使用响应 ← TCP ← 生成 HTTP 响应

TCP 处理连接和有序字节传输;HTTP 说明请求什么资源、处理结果是什么,以及数据应该怎样解释。HTTP 的请求与响应语义见 RFC 9110。

这里使用 HTTP/1.1 的文本报文学习协议。HTTP/2 使用二进制帧,HTTP/3 则基于 QUIC,后者使用 UDP,因此"所有 HTTP 都运行在 TCP 上"并不准确。版本演进改变了传输与组织方式,资源、方法和状态码等基本概念仍然保留。HTTP/2 标准、HTTP/3 标准分别定义了它们的机制。

二、一次网页访问包含的工作

假设用户访问一个网站首页。在需要建立新连接且没有缓存命中的简化场景下,浏览器先确定服务器地址和端口,建立连接,再发送请求。服务器解析目标资源,读取文件或执行业务,最后返回响应。

如果 URL 使用域名,通常还需要通过 DNS(Domain Name System,域名系统)获取对应地址;若地址已经缓存,则可能直接使用缓存结果。访问 HTTPS 时,还需要完成相应的安全连接过程。

浏览器拿到首页,并不代表整张页面已经加载完成。首页可能包含:

html 复制代码
<h1>Linux 学习笔记</h1>
<img src="/image/th.jpg" alt="示例图片">
<img src="/image/xlh.jpg" alt="另一张图片">

这份 HTML(HyperText Markup Language,超文本标记语言)只描述页面结构,并引用两张图片。浏览器需要另外获取图片,页面引用的样式表和脚本也可能触发请求。因此,打开一个页面,往往会看到多条 HTTP 请求。

服务器不一定把图片内容直接放进首页响应中。一份响应通常对应一次请求处理;浏览器根据页面里的引用继续取资源,再组合出最终看到的内容。MDN 的 HTTP 概述介绍了这一资源获取过程。

三、URL 与资源定位

URL(Uniform Resource Locator,统一资源定位符)用于描述资源的位置。例如:

text 复制代码
http://example.com:8080/blog/index.html?tag=linux&page=1#intro

它可以拆成几个部分:

部分 示例 作用
方案 http 指定访问方案
主机 example.com 指定目标主机
端口 8080 指定服务入口
路径 /blog/index.html 标识要访问的资源
查询字符串 tag=linux&page=1 向资源传递查询信息
片段 intro 标识资源中的一个位置

HTTP 默认端口为 80,HTTPS 默认端口为 443。使用其他端口时,需要在地址中明确写出。

浏览器直接向源站发送普通 HTTP/1.1 请求时,请求行里的目标通常是路径与查询字符串:

http 复制代码
GET /blog/index.html?tag=linux&page=1 HTTP/1.1
Host: example.com:8080

其中没有 #intro。片段通常由浏览器在获取资源后处理,例如滚动到页面某个标题,不作为该资源请求的目标内容发送给服务器。URI 片段说明明确区分了这一点。

路径也不等于服务器操作系统中的绝对文件路径。若 Web 根目录设为 wwwroot,服务端可以把 /index.html 映射到 wwwroot/index.html,把 / 映射到默认首页。这个映射属于服务器实现。

路径还可以代表业务接口,例如 /Search。服务器匹配该路径后调用搜索函数,不需要存在一个名为 Search 的磁盘文件。资源地址与本地文件之间,没有必须一一对应的关系。

查询字符串与请求正文也应分开保存。? 后面的 tag=linux 属于请求目标的一部分;正文位于报头后的空行之后。实现时即使把二者都放进一个字符串成员,也不能据此把它们当成同一段协议内容。

四、HTTP 请求的结构

HTTP/1.1 请求由请求行、若干报头字段、空行和可选正文组成。请求行说明要做什么,报头补充描述信息,正文承载需要提交的数据。

http 复制代码
GET /index.html HTTP/1.1
Host: 127.0.0.1:8080
Accept: text/html
Connection: close

这是一份没有正文的请求。第一行的三个部分分别是:

  • GET:请求方法,这里表示获取目标资源的表示。
  • /index.html:请求目标。
  • HTTP/1.1:请求采用的协议版本。

随后每行是一个报头字段。Host 指定目标主机信息,Accept 表达客户端愿意接收的媒体类型,Connection: close 表示希望在本次交换后关闭连接。

这些示例为了阅读方便显示成普通换行。真正构造 HTTP/1.1 报文时,起始行和报头行使用 \r\n,即回车与换行两个字节。最后一条报头行之后再写一个 \r\n,便出现结束报头区的空行。这一格式见 RFC 9112 的报文定义。

空行必须存在,即使没有正文。接收方依靠它判断报头何时结束,不能通过"没有其他内容了"来猜。

再看一份提交 JSON 的请求:

http 复制代码
POST /api/calc HTTP/1.1
Host: 127.0.0.1:8080
Content-Type: application/json
Content-Length: 33
Connection: close

{"left":10,"right":20,"oper":"+"}

这里正文恰好是前面计算器使用的 JSON。它的序列化规则没有消失,而是被放进 HTTP 的正文中。HTTP 负责组织外层消息,JSON 负责表达里面的计算字段。

方法与正文格式也不是绑定关系。POST 可以提交 JSON,也可以提交表单或其他数据,接收方需要结合报头和接口约定解释。方法的具体语义将在第二篇展开。

五、HTTP 响应的结构

响应与请求的外观相似,只是第一行变成状态行:

http 复制代码
HTTP/1.1 200 OK
Content-Type: text/plain; charset=utf-8
Content-Length: 10
Connection: close

Hello HTTP

第一行由协议版本、三位状态码和原因短语组成。这里 200 表示请求成功,OK 是便于人阅读的描述。程序判断结果时应关注状态码,不能只比较描述字符串。

Content-Type 描述正文类型,Content-Length 给出正文长度。空行之后的 Hello HTTP 才是正文,本例不包含末尾换行,长度为 10 字节。

把正文改成 HTML,并设置相应类型,浏览器便能按网页内容处理:

http 复制代码
Content-Type: text/html; charset=utf-8

但只发送 HTML 字符串,并不等于已经构造出规范的 HTTP/1.1 响应。状态行、报头、空行和正文组织起来,客户端才能明确知道处理结果与内容边界。请求和响应的共同结构可参考 MDN 的 HTTP 消息说明。

响应正文也不一定是网页。获取图片可以返回图片字节;调用计算接口可以返回 JSON;资源不存在时则可能返回错误页面。HTTP 报文本身与它承载的内容格式,应当分别理解。

六、三个常用报头

1. Host

网络连接已经指定了服务器地址,为什么还需要 Host?因为同一台服务器、同一个 IP 地址,可以为多个站点提供服务。服务器可以结合 Host 选择处理请求的站点。

例如,a.example.com 和 b.example.com 可以解析到同一个 IP,但请求中的 Host 不同,服务端就能据此区分。HTTP/1.1 请求必须包含 Host;端口不是默认端口时,也应正确携带端口信息。Host 文档说明了相应要求。

2. Content-Type

Content-Type 回答"正文是什么类型"。常见取值包括:

正文内容 媒体类型
HTML 网页 text/html
普通文本 text/plain
JSON 数据 application/json
JPEG 图片 image/jpeg
PNG 图片 image/png

类型声明不会自动转换正文。把 JPEG 图片字节标成 text/html,图片不会因此变成网页,只会使客户端获得错误的解释信息。静态文件服务器可以根据扩展名选择合适的媒体类型,再把文件字节放入正文。

文本还可能需要字符编码参数。charset=utf-8 表示按 UTF-8 解释文本,不能把"内容是 HTML"和"字符怎样编码"混为一谈。Content-Type 文档给出了类型和参数的用法。

3. Content-Length

Content-Length 表示正文的字节长度,不是整个 HTTP 报文长度,也不是显示出来的汉字数量。若正文以 UTF-8 编码,汉字通常占多个字节;正文发生压缩时,还需要按实际传输内容计算。

使用 C++ 的 std::string 保存最终正文时,可以用 body.size() 得到字节数。应在正文生成完成后计算,避免正文后来改变,长度字段却仍是旧值。Content-Length 文档明确以字节定义这个字段。

若声明长度大于实际发送量,客户端可能继续等待;若声明更小,剩余字节可能干扰后面的消息解析。保持连接复用时,这种错误尤其容易让下一份响应也无法被正确识别。

七、空行与正文完整性

报头结束和整份消息结束,是两个不同的判断。

text 复制代码
先找到 \r\n\r\n
        ↓
报头已经完整
        ↓
根据协议规则确定正文长度
        ↓
正文收齐后,才能处理完整消息

例如,请求声明 Content-Length: 33,但当前只收到正文的 12 字节,就应该保留已有数据,继续接收剩下的 21 字节。

因此,不能把一次 recv 的结果直接当成完整 HTTP 请求。TCP 可能把报头拆到多次接收,也可能一次返回完整请求加下一条请求的一部分。上一节自定义计算器协议中使用的持久接收缓冲区,在这里仍然需要。

HTTP/1.1 还支持分块传输等正文定界方式,并存在按请求方法、响应状态判断无正文的情况。Content-Length 不是所有消息唯一的定界规则。实现教学服务器时可以先支持明确受限的报文,但遇到不支持的形式要拒绝,不能按已有规则硬猜。

尤其不能仅凭 GET 或 POST 的名字判断整份消息长度。请求目标、报头和正文有各自的位置与规则,方法语义和字节边界应分别处理。

八、构造一份简单响应

把报文结构落实到代码,核心仍然是序列化。下面的函数构造一份返回 HTML 的固定成功响应:

cpp 复制代码
#include <string>

std::string BuildHtmlResponse(const std::string& body) {
    std::string response = "HTTP/1.1 200 OK\r\n";
    response += "Content-Type: text/html; charset=utf-8\r\n";
    response += "Content-Length: ";
    response += std::to_string(body.size());
    response += "\r\nConnection: close\r\n";
    response += "\r\n";
    response += body;
    return response;
}

调用时可以传入 "<h1>Hello HTTP</h1>",得到包含状态行、报头、空行和正文的完整字符串。正文里即使出现换行,也不会重新开始一个报头,因为接收方已经通过空行进入正文区域。

这个函数只负责生成报文字节。外层服务端还要先收齐并解析请求,处理适用的方法和资源,再循环发送响应,最后按 Connection: close 的约定关闭连接。不能认为构造好字符串,一次 send 就一定发送完了。

在程序结构上,可以让 HttpRequest 保存解析后的方法、目标、报头和正文,让 HttpResponse 保存状态码、响应报头和正文,再由 Serialize 负责拼接。网络收发与协议格式分开后,修改网页内容就不需要改动连接处理代码。

九、观察实际的请求与响应

本机已有 HTTP 服务监听 8080 端口时,可以在 Linux 终端使用:

bash 复制代码
curl --http1.1 --noproxy '*' -v http://127.0.0.1:8080/

--http1.1 指定使用 HTTP/1.1,--noproxy '*' 避免本次请求经过代理,-v 显示详细交互信息。输出中的 > 标记发送的报头行,< 标记接收的报头行;这些符号是 curl 的展示标记,不属于协议报文。curl 官方手册说明了这些选项。

观察时先确认请求目标和 Host,再看响应状态、Content-Type 与 Content-Length。随后核对正文内容是否与类型、长度一致。

如果连接被拒绝,应先排查监听端口和连接问题;如果已经收到 404,说明服务器返回了 HTTP 层的结果,需要检查请求路径及资源映射。浏览器里显示错误页面,不代表 TCP 没有建立成功。

十、无状态与连接复用

HTTP 的"无状态"表示协议不会自动维护业务会话,不能据此认定用户上一次做过什么。网站的登录状态,需要应用层通过 Cookie、会话或令牌等机制实现,第二篇会继续讨论。

连接复用则是另一个层次的问题。HTTP/1.1 默认支持持久连接,可以在同一条连接上进行多次请求与响应。保留 TCP 连接不等于保留登录身份,关闭连接也不意味着用户退出登录。HTTP 连接管理说明解释了持久连接与短连接的区别。

HTTPS 使用 TLS(Transport Layer Security,传输层安全协议)提供通信保护。仅把 URL 中的 http 改为 https,或者把监听端口改成 443,都不能让普通 TCP 服务器自动具备 HTTPS 能力。

结语

理解 HTTP/1.1,可以从一份完整报文入手:请求行表达目标与方法,状态行表达处理结果,报头补充描述,空行分开报头与正文,长度等定界规则说明消息何时完整。

URL、HTTP 与 TCP 也各有职责:URL 标识资源位置,HTTP 组织资源交互,TCP 在本例中传递字节。第二篇将进一步讨论请求方法、状态码、重定向以及登录状态;第三篇再把这些规则落实到 Linux HTTP 服务器的解析、路由和资源返回中。

相关推荐
Iruoyaoxh31 分钟前
linux-进程概念
linux
乐橙开放平台36 分钟前
门店监控想集中看?乐橙含光支持多品牌摄像头跨店管理
网络·笔记·物联网·自动化·音视频·智能家居
weixin_4173968342 分钟前
监控画面卡顿与录像缺失:码率、NVR与存储容量的排查顺序
前端·网络·技术·酒店·综合布线·弱电
pt104343 分钟前
Cisco设备网络安全系列-10:思科混合办公、终端、物联网与多云数据中心
网络·物联网·安全
权球物联国际流量服务1 小时前
比利时物联网卡选型:至少确认Band20,4个频段+2种网络制式怎么配?
网络·物联网
yunwei371 小时前
eBPF 实例教程:跟踪 Intel NPU 内核驱动操作
linux·后端·性能优化
邪修king4 小时前
Re:Linux 系统篇(三十五):进程间通信Chapter2:从匿名管道到进程池:Linux 进程间通信实战(深度理解「先描述,再组织」)
java·linux·运维
2301_777998344 小时前
ARP协议进阶:ARP缓存、RARP与ARP欺骗原理解析
linux·服务器·网络·网络协议·缓存
m0_715674439 小时前
可视化易用·低误报率·跨区域部署:知源-AI数据分类分级系统在金融行业的落地实践
网络·安全·网络安全