基于 C++ 的 JSON 与 HTTP 网络编程实战
前置知识:序列化与反序列化
在 TCP 通信中,由于 TCP 是面向字节流的,发送数据前通常需要定义结构化数据描述传输内容。直接传递结构体内存字节会引发字节序和内存对齐问题。因此需要借助序列化,将结构化数据转换为连续的字节流。
序列化分为文本序列化与二进制序列化。文本序列化(如 JSON)直观、可读性高;二进制序列化(如 Protobuf)体积小、解析快。
| 特性 | 文本序列化 (JSON/XML) | 二进制序列化 (Protobuf/Thrift) |
|---|---|---|
| 可读性 | 极高 | 低 |
| 传输体积 | 较大 | 极小 |
| 解析速度 | 较慢 | 极快 |
| 跨语言 | 完美 | 优秀 |
实际开发中通常使用成熟的第三方库,本文重点介绍 JSON。
JSON 详解
JSON(JavaScript Object Notation)是一种轻量级、基于文本的数据交换格式。它支持整型、浮点型、布尔型、字符串、对象和数组等类型。
nlohmann/json 库使用
C++ 中常用 json.hpp (nlohmann/json)。需自行引入该头文件。
初始化
#include "json.hpp"
#include <iostream>
using json = nlohmann::json;
int main() {
// 列表初始化
json j1 = {{"name", "WZ"}, {"age", 20}};
// 赋值运算符初始化
json j2;
j2["name"] = "wz";
j2["age"] = 20;
}
序列化与反序列化
- 序列化:调用
dump()成员函数,返回std::string。可传参数控制缩进。 - 反序列化:调用静态函数
parse(string)。
// 反序列化示例
std::string s = R"({"name":"WZ","age":18})";
json j = json::parse(s);
std::cout << j["name"] << std::endl;
原理简述
json 类内部维护类型变量与值变量(联合体)。基本类型直接存储,复杂类型存储指针。构造函数根据输入决定类型(对象或数组),利用 std::initializer_list 进行初始化。operator[] 重载用于访问键值对或数组索引,支持自动扩容和类型转换。
HTTP 协议基础
HTTP 是基于 TCP 的应用层协议,定义了请求与响应报文的格式。
URL 与域名
URL 包含协议、域名、端口、路径等。域名通过 DNS 解析为 IP 地址。DNS 查询涉及本地缓存、Hosts 文件及根域名服务器迭代查询。
HTTP 报文结构
请求报文
由请求行、请求头、空行和可选的请求正文组成。
- 请求行:方法、URL、协议版本。
- 常见方法:
- GET:获取资源,无 Body。
- POST:提交数据,有 Body。
- 请求头:Host, User-Agent, Content-Type 等。
响应报文
由响应行、响应头、空行和响应正文组成。
- 状态码:
- 2xx:成功(200 OK)
- 3xx:重定向(301, 302)
- 4xx:客户端错误(404 Not Found)
- 5xx:服务器错误(500 Internal Error)
Session 与 Cookie
HTTP 是无状态的。通过 Set-Cookie 字段下发 Session ID,客户端后续请求携带 Cookie 字段以维持会话状态。
Web 服务器实现
架构设计
使用 C++ 面向对象封装 Socket 操作,采用线程池处理并发连接。
Socket 封装
封装 socket, bind, listen, accept 等系统调用,管理文件描述符生命周期。
class sock {
public:
void socket();
void bind(std::string ip, uint16_t port);
void listen();
int accept(struct sockaddr_in* client, socklen_t* clientlen);
~sock() { if(socketfd >= 0) ::close(socketfd); }
private:
int socketfd;
};
线程池
主线程接收连接后,将任务放入队列,工作线程从队列获取并执行。
请求处理流程
- 读取请求:循环
recv直到遇到 `
。 2. **解析请求**:分离请求行、头部和正文。处理 POST 的 Content-Length。 3. **业务分发**:根据 Method (GET/POST) 调用不同处理器。 4. **构建响应**:生成状态行、头部和正文。 5. **发送响应**:send` 数据并关闭连接。
核心代码模块
请求解析
bool Get_HttpRequest(size_t socketfd, Http_Request& hr) {
std::string data;
char buffer[BUFFER_SIZE];
while(true) {
ssize_t read_bytes = recv(socketfd, buffer, BUFFER_SIZE - 1, 0);
if(read_bytes <= 0) return false;
data.append(buffer, read_bytes);
if(data.find("\r\n\r\n") != std::string::npos) break;
}
// 进一步解析 Head 和 Body...
return hr.Deserialization(head);
}
GET 请求处理(静态资源)
映射 URL 到文件系统路径,读取文件内容,设置正确的 MIME Type。
std::string Http_Get_Handler(Http_Request& hr) {
std::string file_path = path + hr.url;
std::string body = read_file(file_path);
if(body.empty()) {
// 返回 404
} else {
// 返回 200 OK
}
return res;
}
POST 请求处理(计算器)
解析表单数据,执行计算逻辑,返回 HTML 结果页。
std::string Http_Post_Handler(Http_Request& hr) {
if(hr.url == "/calc") {
// 解析 body: a=1&op=+&b=2
// 计算结果
// 返回 200 OK 及 HTML
}
return process_bad_request();
}
运行说明
编译链接后,命令行指定端口启动服务器。
./server 8080
总结
本文详细讲解了 JSON 与 HTTP 协议原理,并通过 C++ 实现了具备静态资源服务和动态计算功能的 Web 服务器。涵盖了 Socket 编程、线程池、日志记录及文件 IO 等核心技术点,适合深入理解网络编程底层机制。


