跳到主要内容Linux 网络编程:使用 C++ 实现基于 JSON 和 HTTP 的 Web 计算器服务器 | 极客日志C++
Linux 网络编程:使用 C++ 实现基于 JSON 和 HTTP 的 Web 计算器服务器
综述由AI生成C++ 环境下 JSON 序列化与反序列化的原理及 nlohmann/json 库的使用,深入剖析了 HTTP 协议结构、请求响应报文格式及 GET/POST 方法区别。在此基础上,通过封装 Socket 类、线程池及任务处理机制,从零实现了支持静态资源访问与动态计算功能的 Web 服务器核心逻辑,涵盖了 TCP 连接管理、数据解析及文件读写等关键技术点。
道系青年10 浏览 Linux 网络编程:JSON+HTTP,用 C++ 手搓一个 Web 计算器服务器
前言
在之前的学习中,我们详细介绍了序列化与反序列化的概念。对于使用 TCP 协议进行通信的双方,由于 TCP 是面向字节流的,在发送数据之前,通常需要定义一种结构化的数据来描述传输内容。在 C++ 中,这种结构化数据通常表现为对象或结构体。然而,我们不能直接将结构体内存中对应的字节原样发送到另一端,因为直接传递内存字节会引发字节序和结构体内存对齐的问题。
因此,我们需要借助序列化。序列化是指将结构化的数据按照预定的规则转换为连续的字节流。其主要目的是屏蔽平台差异,使得位于不同平台的进程能够以统一的方式解析该字节流。序列化通常分为两种形式:文本序列化与二进制序列化。
| 特性 | 文本序列化 (JSON/XML) | 二进制序列化 (Protobuf/Thrift) |
|---|
| 可读性 | 极高(肉眼可读) | 低(十六进制乱码) |
| 传输体积 | 较大(数字变字符,带大量引号) | 极小(紧凑编码) |
| 解析速度 | 较慢(需字符串扫描、词法解析) | 极快(直接偏移寻址或位运算) |
| 跨语言 | 完美(天然支持) | 优秀(需编译 IDL 文件) |
在实际开发中,我们通常不需要从头实现序列化,可以使用成熟的第三方库来完成这项工作。本文将介绍的第一个主题——JSON,就是一种广泛应用的文本序列化格式。
JSON
什么是 JSON
JSON(JavaScript Object Notation)是一种轻量级、基于文本、人类可读的数据交换格式。JSON 源于 JavaScript,借鉴了其对象和数组的表示方法。但由于 JSON 本身是文本格式,且所表示的基本数据类型在绝大多数编程语言中都得到支持,因此 JSON 并不局限于 JavaScript,而是能够被多种编程语言解析与生成。
JSON 支持若干基本数据类型,例如整型、浮点型和布尔型,也支持字符串、对象等复杂类型:
| JSON 类型 | C++ 对应类型 | 描述 |
|---|
| Number | int, double, float | JSON 不区分整数和浮点数,统一视为数字。 |
| Boolean | bool | 只有 true 和 false 两个字面值。 |
| String | std::string | 必须使用双引号包围,支持转义字符。 |
| Null | nullptr / NULL | 表示空值或不存在。 |
如果我们需要将一个对象或结构体的数据传递给另一端,在未接触 JSON 时,通常需要手动将其各字段拼接成字符串。而 JSON 可以直接表示对象,其方法是用一对大括号包裹内容,括号内是一个或多个键值对。
{"age":20,"sex":"girl","height":160}
需要注意的是,值可以是任意基本类型,但键必须是字符串类型。
nlohmann/json 库的使用
在 C++ 中,常用的 JSON 库包括 json.hpp(即 nlohmann/json)。它提供的 JSON 对象可被视为一个容器,用于存储要发送或解析的 JSON 数据,并封装了丰富的操作方法。
初始化
json 类最常见的数据类型是对象(object)和数组(array)。初始化一个 json 对象主要有两种方式。
#include "json.hpp"
#include <iostream>
int main() {
nlohmann::json j = {{"name", "WZ"}, {"age", 20}, {"gender", "girl"}};
return 0;
}
nlohmann::json j;
j["name"] = "wz";
j["age"] = 20;
j["gender"] = "girl";
序列化与反序列化
json 类提供了 dump() 成员函数用于序列化,其返回类型为 std::string。若向 dump() 传递一个整型参数,则输出的字符串会进行格式化缩进。
反序列化方面,json 类提供了静态成员函数 parse(),它接收一个 JSON 格式的字符串,在内部解析后存储到 json 对象中。
#include "json.hpp"
#include <iostream>
int main() {
std::string s = R"({"name":"WZ","age":18,"is_student":true})";
nlohmann::json j = nlohmann::json::parse(s);
std::cout << j["name"] << std::endl;
return 0;
}
原理简析
json.hpp 库内部维护一个 json 类,该类包含两个核心成员变量:类型变量与值变量。其中,类型变量用于记录当前 json 对象所维护的原始数据类型。值变量被实现为一个联合体,所有类型共用同一内存区域。
enum class value_t {
null, number_integer, string, array, object
};
class json {
public:
union internal_value {
int64_t number_integer;
std::string* string;
std::vector<json>* array;
std::map<std::string, json>* object;
};
value_t m_type = value_t::null;
internal_value m_value;
};
HTTP 协议
引入
HTTP 正是一个应用层的通信协议。要理解 HTTP 的原理,我们首先要从整个通信过程的起点说起,也就是在浏览器输入网址的那一刻。这涉及客户端与服务端之间的通信原理。
URL 与域名
URL(Uniform Resource Locator,统一资源定位符)通常由以下几个部分组成:协议、域名、端口、路径、查询参数和片段。
例如:https://www.example.com:443/music/list?id=1024&type=pop#comment
域名通过 DNS 服务转换为 IP 地址。DNS 解析过程包括本地缓存、hosts 文件、本地 DNS 服务器以及根域名服务器的迭代查询。
HTTP 请求报文
HTTP 请求报文可由三部分或四部分构成,包括请求行、请求头、空行和可选的请求正文。
请求行:由请求方法、URL 和协议版本三部分组成。
| 请求方法 | 语义 | 典型应用场景 |
|---|
| GET | 获取资源 | 浏览网页、搜索图片 |
| POST | 新增或处理资源 | 注册账号、发表评论 |
| PUT | 更新(全量覆盖) | 修改用户完整档案 |
| DELETE | 删除资源 | 注销账户 |
请求头:由多行键值对组成,每行以回车换行符结束。常见字段包括 Host、User-Agent、Accept 等。
HTTP 响应报文
响应报文的格式与请求报文高度对称,也由四部分组成,分别是响应行、响应头、空行和响应正文。
响应行:由协议版本、状态码和状态描述组成。状态码首位数字为 1~5,分别对应不同的状态类别(信息性、成功、重定向、客户端错误、服务器错误)。
HTTP 服务器实现
架构设计
本次使用 C++ 编写 Web 服务器,借助 C++ 的面向对象特性,将服务器抽象为一个对象,用 Httpserver 类进行描述。将上述固定流程——即系统接口的调用——封装到 Httpserver 类的成员函数中。
此外,我们将 socket 相关操作封装到一个专门的 sock 类中。该类维护一个文件描述符,并提供 socket、bind 等方法。同时采用 RAII 思想,将套接字的生命周期交由 sock 类管理。
线程池与任务处理
服务端会持续收到完成 TCP 三次握手的新连接,为了避免频繁创建和销毁线程带来的开销,这里引入线程池。线程池维护一个任务缓冲区与一组消费者线程。我们定义一个 Task 任务对象,其中包含一个 run 方法,该方法封装了通信处理逻辑。
class threadpool {
public:
static threadpool& getinstance() {
static threadpool instance;
return instance;
}
void start() {
for(int i = 0; i < Max_size; i++) {
pthread_t tid;
pthread_create(&tid, NULL, handletask, this);
}
}
private:
std::vector<Task> q;
int Max_size;
pthread_mutex_t mutex;
sem_t element;
sem_t space;
};
请求解析与处理
由于 HTTP 协议基于 TCP 协议,整个通信的第一个环节便是读取客户端发来的请求报文。TCP 协议是面向字节流的,这意味着读取报文时以字节为单位,可能导致一次读取仅得到报文的一部分。
我们需要依据 HTTP 请求报文的格式进行解析。HTTP 请求报文由四个部分组成。其中,请求头结束后会有一个空行,而请求头最后一行末尾带有回车换行符(CRLF),与空行共同构成连续的两个 CRLF(即 \r\n\r\n)。
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;
}
return hr.Deserialization(head);
}
成功获取完整 TCP 报文段并反序列化后,各字段将解析并存储到一个 http_request 对象中。后续逻辑进入业务分发阶段。系统依据 method 字段判断请求类型:若为 GET 方法,则优先尝试静态资源映射;若为 POST 方法,则触发动态业务逻辑处理。
GET 请求处理
GET 请求通常用于从服务器获取静态资源,即文件。在此上下文中,URL 对应于服务器文件系统下的一个相对路径。
std::string Http_Get_Handler(Http_Request& hr) {
std::string file_path;
std::string content_type;
if(hr.url == "/" || hr.url == "/index.html") {
file_path = path + "/index.html";
content_type = "text/html";
} else {
file_path = path + hr.url;
}
std::string body = read_file(file_path);
return res;
}
POST 请求处理
POST 请求会携带请求正文,并且其请求行中的 URL 是一个虚拟路径,用于映射到服务器预定义的函数。本示例设定为一个计算器功能。
std::string Http_Post_Handler(Http_Request& hr) {
std::unordered_map<std::string, std::string> val;
if(hr.url == "/calc") {
int calc_result;
if(process_calculation(val, calc_result)) {
}
}
return res;
}
主函数入口
服务器运行于 Linux 环境,通常通过命令行启动进程,配置信息可通过命令行参数传递。
int main(int argc, char* argv[]) {
if(argc != 2) {
usage(argv[0]);
exit(-1);
}
uint16_t port = std::stoi(argv[1]);
Httpserver hs(_default, port);
hs.init();
hs.start();
return 0;
}
结语
本文详细讲解了 C++ 环境下 JSON 序列化与反序列化的原理及 nlohmann/json 库的使用,深入剖析了 HTTP 协议结构、请求响应报文格式及 GET/POST 方法区别。在此基础上,通过封装 Socket 类、线程池及任务处理机制,从零实现了支持静态资源访问与动态计算功能的 Web 服务器核心逻辑,涵盖了 TCP 连接管理、数据解析及文件读写等关键技术点。
相关免费在线工具
- Base64 字符串编码/解码
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
- Base64 文件转换器
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
- Markdown转HTML
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
- HTML转Markdown
将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online
- JSON 压缩
通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online
- JSON美化和格式化
将JSON字符串修饰为友好的可读格式。 在线工具,JSON美化和格式化在线工具,online