C++ 实现基于 JSON 和 HTTP 协议的 Web 计算器服务器
序列化与反序列化用于屏蔽平台差异,JSON 作为文本序列化格式便于跨语言数据交换。HTTP 协议定义了客户端与服务端的通信规则,包含请求行、头部及正文。基于 C++ 与 Linux 网络编程,通过 Socket 封装、线程池管理及 HTTP 报文解析,可实现支持静态资源与动态计算的 Web 服务器。

序列化与反序列化用于屏蔽平台差异,JSON 作为文本序列化格式便于跨语言数据交换。HTTP 协议定义了客户端与服务端的通信规则,包含请求行、头部及正文。基于 C++ 与 Linux 网络编程,通过 Socket 封装、线程池管理及 HTTP 报文解析,可实现支持静态资源与动态计算的 Web 服务器。

在 TCP 协议通信中,由于 TCP 是面向字节流的,发送数据前通常需要定义结构化数据来描述传输内容。直接传递结构体内存字节会引发字节序和内存对齐问题,因此需要借助序列化将结构化数据转换为连续的字节流。
文本序列化将数据转换为字符串,直观可读但体积较大;二进制序列化直接发送原始二进制序列,体积小但不可读。JSON 是一种广泛应用的文本序列化格式,支持跨平台、跨语言的数据交换。
JSON(JavaScript Object Notation)是一种轻量级、基于文本的数据交换格式。它源于 JavaScript,但被多种编程语言支持。
| JSON 类型 | C++ 对应类型 | 描述 |
|---|---|---|
| Number | int, double, float | 统一视为数字 |
| Boolean | bool | true 或 false |
| String | std::string | 双引号包围 |
| Null | nullptr / NULL | 空值 |
C++ 中常用的 JSON 库包括 json.hpp (nlohmann/json)。
对象初始化:
#include "json.hpp"
#include <iostream>
int main() {
// 列表初始化
nlohmann::json j = {{"name", "WZ"}, {"age", 20}, {"gender", "girl"}};
// 赋值运算符初始化
nlohmann::json j2;
j2["name"] = "wz";
j2["age"] = 20;
}
序列化与反序列化:
// 序列化
std::string s = j.dump(); // 紧凑格式
std::string s_pretty = j.dump(4); // 格式化缩进
// 反序列化
std::string raw_json = R"({"name":"WZ","age":18})";
nlohmann::json j = nlohmann::json::parse(raw_json);
json 类内部维护类型变量与联合体值变量。基本类型直接存储值,复杂类型存储指针。通过重载 operator[] 和类型转换运算符实现灵活访问。
HTTP 是应用层通信协议,基于 TCP 传输。客户端与服务端通过请求报文和响应报文交互。
URL 包含协议、域名、端口、路径、查询参数和片段。例如:https://www.example.com:443/music/list?id=1024。
域名需转换为 IP 地址才能通信。过程涉及本地缓存、hosts 文件、本地 DNS 服务器及根/顶级/权威域名服务器的迭代查询。
由请求行、请求头、空行和可选的请求正文组成。
请求行示例:
POST /api/login HTTP/1.1
Host: www.example.com
User-Agent: Mozilla/5.0
Content-Type: application/x-www-form-urlencoded
Content-Length: 27
Connection: keep-alive
username=admin&password=123456
常见请求方法:
| 方法 | 语义 | 数据位置 | 幂等性 | 安全性 |
|---|---|---|---|---|
| GET | 获取资源 | URL 参数 | 是 | 是 |
| POST | 新增/处理 | 请求体 | 否 | 否 |
| PUT | 更新 | 请求体 | 是 | 否 |
| DELETE | 删除 | URL 路径 | 是 | 否 |
由响应行、响应头、空行和响应正文组成。
状态码分类:
服务器框架遵循固定模式:创建监听套接字 -> 绑定 IP 端口 -> 监听 -> 接受连接 -> 处理请求。
封装了 sock 类管理 Socket 生命周期,使用 RAII 思想自动释放资源。
class sock {
public:
sock(): socketfd(-1) {}
~sock() { if(socketfd >= 0) ::close(socketfd); }
void socket();
void bind(std::string ip, uint16_t port);
void listen();
int accept(struct sockaddr_in* client, socklen_t* clientlen);
// ... 其他方法
private:
int socketfd;
};
为避免频繁创建销毁线程的开销,引入线程池。主线程接收连接后构造 Task 对象放入队列,消费者线程获取任务执行通信逻辑。
读取完整 TCP 报文段,依据 \r\n\r\n 判断请求头结束。根据 Content-Length 判断是否有请求正文并读取完整数据。
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);
}
std::string Http_Post_Handler(Http_Request& hr) {
if(hr.url == "/calc") {
// 解析 body 中的键值对
// 执行计算
// 返回 HTML 结果页
}
return process_bad_request();
}
#pragma once
#include "Socket.hpp"
#include "log.hpp"
#include "Threadpool.h"
class Httpserver {
public:
Httpserver(std::string _ip = "0.0.0.0", uint16_t _port = 80);
void init();
void start();
private:
uint16_t port;
std::string ip;
sock listen_socket;
bool islistening;
};
#pragma once
#include <functional>
#include <string>
#include <sys/types.h>
#include <unistd.h>
#include <fstream>
#include <unordered_map>
#include "protocol.hpp"
#include "log.hpp"
class Task {
public:
Task(int _socketfd);
void run();
private:
int socketfd;
static std::unordered_map<std::string, std::string> map;
};
#pragma once
#include "log.hpp"
#include <iostream>
#include <vector>
#include <string>
#include <sstream>
#include <unordered_map>
class Http_Request {
public:
bool Deserialization(std::string& head);
void debugprint();
std::unordered_map<std::string, std::string> headers;
std::string text;
std::string method;
std::string url;
std::string http_version;
};
#pragma once
#include <iostream>
#include <string>
#include <time.h>
#include <unistd.h>
#include <stdarg.h>
class log {
private:
std::string memssage;
int method;
public:
log(int _method = screen);
void logmessage(int leval, const char* format, ...);
};
extern log lg;
#include "Httpserver.hpp"
#include "log.hpp"
#include <string>
void usage(std::string progmaname) {
std::cout << "usage wrong: " << progmaname << " <port>" << std::endl;
}
int main(int argc, char* argv[]) {
if(argc != 2) {
usage(argv[0]);
exit(-1);
}
uint16_t port = std::stoi(argv[1]);
Httpserver hs("0.0.0.0", port);
hs.init();
hs.start();
return 0;
}

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online
通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online
将JSON字符串修饰为友好的可读格式。 在线工具,JSON美化和格式化在线工具,online