跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客GitHub 精选镜像工具UI配色美学隐私政策关于联系
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
C++大前端

C++ 手写 HTTP 服务器:从请求解析到响应构建全流程

C++ 手写 HTTP 服务器涉及 TCP 通信基础、HTTP 协议结构解析及代码实现。文章涵盖 URL 与 DNS 原理、请求响应报文格式、Socket 编程模型以及 GET 与 POST 方法差异。通过完整代码示例展示如何构建支持文件访问的简易 Web 服务器,帮助理解网络编程底层逻辑。

莫名其妙发布于 2026/3/26更新于 2026/6/513 浏览
C++ 手写 HTTP 服务器:从请求解析到响应构建全流程

C++ 手写 HTTP 服务器:从请求解析到响应构建全流程

在网络编程中,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 与 DNS 解析

当我们通过浏览器访问网站时,输入的其实是域名。例如 https://www.baidu.com/。浏览器并不会直接通过这个字符串访问,而是需要先将其解析为对应的 IP 地址。

具体流程如下:

  • 客户端(浏览器或操作系统)向本地 DNS 服务器发送解析请求。
  • 如果本地 DNS 服务器没有缓存结果,它会向更高层级的 DNS 服务器发起查询:根域名服务器 -> 顶级域名服务器 -> 权威域名服务器。
  • 最终找到该域名对应的 IP 地址并返回给客户端。

拿到 IP 地址后,客户端通过 IP 地址 + 端口号 建立连接。你可能注意到 HTTPS 链接中通常不显示端口号,这是因为 HTTPS 协议隐含了默认端口 443,就像生活中 110 代表报警电话一样,浏览器会自动补全,而非端口不存在。

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

HTTP 协议结构

HTTP 通信本质上是在 TCP 之上,按照固定格式传输的一段'结构化字符串'。一次完整的 HTTP 通信包含请求报文和响应报文。

请求报文结构

  1. 请求行:包含请求方法(如 GET、POST)、资源路径以及 HTTP 版本。明确'我要做什么、访问哪里、用什么协议版本'。
  2. 请求报头:多行 key:value 形式,携带额外信息(如 Host、Content-Type)。以 \r\n 分隔。
  3. 空行:表示报头结束,后续内容即为正文。
  4. 请求正文:GET 请求通常为空,POST 请求则携带表单或 JSON 数据。

响应报文结构

  1. 状态行:包含 HTTP 版本、状态码及描述信息(如 200 OK)。
  2. 响应报头:说明返回数据的类型、长度等。
  3. 空行:分隔元数据和数据内容。
  4. 响应正文:客户端真正关心的部分,如 HTML、JSON 或图片数据。

HTTP 服务器实现

接下来我们尝试用 C++ 实现一个能被浏览器访问的简易 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://localhost: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://localhost:8080/index.html">回到首页</a>
</body>
</html>

HTTP 的方法

其中最常用的就是 GET 和 POST 方法。我们通过现象来看看它们有什么不同。

GET 方法

<form action="http://localhost:8080/a/b/hello.html" method="GET">
银行卡账号:<input type="text" name="user"><br>
银行卡密码:<input type="password" name="pass"><br>
<input type="submit" value="登录">
</form>

使用 GET 方法提交表单后,参数会拼接到 URL 之后。这意味着敏感信息(如密码)可能会暴露在地址栏或日志中,且 URL 长度有限制。

POST 方法

<form action="http://localhost:8080/a/b/hello.html" method="POST">
银行卡账号:<input type="text" name="user"><br>
银行卡密码:<input type="password" name="pass"><br>
<input type="submit" value="登录">
</form>

使用 POST 方法时,提交的参数显示在 HTTP 请求信息的正文部分(Body),不会出现在 URL 中,相对更安全,适合传输敏感数据。

总结

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

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

总而言之,HTTP 不再是黑盒,而只是一个'有规则的字符串协议'。理解这一点,对深入掌握网络编程至关重要。

目录

  1. C++ 手写 HTTP 服务器:从请求解析到响应构建全流程
  2. 常见应用层协议概览
  3. 认识 URL 与 DNS 解析
  4. HTTP 协议结构
  5. 请求报文结构
  6. 响应报文结构
  7. HTTP 服务器实现
  8. 一、实现效果
  9. 二、基本原理
  10. 三、核心代码思路
  11. 1. 读取浏览器请求
  12. 2. 解析请求行
  13. 3. 拼接文件路径
  14. 4. 读取网页内容
  15. 5. 构造 HTTP 响应
  16. 6. 简单处理 404
  17. HTTP 服务器完整代码
  18. Socket.hpp
  19. HttpServer.cc
  20. index.html
  21. hello.html
  22. HTTP 的方法
  23. GET 方法
  24. POST 方法
  25. 总结
  • 💰 8折买阿里云服务器限时8折了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 🤖 一键搭建Deepseek满血版了解详情
  • 一键打造专属AI 智能体了解详情
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • Gemini Pro 实测:多模态、推理与代码能力的真实表现
  • AIGC 检测技术:如何识别 AI 生成内容并保障原创性
  • 利用 AI 智能体快速完成 C 语言与前端实训项目实战
  • MySQL 备份与恢复实战:XtraBackup 和 mysqldump 详解
  • C++ 红黑树实现详解:规则、效率与核心操作
  • AirSim 无人机仿真入门:实现起飞与降落
  • C++ 模板的幻觉:实例化、重定义与隐藏依赖
  • 基于 ECharts 与 Flask 的交互式数据可视化应用开发
  • 基于 Java 和 Spring Boot 的疫苗接种管理系统设计与实现
  • SpringBoot3+Vue3 大事件项目前端代码优化与功能增强
  • AI 时代初级开发者如何从执行者升级为创意主厨
  • 适合 Java 算法刷题的优质网站推荐
  • 自然语言处理技术与应用实践
  • FLUX.1-dev 本地部署与 Midjourney 迁移指南及 Prompt 工程适配
  • 利用 GitHub Copilot 和 Figma MCP 还原设计稿生成前端代码
  • 库博(CoBOT):嵌入式 C/C++ 代码质量全流程守护方案
  • 基于 Axios 的前端文件上传下载实战指南
  • SpringBoot 项目创建的 5 种主流方式
  • LLaMaFactory 基于魔搭社区免费 GPU 微调大模型实战
  • Rust 获取系统资源监控及自动壁纸设置

相关免费在线工具

  • 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