【高并发服务器项目】2.服务器业务层设计详解

目录

一、业务相关模块

[1.1 HttpRequest](#1.1 HttpRequest)

[1.2 HttpResponse](#1.2 HttpResponse)

[1.3 HttpContext](#1.3 HttpContext)

[1.4 HttpServer](#1.4 HttpServer)

二、处理过程

[2.1 构造服务器](#2.1 构造服务器)

[2.2 设置自定义协议](#2.2 设置自定义协议)

[2.3 设置路由](#2.3 设置路由)

[2.4 收到消息处理流程](#2.4 收到消息处理流程)

[2.4.1 初始状态](#2.4.1 初始状态)

[2.4.2 反序列化(重点)](#2.4.2 反序列化(重点))

[2.4.3 路由分发](#2.4.3 路由分发)

[2.4.4 响应发送](#2.4.4 响应发送)

三、压力测试

[3.1 测试环境](#3.1 测试环境)

[3.2 测试结果](#3.2 测试结果)


一、业务相关模块

1.1 HttpRequest

cpp 复制代码
class HttpRequest
{
public:
    std::string _method;                           // 请求方法 (GET/POST/PUT/DELETE)
    std::string _path;                             // 请求路径
    std::string _version;                          // HTTP版本
    std::string _body;                             // 请求体
    std::smatch _matches;                          // 正则匹配结果
    std::unordered_map<std::string, std::string> _headers;  // 请求头
    std::unordered_map<std::string, std::string> _params;   // 查询参数
};

作用:存储客户端发送的HTTP请求信息

示例https://blog.csdn.net/m0_68617924?spm=1010.2135.3001.5343

解析后的结果:

  • method: GET

  • path: /m0_68617924

  • version: HTTP/1.1

  • headers: Host: blog.csdn.net, User-Agent: Mozilla/5.0, Accept: text/html

  • params: spm=1010.2135.3001.5343

1.2 HttpResponse

cpp 复制代码
class HttpResponse
{
public:
    int _statu;                           // 状态码 (200, 404, 500等)
    bool _redirect_flag;                  // 是否重定向
    std::string _redirect_url;            // 重定向URL
    std::string _body;                    // 响应体
    std::unordered_map<std::string, std::string> _headers;  // 响应头
};

作用:存放HttpRequest经过路由函数处理后的结果

路由函数统一格式

cpp 复制代码
void Func(const HttpRequest& req, HttpResponse* rsp)

该函数支持:

  • 重定向功能

  • 各种格式的响应体

1.3 HttpContext

cpp 复制代码
class HttpContext
{
private:
    int _resp_statu;           // 响应状态
    HttpRecvStatu _recv_statu; // 接收状态
    HttpRequest _request;      // 请求对象
};

作用:管理HTTP协议的解析状态。该模块需要和HttpServer一起理解。

1.4 HttpServer

cpp 复制代码
class HttpServer
{
    using Handler = std::function<void(const HttpRequest&, HttpResponse*)>;
    using Handlers = std::vector<std::pair<std::regex, Handler>>;

private:
    Handlers _get_route;      // GET路由表
    Handlers _post_route;     // POST路由表
    Handlers _put_route;      // PUT路由表
    Handlers _delete_route;   // DELETE路由表
    std::string _basedir;     // 基础目录
    TcpServer _server;        // TCP服务器
};

作用:核心模块,负责:

  1. 将缓冲区数据反序列化成HttpRequest

  2. 调用回调函数处理成HttpResponse

  3. 将响应发送给客户端浏览器


二、处理过程

2.1 构造服务器

cpp 复制代码
HttpServer(int port, int timeout = 10) : _server(port)
{
    _server.EnableInactiveRelease(timeout);
    _server.SetConnectedCallBack(std::bind(&HttpServer::OnConnected, 
                                  this, std::placeholders::_1));
    _server.SetMessageCallBack(std::bind(&HttpServer::OnMessage,
                                  this, std::placeholders::_1, 
                                  std::placeholders::_2));
}

功能

  • 启动服务器并监听端口

  • 获取用户连接

  • 设置底层回调函数

2.2 设置自定义协议

cpp 复制代码
void OnConnected(const PtrConnection& conn)
{
    conn->SetContext(HttpContext());
}

作用 :有连接进来时调用,将连接中的any类型设置为HttpContext,用于存储反序列化草稿,实现自定义协议。

2.3 设置路由

cpp 复制代码
server.SetBaseDir("./wwwroot/");
server.Get("/hello", Hello);
server.Post("/login", Login);
server.Put("/1234.txt", PutFile);
server.Delete("/1234.txt", DelFile);

业务处理示例(复读机):

cpp 复制代码
std::string RequestStr(const HttpRequest& req)
{
    std::stringstream ss;
    ss << req._method << " " << req._path << " " 
       << req._version << "\r\n";
    
    for (auto& it : req._params)
    {
        ss << it.first << ": " << it.second << "\r\n";
    }
    
    ss << "\r\n";
    ss << req._body;
    return ss.str();
}

void Hello(const HttpRequest& req, HttpResponse* rsp)
{
    rsp->SetContent(RequestStr(req), "text/plain");
}

说明 :上层业务处理就是告诉服务器如何将req转为rsp,这些函数会被存入HttpServer对应的路由表中。

2.4 收到消息处理流程

2.4.1 初始状态
  • 连接的写入缓冲区有数据

  • 草稿区(any)已被初始化为HttpContext,但未开始反序列化,内容为空

  • 发送缓冲区为空(还没有路由处理函数)

2.4.2 反序列化(重点)
cpp 复制代码
void RecvHttpRequest(buffer* buf)
{
    switch (_recv_statu)
    {
    case RECV_HTTP_LINE:
        RecvHttpLine(buf);
    case RECV_HTTP_HEAD:
        RecvHttpHead(buf);
    case RECV_HTTP_BODY:
        RecvHttpBody(buf);
    }
}

状态枚举

cpp 复制代码
typedef enum
{
    RECV_HTTP_ERROR,  // 错误状态
    RECV_HTTP_LINE,   // 解析请求行
    RECV_HTTP_HEAD,   // 解析请求头
    RECV_HTTP_BODY,   // 解析请求体
    RECV_HTTP_OVER    // 解析完成
} HttpRecvStatu;

关键点

  • 初始状态为RECV_HTTP_LINE

  • 状态和草稿都会保存在any

  • 下次反序列化时继续上次操作,无需分类讨论

获取请求行

cpp 复制代码
bool RecvHttpLine(buffer* buf)
{
    if (_recv_statu != RECV_HTTP_LINE)
        return 0;
    
    std::string line = buf->GetLineAndPop();
    bool ret = ParseHttpLine(line);
    
    if (!ret)
        return 0;
    
    _recv_statu = RECV_HTTP_HEAD;
    return 1;
}

正则表达式切割

cpp 复制代码
bool ParseHttpLine(const std::string& line)
{
    std::smatch matches;
    std::regex e("(GET|HEAD|POST|PUT|DELETE) ([^?]*)(?:\\?(.*))? (HTTP/1\\.[01])(?:\\n|\\r\\n)?", 
                 std::regex::icase);
    
    bool ret = std::regex_match(line, matches, e);
    if (ret == 0)
    {
        _recv_statu = RECV_HTTP_ERROR;
        _resp_statu = 400;
        return 0;
    }
    
    _request._method = matches[1];
    _request._path = Util::UrlDecode(matches[2], 0);
    _request._version = matches[4];
    
    // 解析查询参数
    std::string query = matches[3];
    std::vector<std::string> query_array;
    Util::Split(query, "&", &query_array);
    
    for (auto& str : query_array)
    {
        size_t pos = str.find("=");
        std::string key = Util::UrlDecode(str.substr(0, pos), 1);
        std::string val = Util::UrlDecode(str.substr(pos + 1), 1);
        _request.SetParam(key, val);
    }
    
    return 1;
}
2.4.3 路由分发
cpp 复制代码
void Route(HttpRequest& req, HttpResponse* rsp)
{
    // 1. 检查是否为静态文件请求
    if (IsFileHandler(req))
        return FileHandler(req, rsp);
    
    // 2. 根据请求方法分发
    if (req._method == "GET" || req._method == "HEAD")
    {
        return Dispatcher(req, rsp, _get_route);
    }
    else if (req._method == "POST")
    {
        return Dispatcher(req, rsp, _post_route);
    }
    else if (req._method == "PUT")
    {
        return Dispatcher(req, rsp, _put_route);
    }
    else if (req._method == "DELETE")
    {
        return Dispatcher(req, rsp, _delete_route);
    }
    
    // 3. 未找到匹配路由
    rsp->_statu = 404;
}

设计特点

  • 每个HTTP方法对应一个路由数组

  • 使用正则表达式匹配路径,支持灵活的路由规则

  • 遍历数组查找匹配项,未找到则返回404

2.4.4 响应发送
cpp 复制代码
void WriteResponse(const PtrConnection& conn, HttpRequest& req, HttpResponse& rsp)
{
    // 1. 设置Connection头
    if (req.Close() == 1)
        rsp.SetHeader("Connection", "close");
    else
        rsp.SetHeader("Connection", "keep-alive");
    
    // 2. 设置Content-Length
    if (!rsp._body.empty() && !rsp.HasHeader("Content-Length"))
        rsp.SetHeader("Content-Length", std::to_string(rsp._body.size()));
    
    // 3. 设置Content-Type
    if (!rsp._body.empty() && !rsp.HasHeader("Content-Type"))
        rsp.SetHeader("Content-Type", "application/octet-stream");
    
    // 4. 处理重定向
    if (rsp._redirect_flag == 1)
        rsp.SetHeader("Location", rsp._redirect_url);
    
    // 5. 序列化响应
    std::stringstream rsp_str;
    rsp_str << req._version << " " 
            << std::to_string(rsp._statu) << " " 
            << Util::StatuDesc(rsp._statu) << "\r\n";
    
    for (auto& head : rsp._headers)
        rsp_str << head.first << ": " << head.second << "\r\n";
    
    rsp_str << "\r\n";
    rsp_str << rsp._body;
    
    // 6. 发送
    conn->Send(rsp_str.str().c_str(), rsp_str.str().size());
}

三、压力测试

3.1 测试环境

  • 服务器配置:2核心8G云服务器

  • 测试工具:webbench-v1.5

  • 并发量:5000

  • 测试时长:12小时

  • 测试方式:客户端与服务器在同一台机器运行

3.2 测试结果

cpp 复制代码
Webbench - Simple Web Benchmark 1.5
Copyright (c) Radim Kolar 1997-2004, GPL Open Source Software.

Benchmarking: GET http://127.0.0.1:8085/hello
5000 clients, running 43200 sec.

Speed=171204 pages/min, 7057 bytes/sec.
Requests: 123267172 succeed, 0 failed.

性能指标

  • 平均请求数:171,204次/分钟

  • 成功请求:123,267,172次

  • 失败请求:0次

相关推荐
样例过了就是过了2 小时前
LeetCode热题100 跳跃游戏 II
c++·算法·leetcode·贪心算法·动态规划
charlie1145141912 小时前
现代Qt开发——0.1——如何在IDE中配置Qt环境?
开发语言·c++·ide·qt·嵌入式
计算机安禾2 小时前
【数据结构与算法】第32篇:交换排序(一):冒泡排序
c语言·数据结构·c++·算法·链表·排序算法·visual studio code
胖咕噜的稞达鸭2 小时前
C/C++动态内存管理,malloc,calloc,realloc的区别,动态内存中的错误汇总
c语言·开发语言·c++
charlie1145141912 小时前
嵌入式C++教程实战之Linux下的单片机编程(6):从点亮第一盏LED开始 —— 我们为什么要用现代C++写STM32
linux·c语言·开发语言·c++·stm32·单片机
linux开发之路2 小时前
C++实现Whisper+Kimi端到端AI智能语音助手
c++·人工智能·llm·whisper·openai
艾莉丝努力练剑2 小时前
【Linux系统:多线程】线程概念与控制
linux·运维·服务器·c++·后端·学习·操作系统
AIminminHu2 小时前
OpenGL渲染与几何内核那点事-项目实践理论补充(二-1-(2):当你的CAD学会“听话”:从鼠标点击到自然语言命令)
c++·人工智能
恒者走天下3 小时前
手机行业cpp c++相关就业岗位详细汇总
c++