跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客我的书AI学习GitHub 精选镜像AI 生图工具UI配色美学关于
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
C++

基于 C++ 手写 HTTP 服务器:请求解析与响应构建

TCP 通信的局限性及 HTTP 协议的基础知识,包括 URL 解析、DNS 查询流程以及 HTTP 报文结构。通过 C++ 代码示例,详细演示了如何从零实现一个支持 GET 和 POST 方法的简单 HTTP 服务器,涵盖 Socket 初始化、请求行解析、文件读取及响应构建等核心步骤,帮助读者理解 Web 服务背后的通信机制。

莫名其妙发布于 2026/3/30更新于 2026/9/1265 浏览
基于 C++ 手写 HTTP 服务器:请求解析与响应构建

在 TCP 通信中,TCP 是面向字节流式的网络通信协议,不具备消息边界的功能。要实现完整的网络通信,必须设计应用层协议。为了方便,已有现成的应用层协议可供直接使用,例如 HTTP(超文本传输协议)。

协议名称协议全称默认端口传输层协议说明
HTTP超文本传输协议80TCP网页访问(明文)
HTTPS安全超文本传输协议443TCP加密网页
FTP文件传输协议21 / 20TCP文件上传下载
TFTP简单文件传输协议69UDP简单传输
SMTP邮件发送协议25TCP发送邮件
POP3邮件接收协议110TCP下载邮件
IMAP邮件访问协议143TCP管理邮件
DNS域名系统53UDP/TCP域名解析
DHCP动态主机配置协议67 / 68UDP自动分配 IP
Telnet远程登录协议23TCP不安全远程登录
SSH安全远程登录22TCP加密远程连接
SNMP网络管理协议161UDP网络设备管理
NTP网络时间协议123UDP时间同步

这些就是常见的一些应用层协议。

认识 URL

当我们在浏览器中通过域名访问网站时,本质上并不是直接访问这个字符串,而是需要先将这个域名解析为对应的 IP 地址,然后进行访问。

具体流程是:

  • 首先,客户端(浏览器或操作系统)会向本地 DNS 服务器发送解析请求,这个服务器提供者就是三大运营商。
  • 如果本地 DNS 服务器没有缓存结果,它会向更高层级的 DNS 服务器发起查询:
    • 根域名服务器
    • 顶级域名服务器
    • 权威域名服务器
  • 最终找到该域名对应的 IP 地址,并将结果返回给客户端

拿到 IP 地址之后,客户端就可以通过:IP 地址 + 端口号。

但是仔细的人就会看到我们使用 IP 地址进行访问的时候,并没有看到端口号这个东西,难道只需要通过 IP 地址就可以访问到具体的网站了吗?其实并不是,其实是因为我们使用的是 HTTPS 协议,这个协议就代表着它的端口号就是 443(这就像我们在生活中 110 就是报警电话,119 就是火警电话一样),所以我们就不需要在显现的写出来,浏览器替我们隐式补全了端口号,而不是端口号不存在。

IP 地址负责定位主机,端口号负责定位主机上的具体服务,二者缺一不可

HTTP 协议

这张图其实展示的是一次完整的 HTTP 通信过程中,请求报文和响应报文的整体结构。从客户端角度来看,当浏览器发起请求时,会先构造一段符合 HTTP 协议规范的文本数据发送给服务器,这段数据最开始是请求行,包含请求方法(如 GET、POST)、资源路径以及 HTTP 版本,用来明确'我要做什么、访问哪里、用什么协议版本'。紧接着是多行请求报头,每一行都是 key:value 的形式,用于携带额外信息,比如目标主机、数据类型、客户端信息等,这些内容全部以 \r\n 作为分隔。

在请求报头结束后,会有一个非常关键的空行,它的作用是明确告诉服务器:报头部分已经结束,接下来如果还有内容,就是请求的有效载荷。对于 GET 请求来说通常没有正文,而 POST 请求则会在这里携带数据,比如表单或 JSON,这部分就是图中标注的'请求正文'。

服务器接收到请求并处理后,会按照同样的协议格式返回响应。响应报文的开头是状态行,包含 HTTP 版本、状态码以及对应的描述信息,例如 200 OK 表示请求成功。随后是多行响应报头,同样以 key:value 的形式存在,用来说明返回数据的类型、长度以及服务器相关信息等。报头之后同样会有一个空行,用于分隔元数据和真正的数据内容,最后的响应正文才是客户端真正关心的部分,可能是 HTML 页面、JSON 数据,或者 JS、CSS 等资源。

