欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 新闻 > 会展 > 【C++接入大模型】WinHTTP类封装:实现对话式大模型接口访问

【C++接入大模型】WinHTTP类封装:实现对话式大模型接口访问

2025/4/2 15:15:15 来源:https://blog.csdn.net/u014161864/article/details/146567980  浏览:    关键词:【C++接入大模型】WinHTTP类封装:实现对话式大模型接口访问

一、类设计概述

近期准备用C++做一些大预言模型方面的开发,先期计划实现C++调用公共的大模型Web接口,因为之前没做过C++的Web开发,经验少,所以对比了一些主流的框架,包括实际测试验证。以下是Windows平台下主流C++ HTTP库的对比分析:

库名称内置支持第三方依赖HTTPS支持API复杂度跨平台社区活跃度典型应用场景
WinHTTP✅ 是(Windows SDK)无(自动处理SSL/TLS)✅ 原生支持(无需OpenSSL)中等❌ 仅Windows中等系统级应用、快速集成、Windows专属开发
libcurl❌ 需手动集成需OpenSSL/zlib等✅ 依赖第三方库配置✅ 是跨平台项目、复杂协议需求
libhv❌ 需手动集成无(内置SSL支持)✅ 需编译时启用中等✅ 是中等高性能网络服务、异步IO场景
Boost.Beast❌ 需Boost库需OpenSSL✅ 需手动配置✅ 是现代C++项目、需要高度定制化
cpp-netlib❌ 需手动集成需Boost/Asio等✅ 需依赖项支持✅ 是传统企业级应用、遗留系统维护
Poco❌ 需手动集成需OpenSSL✅ 需手动配置中等✅ 是中等综合性框架需求、企业级开发
Crow❌ 需手动集成无(仅HTTP)❌ 无原生支持✅ 是轻量级Web服务、快速原型开发

转了一圈,最终选择了Windows原生WinHTTP API实现。

选型原因分析

  1. 内置支持与零配置优势

    • WinHTTP直接集成于Windows系统,无需额外安装或配置OpenSSL等依赖,避免了证书链、动态库路径等复杂问题。对于Windows专属应用,可直接调用winhttp.lib,实现"开箱即用",也支持流式接口。
  2. 简化HTTPS开发

    • WinHTTP自动处理TLS/SSL握手和证书验证(通过SECURITY_FLAG_IGNORE_*标志控制),开发者无需手动管理加密套件或协议版本,显著降低HTTPS开发门槛。
  3. 资源占用与性能平衡

    • 相比libhv或Boost.Beast等跨平台库,WinHTTP在Windows环境下表现出更优的资源利用率,尤其适合对内存占用敏感的系统工具或后台服务。
  4. API复杂度可控

    • 虽然WinHTTP API较为底层,但通过封装(如您提供的SimpleHttp类)可屏蔽复杂性,同时保留对请求头、超时等关键参数的控制能力。
  5. 企业级安全合规

    • WinHTTP通过微软签名验证,符合Windows安全合规要求,适合金融、政务等对供应链安全敏感的场景。

SimpleHttp核心特性

SimpleHttp 是基于 Windows WinHTTP API 封装的 C++ HTTP 客户端类,支持同步/异步 HTTP/HTTPS 请求,提供流式数据处理能力。特性包括:

  • 自适应HTTP/HTTPS协议
  • 支持流式传输(Streaming)
  • 线程安全的数据队列
  • 异常安全的资源管理
  • 符合C++20标准的现代语法

类接口说明

