Server 函数手册
C++ 后端服务器框架Server 函数手册
CHAPTER 22

Server 函数手册

8 分钟阅读 · 1,779 字

依据源码:include/Server.h、src/Server.cpp。
本模块是项目主干,本文特别按你要求区分两种运行模式:

  • 线程池版本:accept -> ThreadPool::addTask(handleClient)(历史/常规)
  • 协程版本:accept -> Scheduler::submit(handleClient)(当前源码默认)

0. 双版本切换点(先看)

在 Server::run() 主循环中:

  • 线程池投递代码被注释:
    • threadpool_.addTask(&Server::handleClient, this, clientSocket, &clientAddress);
  • 当前启用协程调度投递:
    • Scheduler::instance().submit(&Server::handleClient, this, clientSocket, &clientAddress);

因此现状是协程版本在跑,但代码结构保留了线程池版本,面试可讲“可回切”。


1. 顶层自由函数与静态变量

1.1 std::string mpToJson(const std::map<std::string, std::string>& mp)

  • 将简单字符串 map 拼成 JSON 文本。
  • 逻辑较朴素:手工拼接,最后 pop_back 去掉尾逗号。
  • 风险:未做 JSON 转义(value 若含引号/换行可能不合法)。

1.2 Server* Server::instance_

  • 单例指针,用于信号回调中访问当前 Server。

1.3 匿名命名空间常量(接收策略)

  • RECV_CHUNK_SIZE = 64KB
  • MAX_HEADER_SIZE = 1MB
  • MAX_BODY_SIZE = 100MB

1.4 static size_t parseContentLengthFromHeaders(const std::string& headerPart)

  • 小写化头文本后查找 content-length:
  • 解析连续数字,不合法返回 0
  • 用于“先收头后收体”的两阶段收包

2. Server 生命周期函数

2.1 Server::Server(int port, bool printParams)

作用

初始化服务实例,接入崩溃处理与信号处理。

关键步骤

  1. 初始化成员:端口、socket=-1、running=false、日志参数
  2. 初始化 threadpool_(hardware_concurrency 或 1)
  3. instance_ = this
  4. CrashHandler::instance().init([this]{ cleanup(); })
  5. registerSignalHandlers()

2.2 Server::~Server()

  • 输出 "Shutting down server...",调用 stop()

2.3 void Server::cleanup()

用于崩溃路径与 run 退出收尾。

  1. 加 mutex_
  2. running_ = false
  3. 关闭 serverSocket_
  4. Scheduler::instance().stop()
  5. 输出清理日志

注意:打印了 "Thread pool shutdown",但实际调用的是 Scheduler::stop();这是文案与实现不一致点。


2.4 void Server::stop()

正常停服入口:

  1. 若已停止则返回
  2. running_ = false
  3. Scheduler::instance().stop()
  4. 关闭 serverSocket_
  5. Windows 下 WSACleanup()

3. 信号处理相关

3.1 void Server::registerSignalHandlers()

  • 注册 SIGINT(Ctrl+C)
  • Windows 额外 SIGBREAK
  • Linux 额外 SIGTERM
  • 回调中仅将 instance_->running_ = false

3.2 void Server::signalHandler(int)

  • 当前空实现(保留接口)。

4. 路由注册与查找

4.1 基础注册函数

  • get/post/put/del(带/不带 middleware 两组重载)
  • 最终都落到:
    • addRoute(method, path, handler, middlewares)

4.2 void addRoute(...)

  • 加锁 routesMutex_
  • routes_[method][path] = RouteEntry{handler, middlewares}

4.3 void use(Middleware middleware)

  • 全局 middleware 追加到 middlewares_

4.4 const RouteEntry* findRoute(const std::string& method, const std::string& path) const

  • 精确匹配:method map -> path map
  • 未命中返回 nullptr

5. RouterGroup 函数族

5.1 构造

  • RouterGroup(Server& app, std::string prefix, std::vector<Middleware> middlewares)

5.2 group(prefix)

  • 前缀拼接(处理 / 重复)
  • 继承上级 middlewares

5.3 use(middleware)

  • 追加组级 middleware

5.4 resolvePath(path)

  • 处理前缀与子路径拼接规则:
    • a/ + /b -> a/b
    • a + b -> a/b

5.5 mergeMiddlewares(routeMiddlewares)

  • 组级在前,路由级在后,生成最终链

5.6 get/post/put/del(两组重载)

  • 调 app_.addRoute(HTTP_METHOD, resolvePath(path), handler, mergeMiddlewares(...))

6. 运行主流程 run()(双版本核心)

6.1 公共启动阶段

  1. Windows 下 WSAStartup
  2. 创建/绑定 IPv4 socket
  3. 尝试创建/绑定 IPv6 socket(失败不阻断 IPv4)
  4. listen(serverSocket_, 10)
  5. 打印路由与地址信息
  6. 启动协程调度器:
    • configureTimeSlice(5ms)
    • start(hardware/2)
    • submit(doTaskRegular, 30min)

6.2 主 accept 循环

while (running_) {
  clientSocket = accept(...)
  if error -> continue
  dispatch(handleClient)
}
cleanup()

6.3 分派差异(面试重点)

A) 线程池版本(可回切)

  • threadpool_.addTask(&Server::handleClient, this, clientSocket, &clientAddress)
  • 特点:
    • 每连接一个线程池任务
    • 阻塞 recv/send 会占 worker 线程
    • 代码心智简单

