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

HTTP 协议详解及服务器模拟实现

HTTP 协议定义了请求与响应格式,包含常用方法、状态码及 Header 字段。通过 C++ Socket 编程可模拟实现 HTTP 服务器,解析请求并返回静态资源。Cookie 机制用于在客户端存储信息以维持会话状态。

修罗发布于 2026/3/28更新于 2026/7/3035 浏览
HTTP 协议详解及服务器模拟实现

URL

网址即 URL。如果在参数中出现特殊字符,会自动转义。

URL 示例

+ 被转义为 %2b。

URL 转义示例

HTTP 协议格式

请求格式

请求行:方法 + URL + 版本号 + \r\n 请求报头:每行一对键值对 + \r\n \r\n 空行(没有空行的话无法区分报头和正文) 正文(怎么确定正文的大小?报头中 content-length 确定)

请求格式

例如:

请求示例

HTTP 常用方法

HTTP 方法

常用的就 GET, POST。

响应格式

与请求类似。

响应格式

响应示例

常见状态码

状态码

常用状态码:200(OK), 404 (Not Found),403 (Forbidden),302(Redirect,重定向),504(Bad Gateway)。

HTTP 常见 Header

Content-Type: 数据类型 (text/html 等) Content-Length: Body 的长度 Host: 客户端告知服务器,所请求的资源是在哪个主机的哪个端口上; User-Agent: 声明用户的操作系统和浏览器版本信息; referer: 当前页面是从哪个页面跳转过来的; location: 搭配 3xx 状态码使用,告诉客户端接下来要去哪里访问; Cookie: 用于在客户端存储少量信息。通常用于实现会话 (session) 的功能;

模拟实现 HTTP 服务器

目录结构

目录结构

socket.hpp

#pragma once
#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include "log.hpp"
#include <string.h>
#include <unistd.h>
using namespace std;

Log lg;
const int backlog = 10;
enum { SocketErr = 2, BindErr, ListenErr };

class Sock {
public:
    Sock() {}
    ~Sock() {}
    void Socket() {
        sockfd_ = socket(AF_INET, SOCK_STREAM, 0);
        if (sockfd_ < 0) {
            lg(Fatal, "socket error, strerror: %s, errno: %d", strerror(errno), errno);
            exit(SocketErr);
        }
    }
    void Bind(uint16_t port) {
        struct sockaddr_in local;
        memset(&local, 0, sizeof(local));
        local.sin_family = AF_INET;
        local.sin_port = htons(port);
        local.sin_addr.s_addr = INADDR_ANY;
        if (bind(sockfd_, (struct sockaddr*)&local, sizeof(local)) < 0) {
            lg(Fatal, "Bind error, strerror: %s, errno: %d", strerror(errno), errno);
            exit(BindErr);
        }
    }
    void Listen() {
        if (listen(sockfd_, backlog) < 0) {
            lg(Fatal, "Listen Error, strerror: %s, errno: %d", strerror(errno), errno);
            exit(ListenErr);
        }
    }
    int Accept(std::string* ip, uint16_t* port) {
        struct sockaddr_in peer;
        socklen_t len = sizeof(peer);
        int newfd = accept(sockfd_, (struct sockaddr*)&peer, &len);
        if (newfd < 0) {
            lg(Warning, "accept error, strerror: %s, errno: %d", strerror(errno), errno);
            return -1;
        }
        char ipstr[64];
        inet_ntop(AF_INET, &peer.sin_addr, ipstr, sizeof(ipstr));
        *ip = ipstr;
        *port = ntohs(peer.sin_port);
        return newfd;
    }
    bool Connect(const string& ip, const uint16_t& port) {
        struct sockaddr_in peer;
        memset(&peer, 0, sizeof(peer));
        peer.sin_family = AF_INET;
        peer.sin_port = htons(port);
        inet_pton(AF_INET, ip.c_str(), &(peer.sin_addr));
        int n = connect(sockfd_, (struct sockaddr*)&peer, sizeof(peer));
        if (n < 0) {
            cerr << "Connect to" << ip << ":" << port << endl;
            return false;
        }
        return true;
    }
    void Close() { close(sockfd_); }
    int Fd() { return sockfd_; }
private:
    int sockfd_;
};

server.hpp

#pragma once
#include <iostream>
#include "Socket.hpp"
#include <string>
#include <pthread.h>
#include <fstream>
#include <vector>
#include <sstream>
#include <sys/types.h>
#include <sys/socket.h>
#include <unordered_map>

std::string blank_line = "\r\n";
std::string wwwroot = "./wwwroot";
std::string homepage = "index.html";

class HttpServer;

class ThreadData {
public:
    ThreadData(int sockfd, HttpServer* hs) : _sockfd(sockfd), _hs(hs) {}
public:
    int _sockfd;
    HttpServer* _hs;
};