公共接口说明
1. 构造函数
SimpleHttp(const std::string& home, bool https = false);
  • 功能:初始化 WinHTTP 会话,设置基地址和协议类型
  • 参数
    • home:服务器基地址(如 "api.example.com"
    • https:是否使用 HTTPS(默认 false
  • 异常:会话初始化失败时抛出 std::runtime_error
2. 析构函数
~SimpleHttp();
  • 功能:释放所有资源,确保流式线程安全退出
  • 行为
    • 终止流式接收线程(若有)
    • 关闭所有 WinHTTP 句柄
3. GET 请求
int Get(const std::string& upath, std::string& resp, bool stream = false);
  • 功能:发起 GET 请求
  • 参数
    • upath:接口路径(自动拼接基地址)
    • resp:同步模式下存储完整响应
    • stream:是否启用流式处理(默认 false
  • 返回值
    • 0:成功(流式请求立即返回)
    • -1:失败(异常抛出前返回)
  • 异常:网络错误或 HTTP 状态码非 200 时抛出 std::runtime_error
4. POST 请求
int Post(const std::string& upath, const std::string& para, std::string& resp, bool stream = false);
  • 功能:发起 POST 请求
  • 参数
    • upath:接口路径
    • para:JSON 格式请求体
    • resp:同步模式下存储完整响应
    • stream:是否启用流式处理
  • 返回值:同 Get() 方法
  • 默认头:自动添加 Content-Type: application/json(可覆盖)
5. 流式响应获取
int TryFetchResp(std::string& resp);
  • 功能:轮询获取流式数据块
  • 参数resp 存储当前数据块
  • 返回值
    • >0:数据长度
    • 0:流结束
    • -1:暂无数据
  • 线程安全:通过互斥锁保护队列
6. 设置请求头
void SetHeaders(const std::unordered_map<std::string, std::string>& headers);
  • 功能:批量设置请求头
  • 参数:键值对映射(如 { {"Authorization", "Bearer token"} }
  • 合并策略:相同头字段自动合并(WINHTTP_ADDREQ_FLAG_COALESCE

异常处理

  • 抛出类型std::runtime_error
  • 典型场景
    • 网络连接失败(如 DNS 解析错误)
    • 请求创建失败
    • HTTP 状态码非 200
    • 流式线程异常终止

流式处理工作流程

  1. 调用 Get()/Post() 时设置 stream = true
  2. 轮询调用 TryFetchResp() 获取数据
  3. 当返回 0 时终止循环

示例代码

SimpleHttp client("https://api.example.com", true);
client.Get("/stream", response, true);std::string chunk;
do {int size = client.TryFetchResp(chunk);if (size > 0) Process(chunk);else std::this_thread::sleep_for(std::chrono::milliseconds(100));
} while (size != 0);

实现细节

  • 线程管理:流式请求启动独立线程持续接收数据
  • 资源释放:析构函数确保线程和句柄正确关闭
  • 编码转换:内部使用 UTF-8 处理字符串(wstring2s/string2w

限制与注意事项

  1. 仅支持 Windows 平台(依赖 WinHTTP)
  2. 流式请求需手动管理生命周期
  3. HTTPS 默认忽略证书错误(生产环境需修改安全标志)

版本兼容性

  • Windows SDK:最低支持 Windows 7/Server 2008 R2
  • 编译器:需支持 C++17

二、核心函数深度分析

封装的类比较简单,支持GET和POST请求,支持流式接口,下面对封装的每个部分逐一展开说明。

1. 构造函数

SimpleHttp(const std::string& home, bool https = false)

实现要点:

  • 会话初始化:通过WinHttpOpen创建持久化会话,设置用户代理为"SimpleHttp/1.0"
  • 超时控制:统一设置5秒超时(连接/发送/接收/空闲)
  • 异常处理:会话创建失败时抛出runtime_error,包含系统错误码
  • 协议识别:通过_isHttps标记自动区分协议类型

设计考量:

  • 使用默认代理配置(WINHTTP_ACCESS_TYPE_DEFAULT_PROXY)提高兼容性
  • 零初始化参数(WINHTTP_NO_PROXY_NAME)避免冗余配置

2. 析构函数

~SimpleHttp()

资源管理:

  • 线程安全终止:通过原子变量_streamActive控制流式线程退出
  • 句柄清理
    • 顺序关闭请求→连接→会话句柄
    • 使用RAII模式确保资源释放

异常防御:

  • joinable()检查避免二次join导致崩溃
  • 线程终止前设置_streamActive=false防止死锁

3. Get请求

int Get(const std::string& upath, std::string& resp, bool stream = false)

工作流程:

  1. 调用SendRequest发起GET请求
  2. 根据stream参数选择同步/异步模式
  3. 同步模式直接返回完整响应
  4. 异步模式启动子线程持续接收数据

参数设计:

  • upath:URL路径自动拼接基地址
  • stream:控制响应处理模式(内存缓冲/流式队列)

4. Post请求

int Post(const std::string& upath, const std::string& para, std::string& resp, bool stream = false)

增强特性:

  • 自动设置Content-Type为application/json(未指定时)
  • 支持任意格式POST数据(通过para参数)
  • 数据长度自动计算(postData.size()

安全机制:

  • 使用WINHTTP_FLAG_REFRESH强制获取最新资源
  • HTTPS请求自动忽略证书错误(测试环境适用)

5. 流式响应获取

int TryFetchResp(std::string& resp)

队列管理:

  • 互斥锁保护的_datas队列
  • 三态返回值设计:
    • >0:有效数据长度
    • 0:流结束标记
    • -1:暂无数据

性能优化:

  • 队列首部数据优先出队(O(1)时间复杂度)
  • 空队列时立即返回避免阻塞

6. 请求头设置

void SetHeaders(const std::unordered_map<std::string, std::string>& headers)

头处理机制:

  • 使用WINHTTP_ADDREQ_FLAG_COALESCE自动合并重复头
  • 支持批量添加/覆盖现有头
  • 保留默认Content-Type设置

实现细节:

  • 字符串转换使用UTF-8编码
  • 头格式验证(自动添加冒号分隔符)

三、关键私有方法

1. 核心请求处理

int SendRequest(const std::wstring& method, ...)

多阶段处理:

  1. 连接建立WinHttpConnect创建目标服务器连接
  2. 请求创建WinHttpOpenRequest配置请求方法/路径
  3. 安全设置:HTTPS请求忽略证书错误
  4. 头注入:遍历_headers容器添加自定义头
  5. 数据发送WinHttpSendRequest传输请求体
  6. 响应处理:同步模式直接读取,异步模式启动接收线程

异常处理:

  • 每个WinAPI调用后立即检查返回值
  • 使用结构化异常处理(SEH)捕获系统级错误

2. 流式接收器

void StreamReceiver(HINTERNET hConnect, HINTERNET hRequest)

并发设计:

  • 使用原子变量_streamActive控制循环
  • 独立线程持续调用WinHttpReadData
  • 数据块即时推入线程安全队列

资源管理:

  • 函数退出时自动关闭连接/请求句柄
  • 异常传播前确保资源释放

3. 字符串转换

std::string wstring2s(const std::wstring& wstr)
std::wstring string2w(const std::string& str)

编码转换:

  • 基于Windows API的UTF-8转换
  • 预分配目标缓冲区优化性能
  • 空字符串快速路径

四、辅助函数解析

1. 全局替换函数

std::string ReplaceAll(std::string str, const std::string& from, const std::string& to)

实现特点:

  • 原地修改(接收值拷贝)
  • 处理重叠替换问题(pos递增策略)
  • 空模式保护避免死循环

2. JSON内容提取

std::string ExtractContent(std::string& resp)

解析逻辑:

  • 基于特征字符串定位(“content”:")
  • 支持转义字符处理(\n, \r, ", \)
  • 修改输入字符串实现流式解析

健壮性设计:

  • 非规范JSON容错处理
  • 内存安全(范围检查)

五、代码示例说明

1. 同步请求示例

SimpleHttp client("api.example.com", true);
std::string response;
client.Get("/data", response);
  • 即时获取完整响应
  • 适用于小型数据交互

2. 流式处理示例

client.Post("/stream", data, response, true);
while(int size = client.TryFetchResp(chunk)) {if(size > 0) process(chunk);
}
  • 实时处理大文件/持续数据流
  • 减少内存占用(无需完整缓存)

六、问题解决

Q:析构时触发断点

  • 原因:流式线程未正确终止
  • 解决方案:
    ~SimpleHttp() {CancelStream(); // 新增统一清理if(_hSession) WinHttpCloseHandle(_hSession);
    }void CancelStream() {if(_streamActive.exchange(false)) {WinHttpCloseHandle(_hRequest); // 强制终止if(_worker.joinable()) _worker.join();}
    }
    

Q:HTTPS证书错误

  • 解决方案:生产环境应移除证书忽略配置
    if(_isHttps) {DWORD dwFlags = SECURITY_FLAG_SECURE; // 严格验证WinHttpSetOption(...);
    }
    

七、总结

本实现通过封装WinHTTP API,在保持高性能的同时提供了简洁的接口。通过线程安全的流式处理、健壮的错误恢复和现代化的C++特性,适用于需要精细控制HTTP通信的Windows应用场景。未来可通过连接池、异步回调等机制进一步提升性能,满足更复杂的企业级需求。

八、完整代码

#include <windows.h>
#include <winhttp.h>
#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <unordered_map>
#include <tchar.h>
#include <mutex>
#include <atomic>
#include <thread>#pragma comment(lib, "winhttp.lib")
#include <strsafe.h>/// <summary>
/// 简单的HTTP类,自动支持http和https请求
/// 也支持流式处理,此时结果先缓存到队列,需主动调用TryFetchResp去轮询结果
/// </summary>
class SimpleHttp
{
public:SimpleHttp(const std::string& home, bool https = false) :_strBaseUrl(home), _isHttps(https){// 初始化WinHTTP会话 [[1]][[2]]_hSession = WinHttpOpen(L"SimpleHttp/1.0",WINHTTP_ACCESS_TYPE_DEFAULT_PROXY,WINHTTP_NO_PROXY_NAME,WINHTTP_NO_PROXY_BYPASS,0);if (!_hSession) {throw std::runtime_error("Failed to initialize WinHTTP session: " + GetLastError());}// 设置默认超时(5秒)DWORD timeout = 5000;WinHttpSetTimeouts(_hSession, timeout, timeout, timeout, timeout);}~SimpleHttp(){if (_worker.joinable()){_streamActive = false;_worker.join();}if (_hSession) WinHttpCloseHandle(_hSession);}/// <summary>/// 发起GET请求/// </summary>/// <param name="upath">接口名</param>/// <param name="resp">非流式请求时接收应答数据</param>/// <param name="stream">是否为流式请求</param>/// <returns></returns>int Get(const std::string& upath, std::string& resp, bool stream = false){return SendRequest(L"GET", upath, "", resp, stream);}/// <summary>/// /// </summary>/// <param name="upath">接口名</param>/// <param name="para">JSON请求参数</param>/// <param name="resp">非流式请求时接收应答数据</param>/// <param name="stream">是否为流式请求</param>/// <returns></returns>int Post(const std::string& upath, const std::string& para, std::string& resp, bool stream = false){return SendRequest(L"POST", upath, para, resp, stream);}/// <summary>/// 获取流式结果/// </summary>/// <param name="resp"></param>/// <returns>返回本次数据包大小,等于0表示数据包接收完成,-1表示没数据</returns>int TryFetchResp(std::string& resp){std::lock_guard<std::mutex> guard(_mtxDatas);if (_datas.empty()){return (_streamActive ? -1 : 0);}resp = _datas[0];_datas.erase(_datas.begin());return static_cast<int>(resp.size());}/// <summary>/// 设置请求头/// </summary>/// <param name="headers"></param>void SetHeaders(const std::unordered_map<std::string, std::string>& headers){for (auto& e : headers){_headers[e.first] = e.second;}}protected:/// <summary>/// 写入接收数据/// </summary>/// <param name="dat"></param>void Push(const std::string& dat){std::lock_guard<std::mutex> guard(_mtxDatas);if(!dat.empty()) _datas.push_back(dat);}private:int SendRequest(const std::wstring& method, const std::string& upath, const std::string& postData, std::string& resp, bool stream) {DWORD dwSize = 0;DWORD dwDownloaded = 0;LPSTR pszOutBuffer;BOOL bResults = FALSE;HINTERNET hConnect = nullptr;HINTERNET hRequest = nullptr;try {// 创建连接hConnect = WinHttpConnect(_hSession,string2w(_strBaseUrl).c_str(),INTERNET_DEFAULT_PORT,0);if (!hConnect) throw std::runtime_error("Connection failed");// 创建请求hRequest = WinHttpOpenRequest(hConnect,method.c_str(), string2w(upath).c_str(),nullptr, WINHTTP_NO_REFERER,WINHTTP_DEFAULT_ACCEPT_TYPES,(_isHttps ? (WINHTTP_FLAG_SECURE | WINHTTP_FLAG_REFRESH) : 0));if (!hRequest) throw std::runtime_error("Request creation failed");// 设置安全选项(忽略证书错误)if (_isHttps){DWORD dwFlags = SECURITY_FLAG_IGNORE_UNKNOWN_CA |SECURITY_FLAG_IGNORE_CERT_DATE_INVALID;WinHttpSetOption(hRequest, WINHTTP_OPTION_SECURITY_FLAGS, &dwFlags, sizeof(dwFlags));}// 设置请求头SetHeaders(hRequest);// 发送请求bResults = WinHttpSendRequest(hRequest,WINHTTP_NO_ADDITIONAL_HEADERS,0,(LPVOID)postData.c_str(),(DWORD)postData.size(),(DWORD)postData.size(),0);if (!bResults) throw std::runtime_error("Send request failed");// 接收响应if (!WinHttpReceiveResponse(hRequest, nullptr)){throw std::runtime_error("Receive response failed");}// 获取状态码 [[10]]DWORD dwStatusCode = 0;DWORD dwSize = sizeof(dwStatusCode);WinHttpQueryHeaders(hRequest,WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER,WINHTTP_HEADER_NAME_BY_INDEX, &dwStatusCode,&dwSize,WINHTTP_NO_HEADER_INDEX);if (dwStatusCode != 200) {throw std::runtime_error("HTTP error: " + std::to_string(dwStatusCode));}// 流式子线程处理if (stream){_streamActive = true;_worker = std::thread(&SimpleHttp::StreamReceiver, this, hConnect, hRequest);return 0; // 立即返回}// 处理响应数据std::string buffer;do {dwSize = 0;if (!WinHttpQueryDataAvailable(hRequest, &dwSize) || dwSize <= 0) break;size_t nRecv = dwSize;pszOutBuffer = new char[nRecv + 1];ZeroMemory(pszOutBuffer, nRecv + 1);if (WinHttpReadData(hRequest, (LPVOID)pszOutBuffer, dwSize, &dwDownloaded)){buffer.append(pszOutBuffer, dwDownloaded);}delete[] pszOutBuffer;} while (dwSize > 0);resp = buffer;return 0;}catch (const std::exception& e) {if (hRequest) WinHttpCloseHandle(hRequest);if (hConnect) WinHttpCloseHandle(hConnect);throw std::runtime_error("WinHTTP Error: " + std::string(e.what()) +" [ErrorCode: " + std::to_string(GetLastError()) + "]");return -1;}}/// <summary>/// 流式接收应答/// </summary>void StreamReceiver(HINTERNET hConnect, HINTERNET hRequest){DWORD dwSize = 0;DWORD dwDownloaded = 0;char* pszOutBuffer = nullptr;std::string strErr;try {while (_streamActive) {dwSize = 0;if (!WinHttpQueryDataAvailable(hRequest, &dwSize) || dwSize == 0) break;const size_t nRecv = dwSize;pszOutBuffer = new char[nRecv + 1];ZeroMemory(pszOutBuffer, nRecv + 1);if (WinHttpReadData(hRequest, pszOutBuffer, dwSize, &dwDownloaded)){Push(std::string(pszOutBuffer, dwDownloaded));}delete[] pszOutBuffer;}}catch (...) {strErr = "stream recv data error";}// 发送结束标记_streamActive = false;WinHttpCloseHandle(hRequest);WinHttpCloseHandle(hConnect);if (!strErr.empty()){throw std::runtime_error(strErr.c_str());}}/// <summary>/// 设置请求头/// </summary>void SetHeaders(HINTERNET hRequest){// 创建请求后立即设置请求头for (const auto& h : _headers) {std::wstring header = string2w(h.first + ": " + h.second);if (!WinHttpAddRequestHeaders(hRequest,header.c_str(),static_cast<DWORD>(header.length()),WINHTTP_ADDREQ_FLAG_COALESCE)){//throw std::runtime_error("Failed to set header: " + h.first + " [Error: " + std::to_string(GetLastError()) + "]");}}}// std::wstring → std::string (UTF-8)std::string wstring2s(const std::wstring& wstr) {if (wstr.empty()) return {};int len = WideCharToMultiByte(CP_UTF8, 0, wstr.data(), (int)wstr.size(), nullptr, 0, nullptr, nullptr);std::string result(len, '\0');WideCharToMultiByte(CP_UTF8, 0, wstr.data(), (int)wstr.size(), (LPSTR)result.data(), len, nullptr, nullptr);return result;}// std::string (UTF-8) → std::wstringstd::wstring string2w(const std::string& str) {if (str.empty()) return {};int len = MultiByteToWideChar(CP_UTF8, 0, str.data(), (int)str.size(), nullptr, 0);std::wstring result(len, L'\0');MultiByteToWideChar(CP_UTF8, 0, str.data(), (int)str.size(), (LPWSTR)result.data(), len);return result;}private:// 是否httpsbool _isHttps{ false };// 基地址std::string _strBaseUrl;// 默认headersstd::unordered_map<std::string, std::string> _headers;// 异步数据std::vector<std::string> _datas;std::mutex _mtxDatas;// WinHttp对象HINTERNET _hSession = nullptr;// 流式操作子线程std::atomic<bool> _streamActive{ false };std::thread _worker;
};std::string ReplaceAll(std::string str, const std::string& from, const std::string& to) {if (from.empty()) return str; // 避免死循环[[2]]size_t pos = 0;while ((pos = str.find(from, pos)) != std::string::npos) {str.replace(pos, from.length(), to);pos += to.length();}return str;
}std::string ExtractContent(std::string& resp) {std::vector<std::string> results;std::string key = "\"content\":";size_t pos = 0;while ((pos = resp.find(key, pos)) != std::string::npos) {pos += key.length();// 跳过空白和冒号while (pos < resp.size() && (resp[pos] == ' ' || resp[pos] == ':')) pos++;if (resp[pos] != '"') continue; // 非字符串值跳过size_t start = ++pos;std::string content;// 处理转义字符和双引号while (pos < resp.size()) {char c = resp[pos++];if (c == '"') break;else if (c == '\\' && pos < resp.size()){switch (resp[pos++]){case 'n':c = '\n';break;case '\r':c = '\r';break;case '\\':c = '\\';break;case '"':c = '\"';break;default:pos--;break;}}content += c;}resp = resp.substr(pos);return content;}resp = "";return resp;
}int main()
{int iii = 0;std::cin >> iii;// 请求参数char para[4096] = "{\"stream\" : true,\"model\":\"deepseek-r1-distill-qwen-32b\",\"messages\":[{\"role\":\"user\",\"content\":\"Please help me write a C++ class for parsing XML, with user-friendly interfaces that support XPath and iterative data access.\"}]}";// char para[4096] = "{\"stream\" : true,\"model\":\"deepseek-r1-distill-qwen-32b\",\"messages\":[{\"role\":\"user\",\"content\":\"The prime numbers within 10?\"}]}";auto url = "https://cloud.infini-ai.com/maas/v1/chat/completions";auto api_key = "Bearer sk-************";auto model_name = "deepseek-r1-distill-qwen-32b";SimpleHttp web("cloud.infini-ai.com", true);web.SetHeaders({ { "Content-Type", "application/json" }, { "Authorization", "Bearer sk-daxdj5ksqdc6iuvn" } });std::string resp;web.Post("/maas/v1/chat/completions", para, resp, true);int nRecv = 0;do{nRecv = web.TryFetchResp(resp);if (nRecv > 0){while (!resp.empty()){std::cout << ExtractContent(resp);}}else{std::this_thread::sleep_for(std::chrono::milliseconds(100));}} while (nRecv);return 0;
}

运行结果:
在这里插入图片描述

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词