B) 协程版本(当前启用)

  • Scheduler::instance().submit(&Server::handleClient, this, clientSocket, &clientAddress)
  • 特点:
    • 任务调度在协程框架中运行
    • handleClient 内多处调用 safe_point() 主动让出
    • 仍基于阻塞 socket,但可通过协程安全点减少单任务长期独占

关键说明:当前并非完整非阻塞 I/O 协程,网络调用仍是阻塞 recv/send。


7. 客户端处理函数(HTTP 主链)

7.1 void handleClient(int clientSocket, const sockaddr_in* clientAddress)

阶段一:收请求头

  1. 循环 recv
  2. 查找 \r\n\r\n 或 \n\n
  3. 超过 MAX_HEADER_SIZE 直接关闭
  4. 协程版本在循环尾部 safe_point()

阶段二:收请求体

  1. parseContentLengthFromHeaders
  2. 若 body 超过 MAX_BODY_SIZE 关闭连接
  3. 继续读到 headerEnd + contentLength
  4. 每轮 safe_point()

阶段三:解析与分发

  1. parseRequest(requestStr)
  2. 构建 Response
  3. 若 OPTIONS 直接 200
  4. 执行全局 middlewares(可短路)
  5. findRoute
  6. 执行路由 middlewares(可短路)
  7. 执行 handler
  8. 异常 -> response.error(500, ...)
  9. 未命中 -> 404

阶段四:回包与日志

  1. responseStr = buildResponse(response)
  2. 发送采用“循环 send 直到 remaining==0”
  3. 每轮 send 后 safe_point()
  4. 若 response.streamWriter,执行流式发送
  5. 打印访问日志(IP/method/path/code/bodyLen/cost)
  6. 关闭客户端 socket

7.2 void handleClientIPv6(int clientSocket, const sockaddr_in6* clientAddress)

  • 逻辑与 IPv4 版本基本一致
  • 当前发送仍是一次 send(不像 IPv4 版本有 remaining 循环)
  • 日志 IP 使用 getClientIPv6
  • 在 listenIPv6 中通过 threadpool_.addTask 分发(注意与主 accept 的协程分发不同)

这意味着当前 IPv6 分支仍偏线程池路径,属于双模型并存状态。


7.3 void listenIPv6(int serverSocketIPv6)

  • 循环 accept IPv6
  • 成功后 threadpool_.addTask(handleClientIPv6, ...)
  • 退出后关闭 IPv6 socket

8. 请求/响应编解码函数

8.1 Request parseRequest(const std::string& requestStr)

  1. 分离 header/body
  2. 解析请求行:method/path
  3. 解析 query:parseQueryParams
  4. 解析 headers(trim)
  5. 根据 Content-Length 截取 body
  6. request.parseBody()(解析 form/json/multipart 等)

8.2 std::string buildResponse(const Response& response)

  1. 写状态行(200/201/400/404/500)
  2. 注入 CORS 头
  3. 写自定义 headers
  4. 若未设置 Content-Length 自动补齐
  5. 空行分隔
  6. 非流式时追加 body

8.3 std::map<std::string,std::string> parseQueryParams(const std::string& query)

  • 按 & 切分,再按 = 切分
  • value 用 urlDecode

8.4 std::string urlDecode(const std::string& value)

  • + -> 空格
  • %XX 十六进制解码
  • 非法 % 编码保留原字符

9. 网络与工具函数

9.1 std::string getLanIpv4()

  • 创建 UDP socket,connect 到 8.8.8.8:53
  • 通过 getsockname 获取本机出站 IP
  • 失败回退 127.0.0.1

9.2 std::string getLanIpv6()

  • 类似 IPv4 逻辑,目标 2001:4860:4860::8888
  • 过滤 link-local 与 loopback,失败回退 ::1

9.3 std::string getClientIP(...) / getClientIPv6(...)

  • inet_ntop/InetNtopA 转字符串

9.4 void printRegisteredRoutes() const

  • 按 GET/POST/PUT/DELETE 分组打印路由表

9.5 std::string getFormattedDate()

  • 输出形如 %d/%b/%Y:%H:%M:%S +0800

10. 定时任务函数

void doTaskRegular(long long during) const

  • 参数必须 > 0
  • 使用 steady_clock 做间隔调度
  • 每到间隔调用 task()
  • 捕获异常避免循环崩溃
  • 动态 sleep(interval/10 或剩余时间)

该函数由 Scheduler::submit 启动,和主请求处理并行运行。


11. Request / Response 内联函数要点(Server.h)

11.1 Request 相关

  • query_param / body_param / json_param / file
  • parseBody 按 content-type 分发:
    • urlencoded / json / multipart / text / xml / autoDetect
  • parseMultipartFormData 与 parseMultipartPart 实现了简化文件上传解析

11.2 Response 相关

  • json/text/success/error
  • file:整文件读取到 body
  • fileStream:通过 streamWriter 分块发送大文件
  • getFileNameWithExt

12. 双版本面试答法(可直接复述)

  1. 线程池版本:模型稳定、实现直观,但阻塞 I/O 会占住 worker。
  2. 协程版本:通过 Scheduler::submit + safe_point 提升任务切换能力,减少长任务独占。
  3. 当前状态:是“协程调度 + 阻塞 socket”的过渡架构,不是完整 event-loop 非阻塞架构。
  4. 下一步演进:将 socket 改为非阻塞并绑定 epoll/IOCP 事件,再把可等待点映射到协程挂起/恢复。

下一模块(顺序)

Threadpool → docs/modules/detail/Threadpool_FUNCTIONS.md

全部章节