class HttpResponse {
public:
    void Deserialize(std::string req) {
        while (true) {
            std::size_t pos = req.find(blank_line);
            if (pos == std::string::npos) break;
            std::string temp = req.substr(0, pos);
            if (temp.empty()) // 空行
                break;
            vc.push_back(temp);
            req.erase(0, pos + blank_line.size());
        }
        text += req;
    }
    void Parse() {
        std::stringstream ss(vc[0]);
        ss >> method >> url >> version;
        file_path = wwwroot;
        if (url == "/" || url == "index.html") {
            file_path += "/";
            file_path += homepage; // wwwroot/index.html
        } else {
            file_path += url; // wwwroot/a/b/t/t
        }
    }
    void DebugPrint() {
        for (auto line : vc) {
            std::cout << line << std::endl;
            std::cout << "------------" << std::endl;
        }
        std::cout << "method: " << method << std::endl;
        std::cout << "url: " << url << std::endl;
        std::cout << "version: " << version << std::endl;
        std::cout << "file_path: " << file_path << std::endl;
        std::cout << "text: " << text << std::endl;
    }
public:
    vector<string> vc;
    string text;
    std::string method;
    std::string url;
    std::string version;
    std::string file_path;
};

class HttpServer {
public:
    HttpServer(uint16_t port) : _port(port) {}
    void start() {
        _listen.Socket();
        _listen.Bind(_port);
        _listen.Listen();
        lg(Info, "server create success");
        for (;;) {
            std::string clientip;
            uint16_t port;
            int sockfd = _listen.Accept(&clientip, &port);
            lg(Info, "get a newfd:%d", sockfd);
            pthread_t tid;
            ThreadData* td = new ThreadData(sockfd, this);
            pthread_create(&tid, nullptr, ThreadRun, td);
        }
    }
    static std::string ReadHtmlContent(const std::string& htmlpath) {
        std::ifstream in(htmlpath);
        if (!in.is_open()) return "404";
        std::string content;
        std::string line;
        while (getline(in, line)) {
            content += line;
        }
        in.close();
        return content;
    }
    static void HandlerHttp(int sockfd) {
        char buffer[10240];
        ssize_t n = recv(sockfd, buffer, sizeof(buffer) - 1, 0);
        if (n > 0) {
            buffer[n] = 0;
            HttpResponse rep;
            rep.Deserialize(buffer);
            rep.Parse();
            rep.DebugPrint();
            std::string text = ReadHtmlContent(rep.file_path);
            std::string response_line = "HTTP/1.0 200 OK\r\n";
            std::string response_header = "Content-Length: ";
            response_header += std::to_string(text.size());
            std::string response = response_line;
            response += response_header;
            response += "\r\n";
            response += blank_line;
            response += text;
            send(sockfd, response.c_str(), response.size(), 0);
        }
        close(sockfd);
    }
    static void* ThreadRun(void* args) {
        pthread_detach(pthread_self());
        ThreadData* td = static_cast<ThreadData*>(args);
        HandlerHttp(td->_sockfd);
        close(td->_sockfd);
        delete td;
        return nullptr;
    }
    Sock _listen;
    uint16_t _port;
};

index.html

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title></head>
<body>
<h1>这个是我们的首页</h1>
<a href="http://127.0.0.1:8080/a/b/hello.html">到第二张网页</a>
<a href="http://127.0.0.1:8080/c/d/hello2.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>Document</title></head>
<body>
<h1>第二张图片</h1>
<h1>第二张图片</h1>
<h1>第二张图片</h1>
<a href="http://127.0.0.1:8080">回到首页</a>
<a href="http://127.0.0.1:8080/c/d/hello2.html">到第三张网页</a>
</body>
</html>

hello2.html

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title></head>
<body>
<h1>第三张图片</h1>
<a href="http://127.0.0.1:8080">回到首页</a>
<a href="http://127.0.0.1:8080/a/b/hello.html">到第三张网页</a>
</body>
</html>

Log.hpp

#pragma once
#include <iostream>
#include <stdlib.h>
#include <time.h>
#include <stdarg.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
using namespace std;
#define SIZE 1024
#define Info 0
#define Debug 1
#define Warning 2
#define Error 3
#define Fatal 4
#define Screen 1
#define Onefile 2
#define Classfile 3
#define Logfile "log.txt"