从整体来看,这张图的核心就是在说明:HTTP 通信本质上是在 TCP 之上,按照固定格式传输的一段'结构化字符串',通过请求行/状态行、报头、空行和正文这几个部分进行组织,而 \r\n 则充当了天然的分隔符,使客户端和服务器能够准确地解析彼此发送的数据。

HTTP 请求

  • 首行:方法+url+版本
  • Header:请求的属性,冒号分割的键值对;每一组属性之间用 \r\n 进行分割,遇到空行则表示 Header 部分结束
  • Body:空行后面的内容就是 Body,Body 的内容可以是空字符串。

HTTP 响应

  • 首行:版本号 + 状态码 + 状态码解释
  • Header:请求的属性,冒号分割的键值对;每一组属性之间用 \r\n 进行分割,遇到空行则表示 Header 部分结束
  • Body:
  • 空行后面的内容就是 Body,Body 的内容可以是空字符串。如果服务器返回了一个 html 页面,那么 html 页面内容就是在 body 中。

现在我们就来简单实现一个能被浏览器访问的 HTTP 服务器

HTTP 服务器

一、实现效果

启动服务器后,在浏览器输入:

http://ip:port/

即可访问我们自己写的网页:

  • 支持返回 index.html
  • 支持简单页面跳转

二、基本原理

HTTP 服务器本质还是一个 TCP 服务器,只不过多了一步:

👉 解析请求 + 返回符合 HTTP 协议的数据

整体流程:

socket → bind → listen → accept ↓ 创建线程 ↓ read → 解析 → write

三、核心代码思路

1️⃣ 读取浏览器请求
char buffer[1024]; ssize_t s = read(sockfd, buffer, sizeof(buffer) - 1);

浏览器发来的请求大致是:

GET /index.html HTTP/1.1 Host: xxx
2️⃣ 解析请求行
std::stringstream ss(req_header[0]); std::string method, url, version; ss >> method >> url >> version;

我们只关心:

  • 请求方法(GET)
  • 访问路径(url)
3️⃣ 拼接文件路径
std::string path; if (url == "/" || url == "/index.html") path = "./wwwroot/index.html"; else path = "./wwwroot" + url;
4️⃣ 读取网页内容
std::ifstream in(path); while (std::getline(in, line)) { content += line + "\n"; }
5️⃣ 构造 HTTP 响应
std::string response; response = "HTTP/1.0 200 OK\r\n"; response += "Content-Length: " + std::to_string(content.size()) + "\r\n"; response += "Content-Type: text/html\r\n"; response += "\r\n"; response += content;

然后发送给浏览器:

write(sockfd, response.c_str(), response.size());
6️⃣ 简单处理 404
if (!in.is_open()) { std::string body = "<h1>404 Not Found</h1>"; response = "HTTP/1.0 404 Not Found\r\n"; response += "Content-Length: " + std::to_string(body.size()) + "\r\n"; response += "Content-Type: text/html\r\n"; response += "\r\n"; response += body; }

HTTP 服务器完整代码

Socket.hpp

#include <iostream>
#include <string>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <arpa/inet.h>
#include <cstring>
enum Err {
    SocketErr = 1,
    BindErr,
    ListenErr
};
class Socket {
public:
    Socket() { }
    void Init() {
        sockfd_ = socket(AF_INET, SOCK_STREAM, 0);
        if (sockfd_ < 0) {
            std::cout << "socket fail!!!" << std::endl;
            exit(SocketErr);
        }
    }
    void Bind(uint16_t port, std::string ip) {
        struct sockaddr_in server;
        memset(&server, 0, sizeof server);
        server.sin_family = AF_INET;
        server.sin_port = htons(port);
        inet_pton(AF_INET, ip.c_str(), &server.sin_addr);
        if (bind(sockfd_, (struct sockaddr *)&server, sizeof server) < 0) {
            std::cout << "bind fail" << std::endl;
            exit(BindErr);
        }
    }
    void Listen() {
        if (listen(sockfd_, 10) < 0) {
            std::cout << "listen fail" << std::endl;
            exit(ListenErr);
        }
    }
    int Accept(uint16_t *client_port, std::string *client_ip) {
        struct sockaddr_in client;
        bzero(&client, sizeof client);
        socklen_t len = sizeof(client);
        int newfd = accept(sockfd_, (struct sockaddr *)&client, &len);
        if (newfd < 0) {
            return -1;
        }
        char ip[64];
        inet_ntop(AF_INET, &client.sin_addr, ip, sizeof ip);
        *client_port = ntohs(client.sin_port);
        *client_ip = ip;
        return newfd;
    }
    bool Connect(uint16_t server_port, std::string server_ip) {
        struct sockaddr_in server;
        bzero(&server, sizeof server);
        server.sin_family = AF_INET;
        server.sin_port = htons(server_port);
        inet_pton(AF_INET, server_ip.c_str(), &server.sin_addr);
        if (connect(sockfd_, (struct sockaddr *)&server, sizeof(server)) < 0) {
            return false;
        }
        return true;
    }
    ~Socket() {
        close(sockfd_);
    }
private:
    int sockfd_;
};

