
🎬 个人主页 :艾莉丝努力练剑
❄专栏传送门 :《C语言》《数据结构与算法》《C/C++干货分享&学习过程记录》
《Linux操作系统编程详解》《笔试/面试常见算法:从基础到进阶》《Python干货分享》
⭐️为天地立心,为生民立命,为往圣继绝学,为万世开太平
🎬 艾莉丝的简介:

文章目录
- [1 ~> ChatServer main.cpp 工程实现](#1 ~> ChatServer main.cpp 工程实现)
-
- [1.1 整体需求规约](#1.1 整体需求规约)
-
- [1.1.1 业务需求清单](#1.1.1 业务需求清单)
- [1.1.2 文件约束](#1.1.2 文件约束)
- [1.2 ServerConfig 结构体定义](#1.2 ServerConfig 结构体定义)
- [1.3 gflags 参数定义 main.cpp 头部](#1.3 gflags 参数定义 main.cpp 头部)
- [1.4 工具函数实现](#1.4 工具函数实现)
-
- [1.4.1 环境变量读取工具](#1.4.1 环境变量读取工具)
- [1.4.2 信号处理回调函数](#1.4.2 信号处理回调函数)
- [1.4.3 参数校验函数 validateConfig](#1.4.3 参数校验函数 validateConfig)
- [1.4.4 帮助打印函数 showAPIInfo](#1.4.4 帮助打印函数 showAPIInfo)
- [1.5 main 主函数完整实现](#1.5 main 主函数完整实现)
- [1.6 ChatServer::start 内部线程实现 (ChatServer.cpp)](#1.6 ChatServer::start 内部线程实现 (ChatServer.cpp))
- [2 ~> ChatServer.conf 配置文件模板](#2 ~> ChatServer.conf 配置文件模板)
- [3 ~> 环境变量配置(云端 API 密钥)](#3 ~> 环境变量配置(云端 API 密钥))
- [4 ~> 关键易错点总结(面试高频)](#4 ~> 关键易错点总结(面试高频))
-
- [4.1 gflags flagfile 加载顺序](#4.1 gflags flagfile 加载顺序)
- [4.2 线程模型关键点](#4.2 线程模型关键点)
- [4.3 信号处理注意事项](#4.3 信号处理注意事项)
- [4.4 密钥安全规范](#4.4 密钥安全规范)
- [4.5 头文件路径坑](#4.5 头文件路径坑)
- [4.6 参数校验边界](#4.6 参数校验边界)
- [5 ~> CMakeLists.txt 关键片段](#5 ~> CMakeLists.txt 关键片段)
- [6 ~> 程序运行命令示例](#6 ~> 程序运行命令示例)
- 结尾

1 ~> ChatServer main.cpp 工程实现
背景: 基于 C++ + gflags + spdlog 二次封装日志库,实现 AI 聊天服务入口 main 程序;核心职责:参数解析、配置校验、日志初始化、信号处理、服务启动、主线程保活。
1.1 整体需求规约
1.1.1 业务需求清单
- 使用 gflags 库定义全部
ServerConfig配置项;参数来源优先级:gflags 默认值 < ChatServer.conf 配置文件 < 命令行参数 - 网络服务默认绑定地址
0.0.0.0,端口8080;默认日志级别INFO - 云端大模型(deepseek /chatgpt/gemini)API 密钥从操作系统环境变量读取,禁止通过命令行传入;Ollama 全套配置通过 gflags 命令行 / 配置文件提供
- 模型默认参数:
temperature=0.7,max_tokens=2048 - 配置文件
ChatServer.conf放置在 ChatServer 项目目录,由 gflags 原生 flagfile 机制加载,禁止手写自定义配置解析逻辑,禁止代码生成配置文件 - 命令行功能:
-h / --help:打印帮助文档、使用示例、全部 HTTP API 接口说明-v / --version:打印程序版本号,默认版本1.0.0
- 参数合法性校验规则
- temperature 取值范围必须
0.0 ~ 2.0 - max_tokens 必须大于 0,不能为负数
- 云端 API Key 至少存在一个不为空;若填写 Ollama 任意一项,则 Ollama 全部三项配置(模型名称、描述、endpoint)必须全部非空
- temperature 取值范围必须
- 服务器业务线程:ChatServer 内部将 HTTP 服务运行在独立 detach 分离线程;main 主线程负责保活、信号响应,不能阻塞在服务监听逻辑。
- 日志模块:项目 sdk 封装 spdlog 日志
bite::Logger,头文件安装到系统 include,直接引入<ai_chat_sdk/util/myLog.h>,禁止使用相对路径引用头文件。
1.1.2 文件约束
- main.cpp 存放路径:ChatServer 目录下
- 需求文档:
ChatServer/required/main需求.txt
1.2 ServerConfig 结构体定义
文件位置 ChatServer/ChatServer.h
cpp
#pragma once
#include <string>
#include <httplib.h>
namespace ai_chat_server
{
struct ServerConfig
{
// 网络服务配置
std::string host = "0.0.0.0";
int port = 8080;
std::string logLevel = "INFO";
// LLM模型公共参数
double temperature = 0.7;
int maxTokens = 2048;
// 云端模型密钥,从环境变量读取
std::string deepseekAPIKey;
std::string chatGPTAPIKey;
std::string geminiAPIKey;
// Ollama本地大模型配置,由gflags参数传入
std::string ollamaModelName;
std::string ollamaModelDesc;
std::string ollamaEndpoint;
};
class ChatServer
{
public:
explicit ChatServer(const ServerConfig& config);
bool start();
void stop();
bool isRunning() const;
private:
// http路由、业务处理成员省略
};
}
1.3 gflags 参数定义 main.cpp 头部
cpp
#include "ChatServer.h"
#include <ai_chat_sdk/util/myLog.h>
#include <gflags/gflags.h>
#include <fstream>
#include <thread>
#include <csignal>
#include <cstdlib>
// 网络服务参数
DEFINE_string(host, "0.0.0.0", "服务器绑定监听地址");
DEFINE_int32(port, 8080, "HTTP服务监听端口");
DEFINE_string(log_level, "INFO", "日志级别: TRACE|DEBUG|INFO|WARN|ERROR|CRITICAL");
DEFINE_string(config_file, "./ChatServer.conf", "gflags配置文件路径(flagfile格式)");
// LLM模型公共参数
DEFINE_double(temperature, 0.7, "模型温度,取值范围0.0~2.0");
DEFINE_int32(max_tokens, 2048, "模型最大输出token数量");
// Ollama相关参数,命令行/配置文件传入
DEFINE_string(ollama_model_name, "", "Ollama模型名称");
DEFINE_string(ollama_model_desc, "", "Ollama模型描述信息");
DEFINE_string(ollama_endpoint, "", "Ollama服务API地址,例如http://127.0.0.1:11434");
constexpr const char* VERSION = "1.0.0";
// 全局服务器智能指针,用于信号回调访问服务实例
namespace ai_chat_server {
std::unique_ptr<ChatServer> g_server;
std::atomic<bool> g_running{true};
}
1.4 工具函数实现
1.4.1 环境变量读取工具
cpp
// 读取系统环境变量,不存在返回空字符串
static std::string getEnvVar(const std::string& key)
{
const char* val = std::getenv(key.c_str());
return val ? std::string(val) : "";
}
1.4.2 信号处理回调函数
捕获
SIGINT(Ctrl+C)、SIGTERM信号,优雅停止服务,安全退出进程
cpp
static void signalHandler(int sig)
{
using namespace ai_chat_server;
(void)sig;
g_running.store(false);
if(g_server)
{
g_server->stop();
}
}
1.4.3 参数校验函数 validateConfig
cpp
static bool validateConfig(const ai_chat_server::ServerConfig& config)
{
// 校验temperature范围
if(config.temperature < 0.0 || config.temperature > 2.0)
{
ERR("配置校验失败: temperature必须处于0.0~2.0,当前值={}", config.temperature);
return false;
}
// maxTokens必须大于0
if(config.maxTokens <= 0)
{
ERR("配置校验失败: max_tokens必须大于0,当前值={}", config.maxTokens);
return false;
}
// 云端密钥:至少有一个API Key不为空
bool hasCloudKey = !config.deepseekAPIKey.empty()
|| !config.chatGPTAPIKey.empty()
|| !config.geminiAPIKey.empty();
// Ollama:只要任意一个字段非空,则全部三个字段必须全部配置
bool hasAnyOllama = !config.ollamaModelName.empty()
|| !config.ollamaModelDesc.empty()
|| !config.ollamaEndpoint.empty();
bool ollamaComplete = !config.ollamaModelName.empty()
&& !config.ollamaModelDesc.empty()
&& !config.ollamaEndpoint.empty();
if(hasAnyOllama && !ollamaComplete)
{
ERR("配置校验失败:Ollama配置项不能部分填写,model_name、model_desc、endpoint必须同时提供");
return false;
}
// 必须至少拥有云端key,或者完整Ollama配置
if(!hasCloudKey && !ollamaComplete)
{
ERR("配置校验失败:至少配置一个云端API Key,或者一套完整Ollama模型配置");
return false;
}
return true;
}
1.4.4 帮助打印函数 showAPIInfo
-h / --help触发,打印参数说明、启动示例、全部 REST API 接口清单
cpp
static void showAPIInfo()
{
std::cout << "\n==== AIChatServer 使用帮助 ====\n";
std::cout << "版本: " << VERSION << "\n\n";
std::cout << "启动示例:\n";
std::cout << " ./AIChatServer 使用默认参数启动\n";
std::cout << " ./AIChatServer --port=9000 指定端口启动\n";
std::cout << " ./AIChatServer --config_file=./ChatServer.conf 加载配置文件\n";
std::cout << "\n环境变量设置示例(云端密钥):\n";
std::cout << " export DEEPSEEK_API_KEY=xxx\n";
std::cout << "\n可用HTTP API接口:\n";
std::cout << " POST /api/session 创建会话\n";
std::cout << " GET /api/sessions 获取全部会话列表\n";
std::cout << " GET /api/models 获取可用模型列表\n";
std::cout << " DELETE/api/session/{id} 删除指定会话\n";
std::cout << " GET /api/session/{id}/history 获取会话历史\n";
std::cout << " POST /api/message 发送消息,全量返回\n";
std::cout << " POST /api/message/async 发送消息,流式返回\n";
std::cout << "\n";
}
1.5 main 主函数完整实现
执行流程:
- 设置 gflags 提示文本、版本字符串
- 原生 gflags 解析命令行
- 判断配置文件是否存在,使用
gflags::SetCommandLineOption("flagfile",xxx)加载配置文件- 打印帮助文档判断
- 注册信号捕获
- 组装 ServerConfig 结构体:云端 key 读环境变量,Ollama 参数读取 gflags
- 参数合法性校验
- 初始化 spdlog 日志器,设置日志级别
- 实例化 ChatServer,调用 start (),内部 HTTP 服务运行在 detach 分离子线程
- 主线程循环保活,等待停止信号
- 服务退出,程序返回
cpp
int main(int argc, char** argv)
{
using namespace ai_chat_server;
try
{
// gflags初始化
gflags::SetUsageMessage("AI聊天服务,AI ChatServer");
gflags::SetVersionString(VERSION);
// 解析命令行参数,remove_flags=true将解析过的flag从argv移除
gflags::ParseCommandLineFlags(&argc, &argv, true);
// 使用gflags原生flagfile加载配置文件,禁止手写配置解析逻辑
std::ifstream confFile(FLAGS_config_file);
if(confFile.is_open())
{
gflags::SetCommandLineOption("flagfile", FLAGS_config_file.c_str());
confFile.close();
INFO("成功加载配置文件:{}", FLAGS_config_file);
// 注意:flagfile会重新触发参数解析,优先级:命令行 > flagfile > 默认值
}
else
{
WARN("配置文件{}不存在,将仅使用命令行与默认参数", FLAGS_config_file);
}
// -h / --help 打印帮助信息
if(argc >= 1)
{
std::string opt(argv[1]);
if(opt == "-h" || opt == "--help")
{
showAPIInfo();
return 0;
}
}
// 注册信号处理器,捕获Ctrl+C、进程终止信号
signal(SIGINT, signalHandler);
signal(SIGTERM, signalHandler);
// =========组装ServerConfig配置=========
ServerConfig config;
config.host = FLAGS_host;
config.port = FLAGS_port;
config.logLevel = FLAGS_log_level;
config.temperature = FLAGS_temperature;
config.maxTokens = FLAGS_max_tokens;
// 云端大模型密钥从系统环境变量读取,不在命令行暴露密钥
config.deepseekAPIKey = getEnvVar("DEEPSEEK_API_KEY");
config.chatGPTAPIKey = getEnvVar("CHATGPT_API_KEY");
config.geminiAPIKey = getEnvVar("GEMINI_API_KEY");
// Ollama参数全部取自gflags命令行/配置文件
config.ollamaModelName = FLAGS_ollama_model_name;
config.ollamaModelDesc = FLAGS_ollama_model_desc;
config.ollamaEndpoint = FLAGS_ollama_endpoint;
// 参数安全校验
if(!validateConfig(config))
{
ERR("服务配置校验失败,程序退出");
return EXIT_FAILURE;
}
// =========初始化spdlog封装日志器bite::Logger=========
spdlog::level::level_enum logLevel = spdlog::level::info;
if(config.logLevel == "TRACE") logLevel = spdlog::level::trace;
else if(config.logLevel == "DEBUG") logLevel = spdlog::level::debug;
else if(config.logLevel == "INFO") logLevel = spdlog::level::info;
else if(config.logLevel == "WARN") logLevel = spdlog::level::warn;
else if(config.logLevel == "ERROR") logLevel = spdlog::level::err;
else if(config.logLevel == "CRITICAL") logLevel = spdlog::level::critical;
// 初始化日志实例;输出至控制台+日志文件chat_server.log
bite::Logger::initLogger("ChatServer", "chat_server.log", logLevel);
// 打印启动配置信息
INFO("==== AIChatServer启动配置 ====");
INFO("版本号:{}", VERSION);
INFO("监听地址:{}:{}", config.host, config.port);
INFO("日志级别:{}", config.logLevel);
INFO("temperature:{}", config.temperature);
INFO("max_tokens:{}", config.maxTokens);
INFO("DeepSeek Key:{}", config.deepseekAPIKey.empty() ? "未设置" : "已设置");
INFO("ChatGPT Key:{}", config.chatGPTAPIKey.empty() ? "未设置" : "已设置");
INFO("Gemini Key:{}", config.geminiAPIKey.empty() ? "未设置" : "已设置");
INFO("Ollama模型:{}", config.ollamaModelName.empty() ? "未启用" : config.ollamaModelName);
// 创建服务实例,ChatServer::start内部启动独立detach线程运行http服务
g_server = std::make_unique<ChatServer>(config);
bool startOk = g_server->start();
if(!startOk)
{
ERR("ChatServer服务启动失败");
return EXIT_FAILURE;
}
INFO("ChatServer启动成功,地址 http://{}:{}", config.host, config.port);
INFO("按下 Ctrl+C 停止服务");
// =========主线程保活循环=========
// http服务运行在子线程;主线程不能立刻return,否则进程直接退出
// g_running原子布尔,收到SIGINT/SIGTERM会被置false,退出循环
while(g_running.load() && g_server->isRunning())
{
std::this_thread::sleep_for(std::chrono::seconds(1));
}
INFO("收到停止信号,服务正常退出");
g_server.reset();
return EXIT_SUCCESS;
}
catch(const std::exception& e)
{
ERR("程序发生异常: {}", e.what());
return EXIT_FAILURE;
}
catch(...)
{
ERR("程序发生未知异常");
return EXIT_FAILURE;
}
}
1.6 ChatServer::start 内部线程实现 (ChatServer.cpp)
HTTP 服务运行在 detach 分离线程,不阻塞 main 主线程
cpp
bool ChatServer::start()
{
// ...省略路由初始化代码
std::thread serverThread([this](){
_chatServer->listen(_config.host, _config.port);
});
// detach分离线程,线程独立运行,不需要主线程join
serverThread.detach();
_isRunning.store(true);
return true;
}
2 ~> ChatServer.conf 配置文件模板
放置于 ChatServer 目录;gflags flagfile 格式,每行写
--flag_name=value,#代表注释;程序代码不生成该文件,由使用者手动编写维护
conf
# ChatServer.conf gflags flagfile配置文件
# 网络服务配置
--host=0.0.0.0
--port=8080
--log_level=INFO
# LLM公共参数
--temperature=0.7
--max_tokens=2048
# Ollama本地模型配置,使用ollama才需要打开注释
#--ollama_endpoint=http://127.0.0.1:11434
#--ollama_model_name=deepseek‑r1:1.5b
#--ollama_model_desc="本地部署开源推理模型"
3 ~> 环境变量配置(云端 API 密钥)
API 密钥属于敏感信息,不在配置文件 / 命令行明文存储,设置 Linux shell 环境变量
bash
# ~/.bashrc 添加
export DEEPSEEK_API_KEY="sk‑xxxx"
export CHATGPT_API_KEY="sk‑xxxx"
export GEMINI_API_KEY="xxxx"
# 生效配置
source ~/.bashrc
# 验证环境变量是否生效
echo $DEEPSEEK_API_KEY
4 ~> 关键易错点总结(面试高频)
4.1 gflags flagfile 加载顺序
- gflags 内置默认值
- flagfile 配置文件参数
- 命令行传入参数(优先级最高,覆盖配置文件)
- 错误做法:手写文件解析,字符串切割模拟 flagfile;正确:调用
gflags::SetCommandLineOption("flagfile", path)交给库原生解析。
4.2 线程模型关键点
- ChatServer HTTP 监听运行在
detach()分离子线程; - main 主线程必须保留循环保活,如果 main 直接 return,整个进程会销毁,子线程同步终止,服务立刻停止;
- 使用
std::atomic<bool>原子变量做运行标记,在信号处理器修改原子标记,保证多线程内存可见性。
4.3 信号处理注意事项
- 信号回调函数上下文限制:不能执行复杂 IO、不能调用 spdlog 打印日志;只修改原子标记、调用 stop、重置智能指针。
- 捕获
SIGINT(Ctrl+C),SIGTERM(kill 命令终止进程),实现优雅关闭。
4.4 密钥安全规范
- 云端 API 密钥禁止 gflags 命令行参数传入;命令行参数可以被 ps 命令全局查看,造成密钥泄露;
- 密钥从操作系统环境变量读取。
4.5 头文件路径坑
日志库头文件已经安装进系统 include 目录,#include <ai_chat_sdk/util/myLog.h>;禁止使用#include "../sdk/include/util/myLog.h"相对路径,跨编译环境会编译失败。
4.6 参数校验边界
- temperature 闭区间
[0.0, 2.0]; - max_tokens 严格大于 0;
- Ollama 配置:部分字段填写属于非法状态,要么全部为空,要么三项全部非空;
- 整体至少有一套可用模型:云端 key OR 完整 Ollama 配置。
5 ~> CMakeLists.txt 关键片段
cmake
project(AIChatServer)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# 依赖库 gflags spdlog
find_package(gflags REQUIRED)
find_package(spdlog REQUIRED)
add_executable(AIChatServer
ChatServer/main.cpp
ChatServer/ChatServer.cpp
)
target_link_libraries(AIChatServer
gflags::gflags
spdlog::spdlog
ai_chat_sdk
)
6 ~> 程序运行命令示例
bash
# 1. 默认参数启动,读取ChatServer.conf
./AIChatServer
# 2. 指定端口,命令行参数优先级高于配置文件
./AIChatServer --port=9000
#3. 指定自定义配置文件路径
./AIChatServer --config_file=./my.conf
#4. 查看帮助文档
./AIChatServer -h
#5. 查看版本
./AIChatServer -v
结尾
uu们,本文的内容到这里就全部结束了,艾莉丝在这里再次感谢您的阅读!
|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| ### 艾莉丝努力练剑 C/C++ & Linux 底层探索者 | 一个正在努力练剑的技术博主 *** ** * ** *** 👀 【关注】 跟随我一起深耕技术领域,见证每一次成长。 ❤️ 【点赞】 让优质内容被更多人看见,让知识传递更有力量。 ⭐ 【收藏】 把核心知识点存好,在需要时随时查、随时用。 💬 【评论】 分享你的经验或疑问,评论区一起交流避坑! 不要忘记给博主"一键四连"哦! "今日练剑达成!"
"技术之路难免有困惑,但同行的人会让前进更有方向。" |
结语:希望对学习Linux相关内容的uu有所帮助,不要忘记给博主"一键四连"哦!
往期回顾:
🗡博主在这里放了一只小狗,大家看完了摸摸小狗放松一下吧!🗡 ૮₍ ˶ ˊ ᴥ ˋ˶₎ა