class Log {
public:
    Log() { printmethod = Screen; path = "./log/"; mkdir(path.c_str(), 0777); }
    void Enable(int method) { printmethod = method; }
    string levelToString(int level) {
        switch (level) {
        case Info: return "Info";
        case Debug: return "Debug";
        case Warning: return "Warning";
        case Error: return "Error";
        case Fatal: return "Fatal";
        }
    }
    void operator()(int level, const char* format, ...) {
        time_t t = time(nullptr);
        struct tm* ctime = localtime(&t);
        char leftbuffer[SIZE];
        snprintf(leftbuffer, sizeof(leftbuffer), "[%s][%d-%d-%d %d %d %d]", levelToString(level).c_str(), ctime->tm_year + 1900, ctime->tm_mon + 1, ctime->tm_mday, ctime->tm_hour, ctime->tm_min, ctime->tm_sec);
        va_list s;
        va_start(s, format);
        char rightbuffer[SIZE];
        vsnprintf(rightbuffer, sizeof(rightbuffer), format, s);
        va_end(s);
        char logtxt[SIZE * 2];
        snprintf(logtxt, sizeof(logtxt), "%s %s", leftbuffer, rightbuffer);
        printlog(level, logtxt);
    }
    void printlog(int level, const string& logtxt) {
        switch (printmethod) {
        case Screen: cout << logtxt << endl; break;
        case Onefile: printOnefile(Logfile, logtxt); break;
        case Classfile: printClassfile(level, logtxt); break;
        }
    }
    void printOnefile(const string& failname, const string& logtxt) {
        string _failname = path + failname;
        int fd = open(_failname.c_str(), O_WRONLY | O_CREAT | O_APPEND, 0666);
        if (fd < 0) return;
        write(fd, logtxt.c_str(), logtxt.size());
        close(fd);
    }
    void printClassfile(int level, const string& logtxt) {
        string _failname = Logfile;
        _failname += ".";
        _failname += levelToString(level);
        printOnefile(_failname, logtxt);
    }
    ~Log() {}
private:
    int printmethod;
    string path;
};

server.cc

#include <iostream>
#include "HttpServer.hpp"
using namespace std;

int main(int argc, char* argv[]) {
    if (argc != 2) exit(1);
    HttpServer* svr = new HttpServer(stoi(argv[1]));
    svr->start();
    return 0;
}

启动服务端

启动服务端

打开浏览器输入 ip:端口号

浏览器访问

实现成功,点击第二张网页

跳转测试

Xshell 查看浏览器发送的请求

请求包

我们可以看到域名也发生了变化,其实就是跳转到特定的文件

域名变化

Cookie

为什么我们第一次访问网站要登录账户,但是第二次就不需要再登录了? 因为浏览器帮我们记住账户了,账户信息存储在 Cookie 中,此外,服务器为我们分配了 ID,我们每次访问网站,浏览器都会带着 Cookie 和 ID 让服务器确认账号信息。

Cookie 信息在浏览器左上角,如果我们删除 Cookie 就会让我们再次输入账户信息。

Cookie 信息

客户端第一次向服务端发送账号和密码,服务器为客户端建立 Cookie 并分配 ID,之后客户端每次访问服务端都带着 Cookie 和 ID,服务端在数据库查找确认。

在我们服务器代码加入 Cookie 报头

response_header += "Set-Cookie: name=haha&&password=123456";
response_header += "\r\n";

左上角打开我们的浏览器 Cookie 信息

Cookie 设置

目录

  1. URL
  2. HTTP 协议格式
  3. 请求格式
  4. HTTP 常用方法
  5. 响应格式
  6. 常见状态码
  7. HTTP 常见 Header
  8. 模拟实现 HTTP 服务器
  9. socket.hpp
  10. server.hpp
  11. index.html
  12. hello.html
  13. hello2.html
  14. Log.hpp
  15. server.cc
  16. Cookie
  • 免费图片AI生成工具免费生成了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 免费图片视频在线生成30秒,将你的创意变成现实开始设计
  • X/Twitter免费视频下载器免登陆无限额度免费视频解析下载了解详情
  • 100+免费在线小游戏爽一把
极客日志微信公众号二维码

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

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

更多推荐文章

查看全部
  • 校招生做大模型,选预训练还是 SFT?
  • 开源 IP 地址管理工具 phpIPAM 安装与使用指南
  • Python 网页自动化实战:Selenium 与 Requests 应用
  • Ollama WebUI 深度测评:15 款主流开源前端界面选型指南
  • Go 语言泛型与 WebAssembly 技术解析
  • JDK 17 安装与环境配置实战指南
  • NASA 火星任务软件测试:AIGC 模拟极端环境攻击
  • Web 可访问性最佳实践:构建人人可用的前端界面
  • OpenClaw 技能精选:打造本地 AI 助手的超级插件市场
  • Trae 集成 Git 本地仓库管理与初始化指南
  • Flutter 混合开发技术选型与 PlatformView 实践指南
  • Java ForkJoin 框架详解:分治法与并行编程
  • C++ 命令行参数解析实现与源码演示
  • llama.cpp 新增 Web UI,本地大模型部署新方案实测
  • OpenClaw + MCP:构建支持任意工具的 AI 助手
  • MCP 服务器大全:分类汇总与开发指南
  • 编程基础概念:逻辑(Logic)详解
  • Web 服务器负载均衡深度解析:Nginx 配置实践
  • 逆向 WebChat 协议:为 OpenClaw 打造零侵入接入方案
  • C++ 二叉搜索树 (BST) 详解:原理、核心操作与实战实现

相关免费在线工具

  • 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