HttpServer.cc

#include <iostream>
#include <unistd.h>
#include "Socket.hpp"
#include <fstream>
#include <vector>
#include <sstream>
const std::string wwwroot = "./wwwroot";
class HttpServer;
class ThreadData {
public:
    ThreadData(int sockfd, HttpServer *ts) : sockfd_(sockfd), ts_(ts) { }
public:
    int sockfd_;
    HttpServer *ts_;
};
class HttpServer {
public:
    HttpServer(uint16_t port, std::string ip) : port_(port), ip_(ip) { }
    void Init() {
        listenfd_.Init();
        listenfd_.Bind(port_, ip_);
        listenfd_.Listen();
    }
    void start() {
        while (1) {
            uint16_t client_port;
            std::string client_ip;
            int sockfd = listenfd_.Accept(&client_port, &client_ip);
            if (sockfd < 0) {
                continue;
            }
            ThreadData *td = new ThreadData(sockfd, this);
            pthread_t tid;
            pthread_create(&tid, nullptr, ThreadRun, td);
        }
    }
    static void *ThreadRun(void *arg) {
        pthread_detach(pthread_self());
        ThreadData *td = (ThreadData *)arg;
        td->ts_->HttpHandler(td->sockfd_);
        delete td;
        return nullptr;
    }
    std::string ReadHtmlContent(std::string &htmlpath) {
        std::string content;
        std::ifstream in(htmlpath.c_str());
        if (!in.is_open()) {
            return "404";
        }
        std::string line;
        while (std::getline(in, line)) {
            content += line;
        }
        in.close();
        return content;
    }
    void HttpHandler(int sockfd) {
        char buffer[1024];
        ssize_t s = read(sockfd, buffer, sizeof buffer - 1);
        std::string request;
        if (s > 0) {
            buffer[s] = 0;
            std::cout << buffer << std::endl;
            request = buffer;
            std::vector<std::string> req_header;
            while (!request.empty()) {
                ssize_t pos = request.find("\r\n", 0);
                if (pos == std::string::npos) {
                    break;
                }
                std::string line = request.substr(0, pos);
                if (line.empty()) {
                    break;
                }
                req_header.push_back(line);
                request.erase(0, pos + 2);
            }
            std::stringstream ss(req_header[0]);
            std::string method;
            std::string url;
            std::string http_version;
            ss >> method >> url >> http_version;
            std::string path = url;
            if (path == "/" || path == "/index.html") {
                path = wwwroot + "/index.html";
            } else {
                path = wwwroot + url;
            }
            std::string text = ReadHtmlContent(path);
            std::string response;
            if (text == "404") {
                std::string body = "<h1>404 Not Found</h1>";
                response = "HTTP/1.0 404 Not Found\r\n";
                response += "Content-Length: " + std::to_string(body.size()) + "\r\n";
                response += "Content-Type: text/html\r\n";
                response += "\r\n";
                response += body;
            } else {
                response = "HTTP/1.0 200 OK\r\n";
                response += "Content-Length: " + std::to_string(text.size()) + "\r\n";
                response += "Content-Type: text/html\r\n";
                response += "\r\n";
                response += text;
            }
            write(sockfd, response.c_str(), response.size());
        }
        close(sockfd);
    }
    ~HttpServer() { }
private:
    Socket listenfd_;
    uint16_t port_;
    std::string ip_;
};
int main(int argc, char *argv[]) {
    uint16_t port = std::stoi(argv[2]);
    std::string ip = argv[1];
    HttpServer *svr = new HttpServer(port, ip);
    svr->Init();
    svr->start();
    return 0;
}

