文章目录
-
- HTTP协议深度解析(一):协议基础与请求响应格式
- 一、HTTP协议是什么
-
- [1.1 超文本传输协议](#1.1 超文本传输协议)
- [1.2 HTTP的工作流程](#1.2 HTTP的工作流程)
- [1.3 HTTP的特点](#1.3 HTTP的特点)
- [1.4 HTTP与TCP的关系](#1.4 HTTP与TCP的关系)
- 二、URL详解
-
- [2.1 URL的组成](#2.1 URL的组成)
- [2.2 协议部分](#2.2 协议部分)
- [2.3 域名与端口](#2.3 域名与端口)
- [2.4 路径部分](#2.4 路径部分)
- [2.5 查询参数(Query String)](#2.5 查询参数(Query String))
- [2.6 片段标识符(Fragment)](#2.6 片段标识符(Fragment))
- 三、urlencode和urldecode
-
- [3.1 为什么需要urlencode](#3.1 为什么需要urlencode)
- [3.2 urlencode的规则](#3.2 urlencode的规则)
- [3.3 实际例子](#3.3 实际例子)
- [3.4 urldecode](#3.4 urldecode)
- [3.5 在线工具](#3.5 在线工具)
- 四、HTTP请求格式
-
- [4.1 请求格式概览](#4.1 请求格式概览)
- [4.2 首行(Request Line)](#4.2 首行(Request Line))
- [4.3 Header(请求头)](#4.3 Header(请求头))
- [4.4 Body(请求体)](#4.4 Body(请求体))
- [4.5 完整示例](#4.5 完整示例)
- 五、HTTP响应格式
-
- [5.1 响应格式概览](#5.1 响应格式概览)
- [5.2 首行(Status Line)](#5.2 首行(Status Line))
- [5.3 Header(响应头)](#5.3 Header(响应头))
- [5.4 Body(响应体)](#5.4 Body(响应体))
- [5.5 完整示例](#5.5 完整示例)
- [六、用TCP Socket验证HTTP协议](#六、用TCP Socket验证HTTP协议)
-
- [6.1 最简单的HTTP服务器](#6.1 最简单的HTTP服务器)
- [6.2 编译运行](#6.2 编译运行)
- [6.3 浏览器测试](#6.3 浏览器测试)
- [6.4 favicon.ico请求](#6.4 favicon.ico请求)
- 七、Connection字段深度解析
-
- [7.1 持久连接的必要性](#7.1 持久连接的必要性)
- [7.2 HTTP/1.1的持久连接](#7.2 HTTP/1.1的持久连接)
- [7.3 Connection字段的作用](#7.3 Connection字段的作用)
- [7.4 测试持久连接](#7.4 测试持久连接)
- 八、本篇总结
-
- [8.1 核心要点](#8.1 核心要点)
- [8.2 容易混淆的点](#8.2 容易混淆的点)
HTTP协议深度解析(一):协议基础与请求响应格式
💬 开篇:前面两篇完成了自定义应用层协议的设计与实现,从协议格式到序列化,从报文边界到网络计算器,掌握了协议设计的核心思想。但在实际工作中,我们很少从零设计协议,更多的是使用现成的成熟协议。HTTP协议就是互联网最重要的应用层协议之一,是浏览器与服务器通信的基础。这一篇从HTTP的基本概念讲起,深入剖析URL的构成、urlencode的必要性、HTTP请求和响应的格式,然后用TCP Socket手写一个HTTP服务器,用浏览器验证我们的理解。理解了HTTP,就理解了互联网的大部分通信机制。
👍 点赞、收藏与分享:这篇会把HTTP的核心知识讲透,从协议格式到实际验证,每个细节都会讲清楚。如果对你有帮助,请点赞收藏!
🚀 循序渐进:从HTTP是什么开始,到URL详解,到urlencode,到请求响应格式,到手写HTTP服务器验证,一步步理解HTTP的本质。
一、HTTP协议是什么
1.1 超文本传输协议
HTTP(HyperText Transfer Protocol,超文本传输协议)是互联网最重要的应用层协议。
核心作用:定义了客户端(如浏览器)与服务器之间如何通信,以交换或传输超文本(如HTML文档)。
你在浏览器地址栏输入http://www.baidu.com,按下回车,浏览器显示百度首页------这个过程就是HTTP协议在工作。
1.2 HTTP的工作流程
bash
1. 浏览器(客户端)构造HTTP请求
↓
2. 通过TCP连接发送给服务器
↓
3. 服务器解析HTTP请求
↓
4. 服务器处理请求(读取文件、查询数据库等)
↓
5. 服务器构造HTTP响应
↓
6. 通过TCP连接发送给浏览器
↓
7. 浏览器解析HTTP响应
↓
8. 浏览器渲染HTML页面
1.3 HTTP的特点
无连接:http协议本身不维护连接状态/会话语义,每次请求都需要建立新的TCP连接(HTTP/1.0),或者复用连接(HTTP/1.1的持久连接)。
无状态:服务器不会保存客户端的状态信息。两次请求之间,服务器不知道是不是同一个客户端。
这个特点看起来是缺点,但其实是优点:
- 简化了服务器设计
- 提高了并发处理能力
- 如果需要保存状态,可以用Cookie/Session等技术
基于请求-响应模型:"HTTP/1.x 基本是请求驱动;服务器不能在没有客户端请求的情况下凭空发起响应。但可以通过长连接、长轮询、SSE 等实现持续输出。"
1.4 HTTP与TCP的关系
HTTP是应用层协议,TCP是传输层协议。
bash
应用层:HTTP协议(定义请求和响应的格式)
↓
传输层:TCP协议(可靠传输、流量控制、拥塞控制)
↓
网络层:IP协议(路由、寻址)
↓
链路层:以太网等(物理传输)
HTTP依赖TCP来保证数据可靠传输。浏览器发送HTTP请求时,先建立TCP连接(三次握手),然后在TCP连接上传输HTTP数据。
二、URL详解
2.1 URL的组成
URL(Uniform Resource Locator,统一资源定位符),俗称"网址"。
完整格式:
bash
协议://域名:端口/路径?查询参数#片段标识符
示例:
bash
http://www.example.com:8080/index.html?name=张三&age=20#section1
逐个拆解:
| 部分 | 值 | 含义 |
|---|---|---|
| 协议 | http | 使用HTTP协议 |
| 域名 | www.example.com | 服务器地址 |
| 端口 | 8080 | 服务器端口号 |
| 路径 | /index.html | 资源路径 |
| 查询参数 | name=张三&age=20 | 键值对参数 |
| 片段标识符 | section1 | 页面内锚点 |
2.2 协议部分
常见协议:
http:超文本传输协议(明文传输)https:HTTP+SSL/TLS(加密传输)ftp:文件传输协议file:本地文件访问
示例:
http://www.baidu.com # HTTP协议
https://www.baidu.com # HTTPS协议
2.3 域名与端口
域名:服务器的"地址",会通过DNS解析成IP地址。
比如在一次解析中:
www.baidu.com → 14.215.177.38
端口:默认端口可以省略。
| 协议 | 默认端口 |
|---|---|
| HTTP | 80 |
| HTTPS | 443 |
| FTP | 21 |
所以:
bash
[http://www.baidu.com](http://www.baidu.com) 等价于 [http://www.baidu.com:80](http://www.baidu.com:80)
[https://www.baidu.com](https://www.baidu.com) 等价于 [https://www.baidu.com:443](https://www.baidu.com:443)
2.4 路径部分
路径指定了要访问的资源。
bash
[http://www.example.com/index.html](http://www.example.com/index.html) # 访问根目录下的index.html
[http://www.example.com/images/logo.png](http://www.example.com/images/logo.png) # 访问images目录下的logo.png
[http://www.example.com/api/users](http://www.example.com/api/users) # 访问API接口
2.5 查询参数(Query String)
查询参数用?开头,多个参数用&连接。
格式:key1=value1&key2=value2
示例:
bash
[http://www.example.com/search?keyword=Linux&page=2](http://www.example.com/search?keyword=Linux&page=2)
服务器会解析出:
bash
keyword = "Linux"
page = "2"
2.6 片段标识符(Fragment)
片段标识符用#开头,用于定位页面内的锚点。
bash
[http://www.example.com/article.html#section2](http://www.example.com/article.html#section2)
重要:片段标识符不会发送给服务器,只在浏览器端使用。
浏览器会:
- 请求
http://www.example.com/article.html(不包含#section2) - 收到页面后,自动滚动到id为section2的元素
三、urlencode和urldecode
3.1 为什么需要urlencode
URL中有些字符有特殊含义:
| 字符 | 含义 |
|---|---|
/ |
路径分隔符 |
? |
查询参数开始 |
& |
参数分隔符 |
= |
键值对分隔符 |
# |
片段标识符开始 |
| 空格 | 分隔符 |
如果参数值包含这些字符,就会导致歧义。
例如:
bash
[http://www.example.com/search?keyword=C++编程](http://www.example.com/search?keyword=C++编程)
问题:
- 在 x-www-form-urlencoded 编码里,空格会编码成 +;因此如果参数值真的包含 +,应编码为 %2B,避免歧义。
- URL 中非 ASCII 字符应按 UTF-8 bytes 做 percent-encoding,保证一致性与兼容性。
3.2 urlencode的规则
转义规则 :将需要转码的字符转为16进制,然后加上%前缀。
步骤:
- 获取字符的ASCII码(或UTF-8编码)
- 转成16进制
- 前面加上
%
示例:
| 原字符 | ASCII码 | 16进制 | urlencode结果 |
|---|---|---|---|
| 空格 | 32 | 0x20 | %20 |
+ |
43 | 0x2B | %2B |
/ |
47 | 0x2F | %2F |
? |
63 | 0x3F | %3F |
& |
38 | 0x26 | %26 |
= |
61 | 0x3D | %3D |
中文字符(UTF-8编码):
bash
"编" 的UTF-8编码:E7 BC 96
urlencode结果:%E7%BC%96
"程" 的UTF-8编码:E7 A8 8B
urlencode结果:%E7%A8%8B
3.3 实际例子
原始URL:
bash
[http://www.example.com/search?keyword=C++](http://www.example.com/search?keyword=C++) 编程
urlencode后:
bash
[http://www.example.com/search?keyword=C%2B%2B%20%E7%BC%96%E7%A8%8B](http://www.example.com/search?keyword=C%2B%2B%20%E7%BC%96%E7%A8%8B)
拆解:
C:不需要转义,保持C+:转义成%2B+:转义成%2B- 空格:转义成%20
编:转义成%E7%BC%96程:转义成%E7%A8%8B
3.4 urldecode
urldecode是urlencode的逆过程。
步骤:
- 找到
%开头的部分 - 提取后面两个16进制字符
- 转成10进制,得到ASCII码
- 转成字符
示例:
bash
%2B → 0x2B → 43 → '+'
%20 → 0x20 → 32 → ' '(空格)
%E7%BC%96 → 编
3.5 在线工具
可以用在线工具测试urlencode:
- https://www.urlencoder.org/
- 或者用curl命令:
bash
# urlencode
echo -n "C++ 编程" | curl -Gso /dev/null -w %{url_effective} --data-urlencode @- "" | cut -c 3-
# 输出:C%2B%2B%20%E7%BC%96%E7%A8%8B
四、HTTP请求格式
4.1 请求格式概览
HTTP请求分为三部分:
bash
[首行]\r\n
[Header1]: [Value1]\r\n
[Header2]: [Value2]\r\n
...\r\n
\r\n
[Body]
重要 :每行都以\r\n(回车换行)结尾,Header和Body之间有一个空行(\r\n\r\n)。
4.2 首行(Request Line)
格式:
bash
[方法] [URL] [版本]\r\n
示例:
bash
GET /index.html HTTP/1.1\r\n
拆解:
- 方法:GET
- URL:/index.html(只包含路径和查询参数,不包含协议、域名、端口)
- 版本:HTTP/1.1
其他示例:
bash
POST /api/login HTTP/1.1\r\n
GET /search?keyword=Linux HTTP/1.1\r\n
4.3 Header(请求头)
Header是键值对,格式:Key: Value\r\n
示例:
bash
Host: www.example.com\r\n
User-Agent: Mozilla/5.0\r\n
Accept: text/html\r\n
Content-Length: 27\r\n
\r\n
每个Header一行,以\r\n结尾。Header部分结束后有一个空行(\r\n),表示Header结束。
4.4 Body(请求体)
Body是可选的,通常用于POST、PUT等方法传输数据。
如果有Body,Header中必须有Content-Length字段,表示Body的字节数。
示例(POST请求):
bash
POST /api/login HTTP/1.1\r\n
Host: www.example.com\r\n
Content-Type: application/x-www-form-urlencoded\r\n
Content-Length: 27\r\n
\r\n
username=admin&password=123
Body内容:username=admin&password=123(27字节)
4.5 完整示例
一个完整的GET请求:
bash
GET /search?keyword=Linux HTTP/1.1\r\n
Host: www.example.com\r\n
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36\r\n
Accept: text/html,application/xhtml+xml,application/xml\r\n
Accept-Encoding: gzip, deflate\r\n
Connection: keep-alive\r\n
\r\n
一个完整的POST请求:
bash
POST /api/login HTTP/1.1\r\n
Host: www.example.com\r\n
Content-Type: application/x-www-form-urlencoded\r\n
Content-Length: 27\r\n
\r\n
username=admin&password=123
五、HTTP响应格式
5.1 响应格式概览
HTTP响应也分为三部分:
bash
[首行]\r\n
[Header1]: [Value1]\r\n
[Header2]: [Value2]\r\n
...\r\n
\r\n
[Body]
格式和请求类似,只是首行不同。
5.2 首行(Status Line)
格式:
bash
[版本] [状态码] [状态描述]\r\n
示例:
bash
HTTP/1.1 200 OK\r\n
拆解:
- 版本:HTTP/1.1
- 状态码:200
- 状态描述:OK(给人看的,程序只看状态码)
其他示例:
bash
HTTP/1.1 404 Not Found\r\n
HTTP/1.1 500 Internal Server Error\r\n
HTTP/1.1 302 Found\r\n
5.3 Header(响应头)
格式和请求头相同。
示例:
bash
Content-Type: text/html\r\n
Content-Length: 1024\r\n
Server: Apache/2.4.41\r\n
Date: Wed, 21 Oct 2023 07:28:00 GMT\r\n
\r\n
5.4 Body(响应体)
Body通常是HTML页面、JSON数据、图片等内容。
示例(返回HTML页面):
bash
HTTP/1.1 200 OK\r\n
Content-Type: text/html\r\n
Content-Length: 52\r\n
\r\n
<html>
<body>
<h1>Hello World</h1>
</body>
</html>
Body内容:<html>...</html>(52字节)
5.5 完整示例
一个完整的200响应:
bash
HTTP/1.1 200 OK\r\n
Server: nginx/1.18.0\r\n
Date: Wed, 21 Oct 2023 07:28:00 GMT\r\n
Content-Type: text/html\r\n
Content-Length: 52\r\n
Connection: keep-alive\r\n
\r\n
<html>
<body>
<h1>Hello World</h1>
</body>
</html>
一个完整的404响应:
bash
HTTP/1.1 404 Not Found\r\n
Server: nginx/1.18.0\r\n
Date: Wed, 21 Oct 2023 07:28:00 GMT\r\n
Content-Type: text/html\r\n
Content-Length: 48\r\n
\r\n
<html>
<body>
<h1>404 Not Found</h1>
</body>
</html>
六、用TCP Socket验证HTTP协议
6.1 最简单的HTTP服务器
现在我们用TCP Socket写一个最简单的HTTP服务器,只返回"Hello World"。
示例为了演示格式,假设一次 read 读全;实际工程需要循环读直到读到完整 header/body。 同时我们这里简单起见,响应后就close,等价于短连接行为。
cpp
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main(int argc, char* argv[])
{
if (argc != 3) {
printf("Usage: %s [ip] [port]\n", argv[0]);
return 1;
}
// 1. 创建socket
int listenfd = socket(AF_INET, SOCK_STREAM, 0);
if (listenfd < 0) {
perror("socket");
return 1;
}
// 2. bind
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = inet_addr(argv[1]);
addr.sin_port = htons(atoi(argv[2]));
int ret = bind(listenfd, (struct sockaddr*)&addr, sizeof(addr));
if (ret < 0) {
perror("bind");
return 1;
}
// 3. listen
ret = listen(listenfd, 10);
if (ret < 0) {
perror("listen");
return 1;
}
printf("HTTP Server started on %s:%s\n", argv[1], argv[2]);
// 4. 循环accept
while (1) {
struct sockaddr_in client_addr;
socklen_t len = sizeof(client_addr);
int clientfd = accept(listenfd, (struct sockaddr*)&client_addr, &len);
if (clientfd < 0) {
perror("accept");
continue;
}
// 5. 读取请求
char request[10240] = {0};
ssize_t n = read(clientfd, request, sizeof(request) - 1);
if (n < 0) {
perror("read");
close(clientfd);
continue;
}
printf("===== Request =====\n%s\n", request);
// 6. 构造响应
const char* html = "<html><body><h1>Hello World</h1></body></html>";
char response[1024];
sprintf(response,
"HTTP/1.1 200 OK\r\n"
"Content-Type: text/html\r\n"
"Content-Length: %lu\r\n"
"\r\n"
"%s",
strlen(html), html);
// 7. 发送响应
write(clientfd, response, strlen(response));
// 8. 关闭连接
close(clientfd);
}
return 0;
}
6.2 编译运行
bash
gcc -o http_server http_server.c
./http_server 0.0.0.0 9090
输出:
bash
HTTP Server started on 0.0.0.0:9090
6.3 浏览器测试
打开浏览器,输入:
bash
http://127.0.0.1:9090
浏览器显示:
bash
Hello World
服务器端打印:
bash
===== Request =====
GET / HTTP/1.1
Host: 127.0.0.1:9090
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
Accept-Encoding: gzip, deflate
Connection: keep-alive
成功了!我们用TCP Socket实现了一个HTTP服务器,浏览器能够正确解析我们返回的HTML页面。
6.4 favicon.ico请求
你可能注意到,浏览器实际发送了两个请求:
bash
GET / HTTP/1.1
...
GET /favicon.ico HTTP/1.1
...
favicon.ico是什么?
favicon.ico是网站的图标,显示在浏览器标签页上。浏览器会自动请求这个文件。
我们的服务器对所有请求都返回同样的HTML,所以第二个请求也会收到"Hello World",但浏览器发现不是图片格式,就忽略了。
七、Connection字段深度解析
7.1 持久连接的必要性
HTTP/1.0的工作方式:
bash
1. 客户端发起TCP连接(三次握手)
2. 客户端发送HTTP请求
3. 服务器返回HTTP响应
4. 关闭TCP连接(四次挥手)
如果一个网页包含10张图片、5个CSS文件、3个JS文件,就要建立18个TCP连接。每个连接都要三次握手和四次挥手,开销巨大。
7.2 HTTP/1.1的持久连接
HTTP/1.1引入了持久连接(Persistent Connection),也叫长连接(Keep-Alive)。
bash
1. 客户端发起TCP连接(三次握手)
2. 客户端发送请求1
3. 服务器返回响应1
4. 客户端发送请求2(复用同一个TCP连接)
5. 服务器返回响应2
6. ...
7. 客户端或服务器主动关闭连接(四次挥手)
优势:
- 减少了TCP连接建立和关闭的开销
- 减少了网络拥塞(TCP慢启动)
- 降低了延迟
7.3 Connection字段的作用
HTTP/1.0:默认短连接,如果要持久连接,需要显式设置:
bash
Connection: keep-alive
HTTP/1.1:默认持久连接,如果要短连接,需要显式设置:
bash
Connection: close
7.4 测试持久连接
修改我们的HTTP服务器,不要每次都close(clientfd),而是循环读取请求:
cpp
while (1) {
int clientfd = accept(...);
// 循环处理同一个连接的多个请求
while (1) {
char request[10240] = {0};
ssize_t n = read(clientfd, request, sizeof(request) - 1);
if (n <= 0) break; // 连接断开
printf("Request:\n%s\n", request);
// 构造响应
char response[1024];
sprintf(response,
"HTTP/1.1 200 OK\r\n"
"Content-Type: text/html\r\n"
"Content-Length: %lu\r\n"
"Connection: keep-alive\r\n" // 告诉客户端保持连接
"\r\n"
"%s",
strlen(html), html);
write(clientfd, response, strlen(response));
}
close(clientfd);
}
这样,客户端可以在同一个TCP连接上发送多个HTTP请求。
八、本篇总结
8.1 核心要点
HTTP协议基础:
- HTTP是应用层协议,定义了客户端与服务器的通信格式
- 无连接、无状态、基于请求-响应模型
- 依赖TCP协议保证可靠传输
URL详解:
- 格式:协议://域名:端口/路径?查询参数#片段标识符
- 默认端口:HTTP 80、HTTPS 443
- 查询参数用?开头,&分隔,键值对格式
- 片段标识符不发送给服务器
urlencode/urldecode:
- 特殊字符需要转义(/、?、&、=、空格等)
- 转义规则:字符→16进制→加%前缀
- 中文字符按UTF-8编码转义
HTTP请求格式:
- 首行:方法 URL 版本
- Header:键值对,每行\r\n结尾
- 空行:\r\n
- Body:可选,有Content-Length标识长度
HTTP响应格式:
- 首行:版本 状态码 状态描述
- Header:键值对
- 空行
- Body:HTML、JSON、图片等
Connection字段:
- HTTP/1.0默认短连接,需要keep-alive
- HTTP/1.1默认持久连接,可以close
- 持久连接复用TCP连接,减少开销
8.2 容易混淆的点
-
URL中的端口默认可以省略 :http://www.baidu.com实际访问的是http://www.baidu.com:80。
-
片段标识符不发送给服务器 :http://example.com/page#section,服务器只收到/page,不包含#section。
-
urlencode为什么用%:因为%本身不是URL的特殊字符,可以安全使用作为转义标识。
-
HTTP请求的URL只包含路径:GET /index.html HTTP/1.1,不包含协议、域名、端口(这些信息在Host字段中)。
-
\r\n的必要性:HTTP协议规定行分隔符是\r\n,不能只用\n。这是历史原因(电传打字机需要回车+换行两个动作)。
-
Connection: keep-alive和持久连接:HTTP/1.1默认就是持久连接,不需要显式设置keep-alive。只有HTTP/1.0需要。
💬 总结:这一篇从HTTP的基本概念讲起,详细剖析了URL的各个组成部分,解释了urlencode的必要性和规则,深入讲解了HTTP请求和响应的格式(首行、Header、Body),最后用TCP Socket手写了一个HTTP服务器并用浏览器验证。理解了HTTP的请求响应格式,就理解了浏览器和服务器通信的本质。下一篇会详细讲解HTTP方法(GET、POST等)、状态码、常见Header,以及它们的应用场景。
👍 点赞、收藏与分享:如果这篇帮你理解了HTTP协议的基础,请点赞收藏!下一篇会把HTTP的方法、状态码、Header全部讲透!