index.html

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>index</title>
</head>
<body>
<h1>你是一个好人</h1>
<a href="http://127.0.0.1:8080/a/b/hello.html">下一页</a>
</body>
</html>

hello.html

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Hello World</title>
</head>
<body>
<h1>祝你幸福</h1>
<a href="http://127.0.0.1:8080/index.html">回到首页</a>
</body>
</html>

HTTP 的方法

其中最常用的就是 GET 和 POST 方法。

现在我们就来通过现象来看看 GET 和 POST 方法有什么不同。

GET 方法

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>index</title>
</head>
<body>
<h1>你是一个好人</h1>
<form action="http://127.0.0.1:8080/a/b/hello.html" method="GET">
银行卡账号:<input type="text" name="user"><br>
银行卡密码:<input type="password" name="pass"><br>
<input type="submit" value="登录">
</form>
<a href="http://127.0.0.1:8080/a/b/hello.html">下一页</a>
</body>
</html>

通过现象我们可以看到,我们在输入银行卡账号和密码之后,点击登录之后,并没有跳转到我们想要的界面,而是返回给我们 404 响应码,这是因为我们在使用 GET 方法之后,会将我们提交的参数拼接到我们的 url 之后,这就使得我们的 url 中就有了我们提交的参数信息。

POST 方法

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>index</title>
</head>
<body>
<h1>你是一个好人</h1>
<form action="http://127.0.0.1:8080/a/b/hello.html" method="POST">
银行卡账号:<input type="text" name="user"><br>
银行卡密码:<input type="password" name="pass"><br>
<input type="submit" value="登录">
</form>
<a href="http://127.0.0.1:8080/a/b/hello.html">下一页</a>
</body>
</html>

从结果来看,我们可以看到使用 POST 的方法的时候,POST 的方法会将我们提交的参数显示到 http 请求信息中的正文部分,我们可以从正文部分看到我们提交的参数信息。

从之前博客中的 TCP 通信,到现在我们亲手实现一个 HTTP 服务器,其实我们已经了解了

  • 一个请求是怎么被服务器解析的?
  • 浏览器和服务器到底在'说什么'?
  • GET 和 POST 的区别

总而言之就是:

👉 HTTP 不再是黑盒,而只是一个'有规则的字符串协议'

目录

  1. 认识 URL
  2. HTTP 协议
  3. HTTP 请求
  4. HTTP 响应
  5. HTTP 服务器
  6. 一、实现效果
  7. 二、基本原理
  8. 三、核心代码思路
  9. 1️⃣ 读取浏览器请求
  10. 2️⃣ 解析请求行
  11. 3️⃣ 拼接文件路径
  12. 4️⃣ 读取网页内容
  13. 5️⃣ 构造 HTTP 响应
  14. 6️⃣ 简单处理 404
  15. HTTP 服务器完整代码
  16. Socket.hpp
  17. HttpServer.cc
  18. index.html
  19. hello.html
  20. HTTP 的方法
  21. GET 方法
  22. POST 方法

更多推荐文章

查看全部
  • Linux 库制作与原理:从生成使用到 ELF 文件与链接解析
  • Qwen2 模型开源详情及百万级上下文扩展方案
  • Superset vs Metabase:2024 年开源 BI 工具深度横评与选型指南
  • Vite 中 import.meta.glob 批量导入模块用法指南
  • Java 中间件:Dubbo 服务降级(Mock 机制)
  • C++ 核心技术面试考点:语言特性与内存管理
  • 人工智能基础:DIKWP 模型与学习路径
  • NIC400生成Flow全解析(八)Micro Architechture
  • 二分查找算法原理与 C++ 实现详解
  • Python 入门必须掌握的基础知识
  • C++ 模板编程:typename 用法与分离编译问题解析
  • 66 个机器人项目合集:科研、教育、工业与医疗方向资源整理
  • C++ 引用、内联函数与 nullptr:效率提升三大基石
  • ComfyUI Photoshop 插件安装指南:实现 PS 内 AI 绘画
  • JVM 内存模型详解:运行时数据区结构解析
  • VR、具身智能与人形机器人:构建现实世界的智能接口
  • 通义万相 2.1 API 调用指南:Python 实现图像与文本生成
  • GitHub 国内镜像站汇总与 Git Clone 加速配置指南
  • 基于 FPGA 的 CLAHE 自适应限制对比度直方图均衡算法 Verilog 实现
  • 大模型应用中的 Prompt 提示词管理与优化实践

相关免费在线工具

  • 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