根据距离标框完成

This commit is contained in:
2025-11-19 11:06:53 +08:00
parent 29c66a752c
commit 8530821351
16 changed files with 12106 additions and 12 deletions

View File

@@ -6,3 +6,8 @@ ServerPwd:"17227ca72f30f8bca8158bb78f25b43e"
#以下配置存储GPIO输出高低电平--状态机
outPutMode:true
#以下配置存储报警距离
NEAR_THRESHOLD=3
MID_THRESHOLD=5
MAX_DISTANCE=10

View File

@@ -0,0 +1,128 @@
# NetraLib
c/c++基本开发库
# TCP 服务端操作
包括多线程客户端连接,指定客户端数据的收发等等功能
# Linux 中屏蔽所有信号操作
屏蔽所有信号,以防止意外退出
# 写文件操作
允许原文本进行覆盖写,追加写
允许二进制进行覆盖写,追加写
允许在特定位置后面进行插入覆盖操作
允许删除特定字段后面所有内容在进行写操作
可以根据需要计算特定符号最后一个字节或者第一个字节所在位置所在位置
所有操作都添加mutex锁机制 ,保障线程安全
# 读文件操作
支持全文读取(文本和二进制模式)
支持按行读取文本内容
支持按指定字节数读取数据
支持计算第一个指定字节序列结束位置(包含该序列本身)的字节数
提供文件是否存在和文件大小查询
支持重置文件读取位置,实现多次读取
所有操作都添加mutex锁机制 ,保障线程安全
# 字符串操作
支持左右空格删除
支持格式化输出
# Http请求
提供基于 `cpp-httplib` 的简易 HTTP 客户端封装 `NetRequest`,支持:
1. 同步/异步 GET、POSTJSON、表单
2. 连接/读写超时设置、Keep-Alive
3. 并发请求上限控制
4. 可选内存缓存GET 命中时不发起网络请求)
5. 简单日志回调与性能统计
6. 断点续传下载到本地文件
使用步骤:
1) 引入头文件,配置目标地址
```cpp
#include "NetRequest.hpp"
ntq::RequestOptions opt;
opt.scheme = "http"; // 或 https
opt.host = "127.0.0.1"; // 服务器地址
opt.port = 8080; // 端口https 一般 443
opt.base_path = "/api"; // 可选统一前缀
opt.connect_timeout_ms = 3000;
opt.read_timeout_ms = 8000;
opt.write_timeout_ms = 8000;
opt.default_headers = { {"Authorization", "Bearer TOKEN"} }; // 可选
ntq::NetRequest req(opt);
req.setMaxConcurrentRequests(4);
req.enableCache(std::chrono::seconds(10));
```
2) 发送 GET 请求
```cpp
auto r = req.Get("/info");
if (r && r->status == 200) {
// r->body 为返回内容
}
// 带查询参数与额外请求头
httplib::Params q = {{"q","hello"},{"page","1"}};
httplib::Headers h = {{"X-Req-Id","123"}};
auto r2 = req.Get("/search", q, h);
```
3) 发送 POST 请求
```cpp
// JSONContent-Type: application/json
std::string json = R"({"name":"orangepi","mode":"demo"})";
auto p1 = req.PostJson("/set", json);
// 表单application/x-www-form-urlencoded
httplib::Params form = {{"user","abc"},{"pwd","123"}};
auto p2 = req.PostForm("/login", form);
```
4) 异步调用
```cpp
auto fut = req.GetAsync("/info");
auto res = fut.get(); // 与同步用法一致
```
5) 下载到本地(支持断点续传)
```cpp
bool ok = req.DownloadToFile("/files/pkg.bin", "/tmp/pkg.bin", {}, /*resume*/true);
```
6) 统计信息
```cpp
auto s = req.getStats();
// s.total_requests / s.total_errors / s.last_latency_ms / s.avg_latency_ms
```
说明:
- 若使用 HTTPS需在编译时添加 `-DCPPHTTPLIB_OPENSSL_SUPPORT` 并链接 `-lssl -lcrypto`,且将 `opt.scheme` 设为 `"https"`、端口通常为 `443`
- `base_path` 与各函数传入的 `path` 会自动合并,例如 `base_path="/api"``Get("/info")` 实际请求路径为 `/api/info`
7) 快速用法(无需实例化)
```cpp
using ntq::NetRequest;
// 直接传完整 URL 发起 GET
auto g = NetRequest::QuickGet("http://127.0.0.1:8080/api/info");
// 直接传完整 URL 发起 POST(JSON)
auto p1 = NetRequest::QuickPostJson(
"http://127.0.0.1:8080/api/set",
R"({"name":"orangepi","mode":"demo"})"
);
// 直接传完整 URL 发起 POST(表单)
httplib::Params form = {{"user","abc"},{"pwd","123"}};
auto p2 = NetRequest::QuickPostForm("http://127.0.0.1:8080/login", form);
```

View File

@@ -0,0 +1,344 @@
/*
本文件
网络请求类需要实现以下功能:
1. 发送网络请求
2. 接收网络响应
3. 处理网络请求和响应
4. 实现网络请求和响应的回调函数
5. 实现网络请求和响应的错误处理
6. 实现网络请求和响应的日志记录
7. 实现网络请求和响应的性能统计
8. 实现网络请求和响应的并发控制
9. 实现网络请求和响应的缓存管理
10. 实现网络请求和响应的断点续传
11. 实现网络请求和响应的断点续传
12. 实现网络请求和响应的断点续传
13. 实现网络请求和响应的断点续传
14. 实现网络请求和响应的断点续传
*/
#pragma once
#include "httplib.h"
#include <string>
#include <functional>
#include <future>
#include <chrono>
// C++17/14 可选类型回退适配:统一使用 ntq::optional / ntq::nullopt
#if defined(__has_include)
#if __has_include(<optional>)
#include <optional>
// 仅当启用了 C++17 或库声明了 optional 功能时,才使用 std::optional
#if defined(__cpp_lib_optional) || (__cplusplus >= 201703L) || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L)
namespace ntq { template <typename T> using optional = std::optional<T>; }
namespace ntq { constexpr auto nullopt = std::nullopt; using nullopt_t = decltype(std::nullopt); }
#elif __has_include(<experimental/optional>)
#include <experimental/optional>
namespace ntq { template <typename T> using optional = std::experimental::optional<T>; }
namespace ntq { constexpr auto nullopt = std::experimental::nullopt; using nullopt_t = decltype(std::experimental::nullopt); }
#else
#include <utility>
namespace ntq {
struct nullopt_t { explicit constexpr nullopt_t(int) {} };
static constexpr nullopt_t nullopt{0};
template <typename T>
class optional {
public:
optional() : has_(false) {}
optional(nullopt_t) : has_(false) {}
optional(const T &v) : has_(true), value_(v) {}
optional(T &&v) : has_(true), value_(std::move(v)) {}
optional(const optional &o) : has_(o.has_), value_(o.has_ ? o.value_ : T{}) {}
optional(optional &&o) noexcept : has_(o.has_), value_(std::move(o.value_)) {}
optional &operator=(nullopt_t) { has_ = false; return *this; }
optional &operator=(const T &v) { value_ = v; has_ = true; return *this; }
optional &operator=(T &&v) { value_ = std::move(v); has_ = true; return *this; }
explicit operator bool() const { return has_; }
bool has_value() const { return has_; }
T &value() { return value_; }
const T &value() const { return value_; }
T &operator*() { return value_; }
const T &operator*() const { return value_; }
private:
bool has_ = false;
T value_{};
};
}
#endif
#elif __has_include(<experimental/optional>)
#include <experimental/optional>
namespace ntq { template <typename T> using optional = std::experimental::optional<T>; }
namespace ntq { constexpr auto nullopt = std::experimental::nullopt; using nullopt_t = decltype(std::experimental::nullopt); }
#else
#include <utility>
namespace ntq {
struct nullopt_t { explicit constexpr nullopt_t(int) {} };
static constexpr nullopt_t nullopt{0};
template <typename T>
class optional {
public:
optional() : has_(false) {}
optional(nullopt_t) : has_(false) {}
optional(const T &v) : has_(true), value_(v) {}
optional(T &&v) : has_(true), value_(std::move(v)) {}
optional(const optional &o) : has_(o.has_), value_(o.has_ ? o.value_ : T{}) {}
optional(optional &&o) noexcept : has_(o.has_), value_(std::move(o.value_)) {}
optional &operator=(nullopt_t) { has_ = false; return *this; }
optional &operator=(const T &v) { value_ = v; has_ = true; return *this; }
optional &operator=(T &&v) { value_ = std::move(v); has_ = true; return *this; }
explicit operator bool() const { return has_; }
bool has_value() const { return has_; }
T &value() { return value_; }
const T &value() const { return value_; }
T &operator*() { return value_; }
const T &operator*() const { return value_; }
private:
bool has_ = false;
T value_{};
};
}
#endif
#else
// 无 __has_include按语言级别判断
#if (__cplusplus >= 201703L) || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L)
#include <optional>
namespace ntq { template <typename T> using optional = std::optional<T>; }
namespace ntq { constexpr auto nullopt = std::nullopt; using nullopt_t = decltype(std::nullopt); }
#else
#include <experimental/optional>
namespace ntq { template <typename T> using optional = std::experimental::optional<T>; }
namespace ntq { constexpr auto nullopt = std::experimental::nullopt; using nullopt_t = decltype(std::experimental::nullopt); }
#endif
#endif
namespace ntq
{
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/**
* @brief HTTP 响应对象
*
* 用于承载一次请求返回的状态码、正文与响应头;
* 当 from_cache 为 true 时,表示该响应来自本地缓存(而非实时网络)。
*/
struct HttpResponse
{
int status = 0; ///< HTTP 状态码(例如 200, 404 等)
std::string body; ///< 响应正文
httplib::Headers headers; ///< 响应头(大小写不敏感)
bool from_cache = false; ///< 是否来自缓存
};
/**
* @brief 错误码定义
*/
enum class ErrorCode
{
None = 0, ///< 无错误
Network, ///< 网络错误(连接失败/发送失败/接收失败等)
Timeout, ///< 超时
Canceled, ///< 被取消
InvalidURL, ///< URL 非法
IOError, ///< 本地 IO 错误(如写文件失败)
SSL, ///< SSL/HTTPS 相关错误
Unknown ///< 未分类错误
};
/**
* @brief 请求级别的参数配置
*
* 包含基础连接信息(协议、主机、端口)与默认头部、超时设置等。
*/
struct RequestOptions
{
std::string scheme = "http"; ///< 协议http 或 https
std::string host; ///< 目标主机名或 IP必填
int port = 80; ///< 端口http 默认为 80https 通常为 443
std::string base_path; ///< 可选的统一前缀(例如 "/api/v1"
int connect_timeout_ms = 5000; ///< 连接超时(毫秒)
int read_timeout_ms = 10000; ///< 读取超时(毫秒)
int write_timeout_ms = 10000; ///< 写入超时(毫秒)
bool keep_alive = true; ///< 是否保持连接Keep-Alive
httplib::Headers default_headers; ///< 默认头部,随所有请求发送
};
/**
* @class NetRequest
* @brief HTTP 客户端封装(基于 cpp-httplib
*
* 提供同步和异步的 GET/POST 请求、并发限制、可选缓存、日志回调、
* 性能统计以及断点续传下载到文件等能力。
*/
class NetRequest
{
public:
using LogCallback = std::function<void(const std::string &)>; ///< 日志回调类型
/**
* @brief 运行时统计数据
*/
struct Stats
{
uint64_t total_requests = 0; ///< 累计请求次数
uint64_t total_errors = 0; ///< 累计失败次数
double last_latency_ms = 0.0;///< 最近一次请求耗时(毫秒)
double avg_latency_ms = 0.0; ///< 指数平滑后的平均耗时(毫秒)
};
/**
* @brief 构造函数
* @param options 请求参数配置(目标地址、超时、默认头部等)
*/
explicit NetRequest(const RequestOptions &options);
/**
* @brief 析构函数
*/
~NetRequest();
/**
* @brief 设置日志回调
* @param logger 回调函数,接受一行日志字符串
*/
void setLogger(LogCallback logger);
/**
* @brief 设置最大并发请求数
* @param n 并发上限(最小值为 1
*/
void setMaxConcurrentRequests(size_t n);
/**
* @brief 启用内存缓存
* @param ttl 缓存有效期(超时后自动失效)
*/
void enableCache(std::chrono::milliseconds ttl);
/**
* @brief 禁用并清空缓存
*/
void disableCache();
/**
* @brief 发送同步 GET 请求
* @param path 资源路径(会与 base_path 合并)
* @param query 查询参数(会拼接为 ?k=v&...
* @param headers 额外请求头(与默认头部合并)
* @param err 可选错误码输出
* @return 成功返回响应对象,失败返回 std::nullopt
*/
ntq::optional<HttpResponse> Get(const std::string &path,
const httplib::Params &query = {},
const httplib::Headers &headers = {},
ErrorCode *err = nullptr);
/**
* @brief 发送同步 POST JSON 请求
* @param path 资源路径
* @param json JSON 字符串Content-Type: application/json
* @param headers 额外头部
* @param err 可选错误码输出
* @return 成功返回响应对象,失败返回 std::nullopt
*/
ntq::optional<HttpResponse> PostJson(const std::string &path,
const std::string &json,
const httplib::Headers &headers = {},
ErrorCode *err = nullptr);
/**
* @brief 发送同步 POST 表单请求application/x-www-form-urlencoded
* @param path 资源路径
* @param form 表单参数
* @param headers 额外头部
* @param err 可选错误码输出
* @return 成功返回响应对象,失败返回 std::nullopt
*/
ntq::optional<HttpResponse> PostForm(const std::string &path,
const httplib::Params &form,
const httplib::Headers &headers = {},
ErrorCode *err = nullptr);
/**
* @brief 异步 GET 请求
* @return std::future用于获取响应结果
*/
std::future<ntq::optional<HttpResponse>> GetAsync(const std::string &path,
const httplib::Params &query = {},
const httplib::Headers &headers = {},
ErrorCode *err = nullptr);
/**
* @brief 异步 POST JSON 请求
* @return std::future用于获取响应结果
*/
std::future<ntq::optional<HttpResponse>> PostJsonAsync(const std::string &path,
const std::string &json,
const httplib::Headers &headers = {},
ErrorCode *err = nullptr);
/**
* @brief 异步 POST 表单请求
* @return std::future用于获取响应结果
*/
std::future<ntq::optional<HttpResponse>> PostFormAsync(const std::string &path,
const httplib::Params &form,
const httplib::Headers &headers = {},
ErrorCode *err = nullptr);
/**
* @brief 下载文件到本地,支持断点续传
* @param path 资源路径
* @param local_file 本地保存路径
* @param headers 额外头部
* @param resume 是否启用续传Range
* @param chunk_size 预留参数:分块大小(当前由 httplib 内部回调驱动)
* @param err 可选错误码输出
* @return true 下载成功200 或 206false 失败
*/
bool DownloadToFile(const std::string &path,
const std::string &local_file,
const httplib::Headers &headers = {},
bool resume = true,
size_t chunk_size = 1 << 15,
ErrorCode *err = nullptr);
/**
* @brief 获取统计数据快照
*/
Stats getStats() const;
/**
* @brief 便捷:直接用完整 URL 发起 GET无需显式实例化
* @param url 形如 http://host:port/path?x=1 或 https://host/path
* @param headers 额外头部
* @param err 可选错误码输出
*/
static ntq::optional<HttpResponse> QuickGet(const std::string &url,
const httplib::Headers &headers = {},
ErrorCode *err = nullptr);
/**
* @brief 便捷:直接用完整 URL 发起 POST JSON无需显式实例化
* @param url 形如 http://host:port/path?x=1 或 https://host/path
* @param json JSON 字符串Content-Type: application/json
* @param headers 额外头部
* @param err 可选错误码输出
*/
static ntq::optional<HttpResponse> QuickPostJson(const std::string &url,
const std::string &json,
const httplib::Headers &headers = {},
ErrorCode *err = nullptr);
/**
* @brief 便捷:直接用完整 URL 发起 POST 表单(无需显式实例化)
* @param url 形如 http://host:port/path?x=1 或 https://host/path
* @param form 表单参数
* @param headers 额外头部
* @param err 可选错误码输出
*/
static ntq::optional<HttpResponse> QuickPostForm(const std::string &url,
const httplib::Params &form,
const httplib::Headers &headers = {},
ErrorCode *err = nullptr);
private:
struct Impl;
Impl *impl_;
};
}

View File

@@ -0,0 +1,426 @@
#pragma once
#include "QCL_Include.hpp"
namespace QCL
{
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/**
* @class TcpServer
* @brief 简单的多线程TCP服务器类支持多个客户端连接数据收发及断开处理
*
* 该类使用一个线程专门用于监听客户端连接,
* 每当有客户端连接成功时,为其创建一个独立线程处理该客户端的数据收发。
* 线程安全地管理所有客户端Socket句柄。
*/
class TcpServer
{
public:
/**
* @brief 构造函数,指定监听端口
* @param port 服务器监听端口号
*/
TcpServer(int port);
/**
* @brief 析构函数,自动调用 stop() 停止服务器并清理资源
*/
~TcpServer();
/**
* @brief 启动服务器创建监听socket开启监听线程
* @return 启动成功返回true失败返回false
*/
bool start();
/**
* @brief 停止服务器,关闭所有连接,释放资源,等待所有线程退出
*/
void stop();
/**
* @brief 发送消息给指定客户端
* @param clientSock 客户端Socket描述符
* @param message 发送的字符串消息
*/
void sendToClient(int clientSock, const std::string &message);
/**
* @brief 从指定客户端接收数据(单次调用)
* @param clientSock 客户端Socket描述符
* @param flag:false 非阻塞模式,true 阻塞模式
*/
std::string receiveFromClient(int clientSock, bool flag = true);
/**
* @brief 获取连接客户端的IP和端口
* @param clientSock 客户端Socket描述符
*/
char *getClientIPAndPort(int clientSock);
/**
* @brief 获取当前所有已连接客户端Socket的副本
* @return 包含所有客户端Socket的vector线程安全
*/
std::vector<int> getClientSockets();
/**
* @brief 从服务器的客户端列表中移除并关闭一个客户端socket
* @param clientSock 客户端Socket描述符
*/
void removeClient(int clientSock);
/**
* @brief 非阻塞探测客户端是否已断开(不消耗数据)
* @param clientSock 客户端Socket描述符
* @return true 已断开或发生致命错误false 仍然存活或暂无数据
*/
bool isClientDisconnected(int clientSock);
private:
/**
* @brief 监听并接受新的客户端连接(运行在独立线程中)
*/
void acceptClients();
private:
int serverSock_; ///< 服务器监听Socket描述符
int port_; ///< 服务器监听端口
std::atomic<bool> running_; ///< 服务器运行状态标志(线程安全)
std::vector<std::thread> clientThreads_; ///< 用于处理每个客户端的线程集合
std::thread acceptThread_; ///< 负责监听新连接的线程
std::mutex clientsMutex_; ///< 保护clientSockets_的互斥锁
std::vector<int> clientSockets_; ///< 当前所有连接的客户端Socket集合
};
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/**
* @brief 文件写入工具类(线程安全)
*
* 该类支持多种文件写入方式:
* - 覆盖写文本
* - 追加写文本
* - 按位置覆盖原文写入
* - 二进制覆盖写
* - 二进制追加写
*
* 特点:
* - 文件不存在时自动创建
* - 支持覆盖和追加两种模式
* - 支持二进制模式,适合写入非文本数据
* - 内部使用 std::mutex 实现线程安全
*/
class WriteFile
{
public:
/**
* @brief 构造函数
* @param filePath 文件路径
*/
explicit WriteFile(const std::string &filePath);
/**
* @brief 覆盖写文本文件(线程安全)
* @param content 要写入的文本内容
* @return true 写入成功
* @return false 写入失败
*/
bool overwriteText(const std::string &content);
/**
* @brief 追加写文本文件(线程安全)
* @param content 要写入的文本内容
* @return true 写入成功
* @return false 写入失败
*/
bool appendText(const std::string &content);
/**
* @brief 覆盖写二进制文件(线程安全)
* @param data 要写入的二进制数据
* @return true 写入成功
* @return false 写入失败
*/
bool overwriteBinary(const std::vector<char> &data);
/**
* @brief 追加写二进制文件(线程安全)
* @param data 要写入的二进制数据
* @return true 写入成功
* @return false 写入失败
*/
bool appendBinary(const std::vector<char> &data);
/**
* @brief 计算第一个指定字节序列前的字节数
* @param pattern 要查找的字节序列
* @param includePattern true 表示返回值包含 pattern 自身长度false 表示不包含
* @return size_t 字节数,如果文件没打开或 pattern 为空则返回 0
*/
size_t countBytesPattern(const std::string &pattern, bool includePattern = false);
/**
* @brief 在文件中查找指定字节序列并在其后写入内容,如果不存在则追加到文件末尾
* @param pattern 要查找的字节序列
* @param content 要写入的内容
* @return true 写入成功false 文件打开失败
*
* 功能说明:
* 1. 若文件中存在 pattern则删除 pattern 之后的所有内容,并在其后插入 content。
* 2. 若文件中不存在 pattern则在文件末尾追加 content若末尾无换行符则先补充换行。
*/
bool writeAfterPatternOrAppend(const std::string &pattern, const std::string &content);
/**
* @brief 在文件指定位置之后插入内容
* @param content 要插入的内容
* @param pos 插入位置(从文件开头算起的字节偏移量)
* @param length 插入的长度(>= content.size() 时,多余部分用空字节填充;< content.size() 时只截取前 length 个字节)
* @return true 插入成功false 文件打开失败或参数不合法
*
* 功能说明:
* 1. 不会覆盖原有数据,而是将 pos 之后的内容整体向后移动 length 个字节。
* 2. 如果 length > content.size(),则在 content 后补充 '\0'(或空格,可按需求改)。
* 3. 如果 length < content.size(),则只写入 content 的前 length 个字节。
* 4. 文件整体大小会增加 length 个字节。
*
* 举例:
* 原始文件内容: "ABCDEFG"
* insertAfterPos("XY", 2, 3) // 在索引 2 后插入
* 结果: "ABX Y\0CDEFG" (这里 \0 代表补充的空字节)
*/
bool insertAfterPos(const std::string &content, size_t pos, size_t length);
/**
* @brief 在文件指定位置覆盖写入内容
* @param content 要写入的内容
* @param pos 覆盖起始位置(从文件开头算起的字节偏移量)
* @param length 覆盖长度
* @return true 覆盖成功false 文件打开失败或 pos 越界
*
* 功能说明:
* 1. 从 pos 开始覆盖 length 个字节,不会移动或增加文件大小。
* 2. 如果 content.size() >= length则只写入前 length 个字节。
* 3. 如果 content.size() < length则写入 content并用 '\0' 补齐至 length。
* 4. 如果 pos + length 超过文件末尾,则只覆盖到文件尾部,不会越界。
*
* 举例:
* 原始文件内容: "ABCDEFG"
* overwriteAtPos("XY", 2, 3)
* 结果: "ABXYEFG" (原 "CDE" 被 "XY\0" 覆盖,\0 实际不可见)
*/
bool overwriteAtPos(const std::string &content, size_t pos, size_t length);
void close();
private:
std::string filePath_; ///< 文件路径
std::mutex writeMutex_; ///< 线程锁,保证多线程写入安全
/**
* @brief 通用文本写入接口(线程安全)
* @param content 要写入的内容
* @param mode 打开模式(追加/覆盖等)
* @return true 写入成功
* @return false 写入失败
*/
bool writeToFile(const std::string &content, std::ios::openmode mode);
/**
* @brief 通用二进制写入接口(线程安全)
* @param data 要写入的二进制数据
* @param mode 打开模式(追加/覆盖等)
* @return true 写入成功
* @return false 写入失败
*/
bool writeBinary(const std::vector<char> &data, std::ios::openmode mode);
};
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/**
* @brief ReadFile 类 - 读文件操作工具类
*
* 功能:
* 1. 读取全文(文本 / 二进制)
* 2. 按行读取
* 3. 按字节数读取
* 4. 获取指定字节序列前的字节数(包含该字节序列)
* 5. 检查文件是否存在
* 6. 获取文件大小
*
* 设计:
* - 与 WriteFile 类风格保持一致
* - 支持文本文件与二进制文件
* - 自动关闭文件(析构时)
* - 内部使用 std::mutex 实现线程安全
*/
class ReadFile
{
public:
/**
* @brief 构造函数
* @param filename 文件路径
*/
explicit ReadFile(const std::string &filename);
/**
* @brief 析构函数,自动关闭文件
*/
~ReadFile();
/**
* @brief 打开文件(以二进制方式)
* @return true 打开成功
* @return false 打开失败
*/
bool Open();
/**
* @brief 关闭文件
*/
void Close();
/**
* @brief 文件是否已经打开
*/
bool IsOpen() const;
/**
* @brief 读取全文(文本模式)
* @return 文件内容字符串
*/
std::string ReadAllText();
/**
* @brief 读取全文(二进制模式)
* @return 文件内容字节数组
*/
std::vector<char> ReadAllBinary();
/**
* @brief 按行读取文本
* @return 每行作为一个字符串的 vector
*/
std::vector<std::string> ReadLines();
/**
* @brief 读取指定字节数
* @param count 要读取的字节数
* @return 实际读取到的字节数据
*/
std::vector<char> ReadBytes(size_t count);
/**
* @brief 获取指定字节序列前的字节数(包含该字节序列)
* @param marker 要查找的字节序列(可能不止一个字节)
* @return 如果找到返回前面部分字节数找不到返回0
*/
size_t GetBytesBefore(const std::string &marker, bool includeMarker = false);
/**
* @brief 从指定位置读取指定字节数,默认读取到文件末尾
* @param pos 起始位置(字节偏移)
* @param count 要读取的字节数默认为0表示读取到文件末尾
* @return 读取到的字节数据
*/
std::vector<char> ReadBytesFrom(size_t pos, size_t count = 0);
/**
* @brief 检查文件是否存在
*/
bool FileExists() const;
/**
* @brief 获取文件大小(字节数)
*/
size_t GetFileSize() const;
/**
* @brief 重置读取位置到文件开头
*/
void Reset();
private:
std::string filename_; // 文件路径
std::ifstream file_; // 文件流对象
mutable std::mutex mtx_; // 可变,保证 const 方法也能加锁
};
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// 屏蔽所有信号
void blockAllSignals();
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// 字符串操作
// 去除字符串的左空格
std::string Ltrim(const std::string &s);
// 去除字符串右侧的空格
std::string Rtrim(const std::string &s);
// 去除字符串左右两侧的空格
std::string LRtrim(const std::string &s);
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// c++进行格式化输出
// 通用类型转字符串
template <typename T>
std::string to_string_any(const T &value)
{
std::ostringstream oss;
oss << value;
return oss.str();
}
// 递归获取 tuple 中 index 对应参数
template <std::size_t I = 0, typename Tuple>
std::string get_tuple_arg(const Tuple &tup, std::size_t index)
{
if constexpr (I < std::tuple_size_v<Tuple>)
{
if (I == index)
return to_string_any(std::get<I>(tup));
else
return get_tuple_arg<I + 1>(tup, index);
}
else
{
throw std::runtime_error("Too few arguments for format string");
}
}
// format 函数
template <typename... Args>
std::string format(const std::string &fmt, const Args &...args)
{
std::ostringstream oss;
std::tuple<const Args &...> tup(args...);
size_t pos = 0;
size_t arg_idx = 0;
while (pos < fmt.size())
{
if (fmt[pos] == '{' && pos + 1 < fmt.size() && fmt[pos + 1] == '{')
{
oss << '{';
pos += 2;
}
else if (fmt[pos] == '}' && pos + 1 < fmt.size() && fmt[pos + 1] == '}')
{
oss << '}';
pos += 2;
}
else if (fmt[pos] == '{' && pos + 1 < fmt.size() && fmt[pos + 1] == '}')
{
oss << get_tuple_arg(tup, arg_idx++);
pos += 2;
}
else
{
oss << fmt[pos++];
}
}
if (arg_idx < sizeof...(Args))
throw std::runtime_error("Too many arguments for format string");
return oss.str();
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
}

View File

@@ -0,0 +1,55 @@
/**
* @file qcl_include.hpp
* @brief 通用C++开发头文件集合Linux环境
* @note 使用前确保目标平台支持C++17标准
*/
#ifndef QCL_INCLUDE_HPP
#define QCL_INCLUDE_HPP
// ==================== C/C++基础运行时库 ====================
#include <iostream> // 标准输入输出流cin/cout/cerr
#include <string> // std::string类及相关操作
#include <cstring> // C风格字符串操作strcpy/strcmp等
#include <cstdlib> // 通用工具函数atoi/rand/malloc等
#include <cstdio> // C风格IOprintf/scanf
#include <cassert> // 断言宏(调试期检查)
#include <cmath> // 数学函数sin/pow等
#include <ctime> // 时间处理time/clock
#include <csignal> // 信号处理signal/kill
#include <memory> // 智能指针
// ==================== STL容器与算法 ====================
#include <vector> // 动态数组(连续内存容器)
#include <list> // 双向链表
#include <deque> // 双端队列
#include <map> // 有序键值对(红黑树实现)
#include <set> // 有序集合
#include <unordered_map> // 哈希表实现的键值对
#include <unordered_set> // 哈希表实现的集合
#include <stack> // 栈适配器LIFO
#include <queue> // 队列适配器FIFO
#include <algorithm> // 通用算法sort/find等
#include <numeric> // 数值算法accumulate等
#include <iterator> // 迭代器相关
// ==================== 字符串与流处理 ====================
#include <sstream> // 字符串流内存IO
#include <fstream> // 文件流文件IO
#include <iomanip> // 流格式控制setw/setprecision
#include <regex> // 正则表达式
#include <filesystem> // 文件系统(C++17)
#include<termios.h>
// ==================== 并发编程支持 ====================
#include <thread> // 线程管理std::thread
#include <mutex> // 互斥锁mutex/lock_guard
#include <atomic> // 原子操作(线程安全变量)
#include <condition_variable> // 条件变量(线程同步)
// ==================== Linux网络编程 ====================
#include <sys/socket.h> // 套接字基础APIsocket/bind
#include <netinet/in.h> // IPV4/IPV6地址结构体
#include <arpa/inet.h> // 地址转换函数inet_pton等
#include <unistd.h> // POSIX APIclose/read/write
#endif // QCL_INCLUDE_HPP

View File

@@ -0,0 +1,850 @@
cpp-httplib
===========
[![](https://github.com/yhirose/cpp-httplib/workflows/test/badge.svg)](https://github.com/yhirose/cpp-httplib/actions)
A C++11 single-file header-only cross platform HTTP/HTTPS library.
It's extremely easy to setup. Just include the **httplib.h** file in your code!
NOTE: This library uses 'blocking' socket I/O. If you are looking for a library with 'non-blocking' socket I/O, this is not the one that you want.
Simple examples
---------------
#### Server (Multi-threaded)
```c++
#define CPPHTTPLIB_OPENSSL_SUPPORT
#include "path/to/httplib.h"
// HTTP
httplib::Server svr;
// HTTPS
httplib::SSLServer svr;
svr.Get("/hi", [](const httplib::Request &, httplib::Response &res) {
res.set_content("Hello World!", "text/plain");
});
svr.listen("0.0.0.0", 8080);
```
#### Client
```c++
#define CPPHTTPLIB_OPENSSL_SUPPORT
#include "path/to/httplib.h"
// HTTP
httplib::Client cli("http://cpp-httplib-server.yhirose.repl.co");
// HTTPS
httplib::Client cli("https://cpp-httplib-server.yhirose.repl.co");
auto res = cli.Get("/hi");
res->status;
res->body;
```
SSL Support
-----------
SSL support is available with `CPPHTTPLIB_OPENSSL_SUPPORT`. `libssl` and `libcrypto` should be linked.
NOTE: cpp-httplib currently supports only version 1.1.1 and 3.0.
NOTE for macOS: cpp-httplib now can use system certs with `CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN`. `CoreFoundation` and `Security` should be linked with `-framework`.
```c++
#define CPPHTTPLIB_OPENSSL_SUPPORT
#include "path/to/httplib.h"
// Server
httplib::SSLServer svr("./cert.pem", "./key.pem");
// Client
httplib::Client cli("https://localhost:1234"); // scheme + host
httplib::SSLClient cli("localhost:1234"); // host
httplib::SSLClient cli("localhost", 1234); // host, port
// Use your CA bundle
cli.set_ca_cert_path("./ca-bundle.crt");
// Disable cert verification
cli.enable_server_certificate_verification(false);
```
NOTE: When using SSL, it seems impossible to avoid SIGPIPE in all cases, since on some operating systems, SIGPIPE can only be suppressed on a per-message basis, but there is no way to make the OpenSSL library do so for its internal communications. If your program needs to avoid being terminated on SIGPIPE, the only fully general way might be to set up a signal handler for SIGPIPE to handle or ignore it yourself.
Server
------
```c++
#include <httplib.h>
int main(void)
{
using namespace httplib;
Server svr;
svr.Get("/hi", [](const Request& req, Response& res) {
res.set_content("Hello World!", "text/plain");
});
svr.Get(R"(/numbers/(\d+))", [&](const Request& req, Response& res) {
auto numbers = req.matches[1];
res.set_content(numbers, "text/plain");
});
svr.Get("/body-header-param", [](const Request& req, Response& res) {
if (req.has_header("Content-Length")) {
auto val = req.get_header_value("Content-Length");
}
if (req.has_param("key")) {
auto val = req.get_param_value("key");
}
res.set_content(req.body, "text/plain");
});
svr.Get("/stop", [&](const Request& req, Response& res) {
svr.stop();
});
svr.listen("localhost", 1234);
}
```
`Post`, `Put`, `Delete` and `Options` methods are also supported.
### Bind a socket to multiple interfaces and any available port
```cpp
int port = svr.bind_to_any_port("0.0.0.0");
svr.listen_after_bind();
```
### Static File Server
```cpp
// Mount / to ./www directory
auto ret = svr.set_mount_point("/", "./www");
if (!ret) {
// The specified base directory doesn't exist...
}
// Mount /public to ./www directory
ret = svr.set_mount_point("/public", "./www");
// Mount /public to ./www1 and ./www2 directories
ret = svr.set_mount_point("/public", "./www1"); // 1st order to search
ret = svr.set_mount_point("/public", "./www2"); // 2nd order to search
// Remove mount /
ret = svr.remove_mount_point("/");
// Remove mount /public
ret = svr.remove_mount_point("/public");
```
```cpp
// User defined file extension and MIME type mappings
svr.set_file_extension_and_mimetype_mapping("cc", "text/x-c");
svr.set_file_extension_and_mimetype_mapping("cpp", "text/x-c");
svr.set_file_extension_and_mimetype_mapping("hh", "text/x-h");
```
The followings are built-in mappings:
| Extension | MIME Type | Extension | MIME Type |
| :--------- | :-------------------------- | :--------- | :-------------------------- |
| css | text/css | mpga | audio/mpeg |
| csv | text/csv | weba | audio/webm |
| txt | text/plain | wav | audio/wave |
| vtt | text/vtt | otf | font/otf |
| html, htm | text/html | ttf | font/ttf |
| apng | image/apng | woff | font/woff |
| avif | image/avif | woff2 | font/woff2 |
| bmp | image/bmp | 7z | application/x-7z-compressed |
| gif | image/gif | atom | application/atom+xml |
| png | image/png | pdf | application/pdf |
| svg | image/svg+xml | mjs, js | application/javascript |
| webp | image/webp | json | application/json |
| ico | image/x-icon | rss | application/rss+xml |
| tif | image/tiff | tar | application/x-tar |
| tiff | image/tiff | xhtml, xht | application/xhtml+xml |
| jpeg, jpg | image/jpeg | xslt | application/xslt+xml |
| mp4 | video/mp4 | xml | application/xml |
| mpeg | video/mpeg | gz | application/gzip |
| webm | video/webm | zip | application/zip |
| mp3 | audio/mp3 | wasm | application/wasm |
### File request handler
```cpp
// The handler is called right before the response is sent to a client
svr.set_file_request_handler([](const Request &req, Response &res) {
...
});
```
NOTE: These static file server methods are not thread-safe.
### Logging
```cpp
svr.set_logger([](const auto& req, const auto& res) {
your_logger(req, res);
});
```
### Error handler
```cpp
svr.set_error_handler([](const auto& req, auto& res) {
auto fmt = "<p>Error Status: <span style='color:red;'>%d</span></p>";
char buf[BUFSIZ];
snprintf(buf, sizeof(buf), fmt, res.status);
res.set_content(buf, "text/html");
});
```
### Exception handler
The exception handler gets called if a user routing handler throws an error.
```cpp
svr.set_exception_handler([](const auto& req, auto& res, std::exception_ptr ep) {
auto fmt = "<h1>Error 500</h1><p>%s</p>";
char buf[BUFSIZ];
try {
std::rethrow_exception(ep);
} catch (std::exception &e) {
snprintf(buf, sizeof(buf), fmt, e.what());
} catch (...) { // See the following NOTE
snprintf(buf, sizeof(buf), fmt, "Unknown Exception");
}
res.set_content(buf, "text/html");
res.status = 500;
});
```
NOTE: if you don't provide the `catch (...)` block for a rethrown exception pointer, an uncaught exception will end up causing the server crash. Be careful!
### Pre routing handler
```cpp
svr.set_pre_routing_handler([](const auto& req, auto& res) {
if (req.path == "/hello") {
res.set_content("world", "text/html");
return Server::HandlerResponse::Handled;
}
return Server::HandlerResponse::Unhandled;
});
```
### Post routing handler
```cpp
svr.set_post_routing_handler([](const auto& req, auto& res) {
res.set_header("ADDITIONAL_HEADER", "value");
});
```
### 'multipart/form-data' POST data
```cpp
svr.Post("/multipart", [&](const auto& req, auto& res) {
auto size = req.files.size();
auto ret = req.has_file("name1");
const auto& file = req.get_file_value("name1");
// file.filename;
// file.content_type;
// file.content;
});
```
### Receive content with a content receiver
```cpp
svr.Post("/content_receiver",
[&](const Request &req, Response &res, const ContentReader &content_reader) {
if (req.is_multipart_form_data()) {
// NOTE: `content_reader` is blocking until every form data field is read
MultipartFormDataItems files;
content_reader(
[&](const MultipartFormData &file) {
files.push_back(file);
return true;
},
[&](const char *data, size_t data_length) {
files.back().content.append(data, data_length);
return true;
});
} else {
std::string body;
content_reader([&](const char *data, size_t data_length) {
body.append(data, data_length);
return true;
});
}
});
```
### Send content with the content provider
```cpp
const size_t DATA_CHUNK_SIZE = 4;
svr.Get("/stream", [&](const Request &req, Response &res) {
auto data = new std::string("abcdefg");
res.set_content_provider(
data->size(), // Content length
"text/plain", // Content type
[data](size_t offset, size_t length, DataSink &sink) {
const auto &d = *data;
sink.write(&d[offset], std::min(length, DATA_CHUNK_SIZE));
return true; // return 'false' if you want to cancel the process.
},
[data](bool success) { delete data; });
});
```
Without content length:
```cpp
svr.Get("/stream", [&](const Request &req, Response &res) {
res.set_content_provider(
"text/plain", // Content type
[&](size_t offset, DataSink &sink) {
if (/* there is still data */) {
std::vector<char> data;
// prepare data...
sink.write(data.data(), data.size());
} else {
sink.done(); // No more data
}
return true; // return 'false' if you want to cancel the process.
});
});
```
### Chunked transfer encoding
```cpp
svr.Get("/chunked", [&](const Request& req, Response& res) {
res.set_chunked_content_provider(
"text/plain",
[](size_t offset, DataSink &sink) {
sink.write("123", 3);
sink.write("345", 3);
sink.write("789", 3);
sink.done(); // No more data
return true; // return 'false' if you want to cancel the process.
}
);
});
```
With trailer:
```cpp
svr.Get("/chunked", [&](const Request& req, Response& res) {
res.set_header("Trailer", "Dummy1, Dummy2");
res.set_chunked_content_provider(
"text/plain",
[](size_t offset, DataSink &sink) {
sink.write("123", 3);
sink.write("345", 3);
sink.write("789", 3);
sink.done_with_trailer({
{"Dummy1", "DummyVal1"},
{"Dummy2", "DummyVal2"}
});
return true;
}
);
});
```
### 'Expect: 100-continue' handler
By default, the server sends a `100 Continue` response for an `Expect: 100-continue` header.
```cpp
// Send a '417 Expectation Failed' response.
svr.set_expect_100_continue_handler([](const Request &req, Response &res) {
return 417;
});
```
```cpp
// Send a final status without reading the message body.
svr.set_expect_100_continue_handler([](const Request &req, Response &res) {
return res.status = 401;
});
```
### Keep-Alive connection
```cpp
svr.set_keep_alive_max_count(2); // Default is 5
svr.set_keep_alive_timeout(10); // Default is 5
```
### Timeout
```c++
svr.set_read_timeout(5, 0); // 5 seconds
svr.set_write_timeout(5, 0); // 5 seconds
svr.set_idle_interval(0, 100000); // 100 milliseconds
```
### Set maximum payload length for reading a request body
```c++
svr.set_payload_max_length(1024 * 1024 * 512); // 512MB
```
### Server-Sent Events
Please see [Server example](https://github.com/yhirose/cpp-httplib/blob/master/example/ssesvr.cc) and [Client example](https://github.com/yhirose/cpp-httplib/blob/master/example/ssecli.cc).
### Default thread pool support
`ThreadPool` is used as a **default** task queue, and the default thread count is 8, or `std::thread::hardware_concurrency()`. You can change it with `CPPHTTPLIB_THREAD_POOL_COUNT`.
If you want to set the thread count at runtime, there is no convenient way... But here is how.
```cpp
svr.new_task_queue = [] { return new ThreadPool(12); };
```
### Override the default thread pool with yours
You can supply your own thread pool implementation according to your need.
```cpp
class YourThreadPoolTaskQueue : public TaskQueue {
public:
YourThreadPoolTaskQueue(size_t n) {
pool_.start_with_thread_count(n);
}
virtual void enqueue(std::function<void()> fn) override {
pool_.enqueue(fn);
}
virtual void shutdown() override {
pool_.shutdown_gracefully();
}
private:
YourThreadPool pool_;
};
svr.new_task_queue = [] {
return new YourThreadPoolTaskQueue(12);
};
```
Client
------
```c++
#include <httplib.h>
#include <iostream>
int main(void)
{
httplib::Client cli("localhost", 1234);
if (auto res = cli.Get("/hi")) {
if (res->status == 200) {
std::cout << res->body << std::endl;
}
} else {
auto err = res.error();
std::cout << "HTTP error: " << httplib::to_string(err) << std::endl;
}
}
```
NOTE: Constructor with scheme-host-port string is now supported!
```c++
httplib::Client cli("localhost");
httplib::Client cli("localhost:8080");
httplib::Client cli("http://localhost");
httplib::Client cli("http://localhost:8080");
httplib::Client cli("https://localhost");
httplib::SSLClient cli("localhost");
```
### Error code
Here is the list of errors from `Result::error()`.
```c++
enum Error {
Success = 0,
Unknown,
Connection,
BindIPAddress,
Read,
Write,
ExceedRedirectCount,
Canceled,
SSLConnection,
SSLLoadingCerts,
SSLServerVerification,
UnsupportedMultipartBoundaryChars,
Compression,
ConnectionTimeout,
};
```
### GET with HTTP headers
```c++
httplib::Headers headers = {
{ "Accept-Encoding", "gzip, deflate" }
};
auto res = cli.Get("/hi", headers);
```
or
```c++
auto res = cli.Get("/hi", {{"Accept-Encoding", "gzip, deflate"}});
```
or
```c++
cli.set_default_headers({
{ "Accept-Encoding", "gzip, deflate" }
});
auto res = cli.Get("/hi");
```
### POST
```c++
res = cli.Post("/post", "text", "text/plain");
res = cli.Post("/person", "name=john1&note=coder", "application/x-www-form-urlencoded");
```
### POST with parameters
```c++
httplib::Params params;
params.emplace("name", "john");
params.emplace("note", "coder");
auto res = cli.Post("/post", params);
```
or
```c++
httplib::Params params{
{ "name", "john" },
{ "note", "coder" }
};
auto res = cli.Post("/post", params);
```
### POST with Multipart Form Data
```c++
httplib::MultipartFormDataItems items = {
{ "text1", "text default", "", "" },
{ "text2", "aωb", "", "" },
{ "file1", "h\ne\n\nl\nl\no\n", "hello.txt", "text/plain" },
{ "file2", "{\n \"world\", true\n}\n", "world.json", "application/json" },
{ "file3", "", "", "application/octet-stream" },
};
auto res = cli.Post("/multipart", items);
```
### PUT
```c++
res = cli.Put("/resource/foo", "text", "text/plain");
```
### DELETE
```c++
res = cli.Delete("/resource/foo");
```
### OPTIONS
```c++
res = cli.Options("*");
res = cli.Options("/resource/foo");
```
### Timeout
```c++
cli.set_connection_timeout(0, 300000); // 300 milliseconds
cli.set_read_timeout(5, 0); // 5 seconds
cli.set_write_timeout(5, 0); // 5 seconds
```
### Receive content with a content receiver
```c++
std::string body;
auto res = cli.Get("/large-data",
[&](const char *data, size_t data_length) {
body.append(data, data_length);
return true;
});
```
```cpp
std::string body;
auto res = cli.Get(
"/stream", Headers(),
[&](const Response &response) {
EXPECT_EQ(200, response.status);
return true; // return 'false' if you want to cancel the request.
},
[&](const char *data, size_t data_length) {
body.append(data, data_length);
return true; // return 'false' if you want to cancel the request.
});
```
### Send content with a content provider
```cpp
std::string body = ...;
auto res = cli.Post(
"/stream", body.size(),
[](size_t offset, size_t length, DataSink &sink) {
sink.write(body.data() + offset, length);
return true; // return 'false' if you want to cancel the request.
},
"text/plain");
```
### Chunked transfer encoding
```cpp
auto res = cli.Post(
"/stream",
[](size_t offset, DataSink &sink) {
sink.os << "chunked data 1";
sink.os << "chunked data 2";
sink.os << "chunked data 3";
sink.done();
return true; // return 'false' if you want to cancel the request.
},
"text/plain");
```
### With Progress Callback
```cpp
httplib::Client client(url, port);
// prints: 0 / 000 bytes => 50% complete
auto res = cli.Get("/", [](uint64_t len, uint64_t total) {
printf("%lld / %lld bytes => %d%% complete\n",
len, total,
(int)(len*100/total));
return true; // return 'false' if you want to cancel the request.
}
);
```
![progress](https://user-images.githubusercontent.com/236374/33138910-495c4ecc-cf86-11e7-8693-2fc6d09615c4.gif)
### Authentication
```cpp
// Basic Authentication
cli.set_basic_auth("user", "pass");
// Digest Authentication
cli.set_digest_auth("user", "pass");
// Bearer Token Authentication
cli.set_bearer_token_auth("token");
```
NOTE: OpenSSL is required for Digest Authentication.
### Proxy server support
```cpp
cli.set_proxy("host", port);
// Basic Authentication
cli.set_proxy_basic_auth("user", "pass");
// Digest Authentication
cli.set_proxy_digest_auth("user", "pass");
// Bearer Token Authentication
cli.set_proxy_bearer_token_auth("pass");
```
NOTE: OpenSSL is required for Digest Authentication.
### Range
```cpp
httplib::Client cli("httpbin.org");
auto res = cli.Get("/range/32", {
httplib::make_range_header({{1, 10}}) // 'Range: bytes=1-10'
});
// res->status should be 206.
// res->body should be "bcdefghijk".
```
```cpp
httplib::make_range_header({{1, 10}, {20, -1}}) // 'Range: bytes=1-10, 20-'
httplib::make_range_header({{100, 199}, {500, 599}}) // 'Range: bytes=100-199, 500-599'
httplib::make_range_header({{0, 0}, {-1, 1}}) // 'Range: bytes=0-0, -1'
```
### Keep-Alive connection
```cpp
httplib::Client cli("localhost", 1234);
cli.Get("/hello"); // with "Connection: close"
cli.set_keep_alive(true);
cli.Get("/world");
cli.set_keep_alive(false);
cli.Get("/last-request"); // with "Connection: close"
```
### Redirect
```cpp
httplib::Client cli("yahoo.com");
auto res = cli.Get("/");
res->status; // 301
cli.set_follow_location(true);
res = cli.Get("/");
res->status; // 200
```
### Use a specific network interface
NOTE: This feature is not available on Windows, yet.
```cpp
cli.set_interface("eth0"); // Interface name, IP address or host name
```
Compression
-----------
The server can apply compression to the following MIME type contents:
* all text types except text/event-stream
* image/svg+xml
* application/javascript
* application/json
* application/xml
* application/xhtml+xml
### Zlib Support
'gzip' compression is available with `CPPHTTPLIB_ZLIB_SUPPORT`. `libz` should be linked.
### Brotli Support
Brotli compression is available with `CPPHTTPLIB_BROTLI_SUPPORT`. Necessary libraries should be linked.
Please see https://github.com/google/brotli for more detail.
### Compress request body on client
```c++
cli.set_compress(true);
res = cli.Post("/resource/foo", "...", "text/plain");
```
### Compress response body on client
```c++
cli.set_decompress(false);
res = cli.Get("/resource/foo", {{"Accept-Encoding", "gzip, deflate, br"}});
res->body; // Compressed data
```
Use `poll` instead of `select`
------------------------------
`select` system call is used as default since it's more widely supported. If you want to let cpp-httplib use `poll` instead, you can do so with `CPPHTTPLIB_USE_POLL`.
Split httplib.h into .h and .cc
-------------------------------
```console
$ ./split.py -h
usage: split.py [-h] [-e EXTENSION] [-o OUT]
This script splits httplib.h into .h and .cc parts.
optional arguments:
-h, --help show this help message and exit
-e EXTENSION, --extension EXTENSION
extension of the implementation file (default: cc)
-o OUT, --out OUT where to write the files (default: out)
$ ./split.py
Wrote out/httplib.h and out/httplib.cc
```
NOTE
----
### g++
g++ 4.8 and below cannot build this library since `<regex>` in the versions are [broken](https://stackoverflow.com/questions/12530406/is-gcc-4-8-or-earlier-buggy-about-regular-expressions).
### Windows
Include `httplib.h` before `Windows.h` or include `Windows.h` by defining `WIN32_LEAN_AND_MEAN` beforehand.
```cpp
#include <httplib.h>
#include <Windows.h>
```
```cpp
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#include <httplib.h>
```
NOTE: cpp-httplib officially supports only the latest Visual Studio. It might work with former versions of Visual Studio, but I can no longer verify it. Pull requests are always welcome for the older versions of Visual Studio unless they break the C++11 conformance.
NOTE: Windows 8 or lower, Visual Studio 2013 or lower, and Cygwin on Windows are not supported.
License
-------
MIT license (© 2023 Yuji Hirose)
Special Thanks To
-----------------
[These folks](https://github.com/yhirose/cpp-httplib/graphs/contributors) made great contributions to polish this library to totally another level from a simple toy!

View File

@@ -0,0 +1,14 @@
#pragma once
/*
主要是用于各种加密
*/
#include "QCL_Include.hpp"
using namespace std;
namespace encrypt
{
string MD5(const string &info);
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,635 @@
#include "NetRequest.hpp"
#include <mutex>
#include <condition_variable>
#include <unordered_map>
#include <fstream>
#include <sstream>
#include <atomic>
namespace ntq
{
namespace
{
static std::string joinPath(const std::string &base, const std::string &path)
{
if (base.empty()) return path.empty() || path[0] == '/' ? path : std::string("/") + path;
if (path.empty()) return base[0] == '/' ? base : std::string("/") + base;
bool base_has = base.front() == '/';
bool base_end = base.back() == '/';
bool path_has = path.front() == '/';
std::string b = base_has ? base : std::string("/") + base;
if (base_end && path_has) return b + path.substr(1);
if (!base_end && !path_has) return b + "/" + path;
return b + path;
}
static std::string paramsToQuery(const httplib::Params &params)
{
if (params.empty()) return {};
std::string s;
bool first = true;
for (auto &kv : params)
{
if (!first) s += '&';
first = false;
s += kv.first;
s += '=';
s += kv.second;
}
return s;
}
static httplib::Headers mergeHeaders(const httplib::Headers &a, const httplib::Headers &b)
{
httplib::Headers h = a;
for (auto &kv : b)
{
// 覆盖同名 header先删再插
h.erase(kv.first);
h.emplace(kv.first, kv.second);
}
return h;
}
}
class ConcurrencyGate
{
public:
explicit ConcurrencyGate(size_t limit) : limit_(limit), active_(0) {}
void set_limit(size_t limit)
{
std::lock_guard<std::mutex> lk(mtx_);
limit_ = limit > 0 ? limit : 1;
cv_.notify_all();
}
struct Guard
{
ConcurrencyGate &g;
explicit Guard(ConcurrencyGate &gate) : g(gate) { g.enter(); }
~Guard() { g.leave(); }
};
private:
friend struct Guard;
void enter()
{
std::unique_lock<std::mutex> lk(mtx_);
cv_.wait(lk, [&]{ return active_ < limit_; });
++active_;
}
void leave()
{
std::lock_guard<std::mutex> lk(mtx_);
if (active_ > 0) --active_;
cv_.notify_one();
}
size_t limit_;
size_t active_;
std::mutex mtx_;
std::condition_variable cv_;
};
struct NetRequest::Impl
{
RequestOptions opts;
LogCallback logger;
Stats stats;
// 并发控制
ConcurrencyGate gate{4};
// 缓存
struct CacheEntry
{
HttpResponse resp;
std::chrono::steady_clock::time_point expiry;
};
bool cache_enabled = false;
std::chrono::milliseconds cache_ttl{0};
std::unordered_map<std::string, CacheEntry> cache;
std::mutex cache_mtx;
void log(const std::string &msg)
{
if (logger) logger(msg);
}
template <typename ClientT>
void apply_client_options(ClientT &cli)
{
const time_t c_sec = static_cast<time_t>(opts.connect_timeout_ms / 1000);
const time_t c_usec = static_cast<time_t>((opts.connect_timeout_ms % 1000) * 1000);
const time_t r_sec = static_cast<time_t>(opts.read_timeout_ms / 1000);
const time_t r_usec = static_cast<time_t>((opts.read_timeout_ms % 1000) * 1000);
const time_t w_sec = static_cast<time_t>(opts.write_timeout_ms / 1000);
const time_t w_usec = static_cast<time_t>((opts.write_timeout_ms % 1000) * 1000);
cli.set_connection_timeout(c_sec, c_usec);
cli.set_read_timeout(r_sec, r_usec);
cli.set_write_timeout(w_sec, w_usec);
cli.set_keep_alive(opts.keep_alive);
}
std::string build_full_path(const std::string &path) const
{
return joinPath(opts.base_path, path);
}
std::string cache_key(const std::string &path, const httplib::Params &params, const httplib::Headers &headers)
{
std::ostringstream oss;
oss << opts.scheme << "://" << opts.host << ':' << opts.port << build_full_path(path);
if (!params.empty()) oss << '?' << paramsToQuery(params);
for (auto &kv : headers) oss << '|' << kv.first << '=' << kv.second;
return oss.str();
}
void record_latency(double ms)
{
stats.last_latency_ms = ms;
const double alpha = 0.2;
if (stats.avg_latency_ms <= 0.0) stats.avg_latency_ms = ms;
else stats.avg_latency_ms = alpha * ms + (1.0 - alpha) * stats.avg_latency_ms;
}
static ErrorCode map_error()
{
// 简化:无法区分具体错误码,统一归为 Network
return ErrorCode::Network;
}
};
NetRequest::NetRequest(const RequestOptions &options)
: impl_(new Impl)
{
impl_->opts = options;
if (impl_->opts.scheme == "https" && impl_->opts.port == 80) impl_->opts.port = 443;
if (impl_->opts.scheme == "http" && impl_->opts.port == 0) impl_->opts.port = 80;
}
NetRequest::~NetRequest()
{
delete impl_;
}
void NetRequest::setLogger(LogCallback logger)
{
impl_->logger = std::move(logger);
}
void NetRequest::setMaxConcurrentRequests(size_t n)
{
impl_->gate.set_limit(n > 0 ? n : 1);
}
void NetRequest::enableCache(std::chrono::milliseconds ttl)
{
impl_->cache_enabled = true;
impl_->cache_ttl = ttl.count() > 0 ? ttl : std::chrono::milliseconds(1000);
}
void NetRequest::disableCache()
{
impl_->cache_enabled = false;
std::lock_guard<std::mutex> lk(impl_->cache_mtx);
impl_->cache.clear();
}
ntq::optional<HttpResponse> NetRequest::Get(const std::string &path,
const httplib::Params &query,
const httplib::Headers &headers,
ErrorCode *err)
{
ConcurrencyGate::Guard guard(impl_->gate);
impl_->stats.total_requests++;
auto start = std::chrono::steady_clock::now();
if (impl_->cache_enabled)
{
std::string key = impl_->cache_key(path, query, mergeHeaders(impl_->opts.default_headers, headers));
std::lock_guard<std::mutex> lk(impl_->cache_mtx);
auto it = impl_->cache.find(key);
if (it != impl_->cache.end() && std::chrono::steady_clock::now() < it->second.expiry)
{
if (err) *err = ErrorCode::None;
auto resp = it->second.resp;
resp.from_cache = true;
return resp;
}
}
ntq::optional<HttpResponse> result;
ErrorCode local_err = ErrorCode::None;
const auto full_path = impl_->build_full_path(path);
auto merged_headers = mergeHeaders(impl_->opts.default_headers, headers);
if (impl_->opts.scheme == "https")
{
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
httplib::SSLClient cli(impl_->opts.host.c_str(), impl_->opts.port);
impl_->apply_client_options(cli);
auto res = query.empty() ? cli.Get(full_path.c_str(), merged_headers)
: cli.Get(full_path.c_str(), query, merged_headers);
if (res)
{
HttpResponse r;
r.status = res->status;
r.body = res->body;
r.headers = res->headers;
r.from_cache = false;
result = r;
}
else
{
local_err = Impl::map_error();
}
#else
impl_->log("HTTPS requested but OpenSSL is not enabled; falling back to error.");
local_err = ErrorCode::SSL;
#endif
}
else
{
httplib::Client cli(impl_->opts.host.c_str(), impl_->opts.port);
impl_->apply_client_options(cli);
auto res = query.empty() ? cli.Get(full_path.c_str(), merged_headers)
: cli.Get(full_path.c_str(), query, merged_headers);
if (res)
{
HttpResponse r;
r.status = res->status;
r.body = res->body;
r.headers = res->headers;
r.from_cache = false;
result = r;
}
else
{
local_err = Impl::map_error();
}
}
auto end = std::chrono::steady_clock::now();
impl_->record_latency(std::chrono::duration<double, std::milli>(end - start).count());
if (!result.has_value())
{
impl_->stats.total_errors++;
if (err) *err = local_err;
return ntq::nullopt;
}
if (impl_->cache_enabled)
{
std::string key = impl_->cache_key(path, query, merged_headers);
std::lock_guard<std::mutex> lk(impl_->cache_mtx);
impl_->cache[key] = Impl::CacheEntry{*result, std::chrono::steady_clock::now() + impl_->cache_ttl};
}
if (err) *err = ErrorCode::None;
return result;
}
ntq::optional<HttpResponse> NetRequest::PostJson(const std::string &path,
const std::string &json,
const httplib::Headers &headers,
ErrorCode *err)
{
ConcurrencyGate::Guard guard(impl_->gate);
impl_->stats.total_requests++;
auto start = std::chrono::steady_clock::now();
ntq::optional<HttpResponse> result;
ErrorCode local_err = ErrorCode::None;
const auto full_path = impl_->build_full_path(path);
auto merged_headers = mergeHeaders(impl_->opts.default_headers, headers);
if (impl_->opts.scheme == "https")
{
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
httplib::SSLClient cli(impl_->opts.host.c_str(), impl_->opts.port);
impl_->apply_client_options(cli);
auto res = cli.Post(full_path.c_str(), merged_headers, json, "application/json");
if (res)
{
HttpResponse r{res->status, res->body, res->headers, false};
result = r;
}
else
{
local_err = Impl::map_error();
}
#else
local_err = ErrorCode::SSL;
#endif
}
else
{
httplib::Client cli(impl_->opts.host.c_str(), impl_->opts.port);
impl_->apply_client_options(cli);
auto res = cli.Post(full_path.c_str(), merged_headers, json, "application/json");
if (res)
{
HttpResponse r{res->status, res->body, res->headers, false};
result = r;
}
else
{
local_err = Impl::map_error();
}
}
auto end = std::chrono::steady_clock::now();
impl_->record_latency(std::chrono::duration<double, std::milli>(end - start).count());
if (!result)
{
impl_->stats.total_errors++;
if (err) *err = local_err;
return ntq::nullopt;
}
if (err) *err = ErrorCode::None;
return result;
}
ntq::optional<HttpResponse> NetRequest::PostForm(const std::string &path,
const httplib::Params &form,
const httplib::Headers &headers,
ErrorCode *err)
{
ConcurrencyGate::Guard guard(impl_->gate);
impl_->stats.total_requests++;
auto start = std::chrono::steady_clock::now();
ntq::optional<HttpResponse> result;
ErrorCode local_err = ErrorCode::None;
const auto full_path = impl_->build_full_path(path);
auto merged_headers = mergeHeaders(impl_->opts.default_headers, headers);
if (impl_->opts.scheme == "https")
{
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
httplib::SSLClient cli(impl_->opts.host.c_str(), impl_->opts.port);
impl_->apply_client_options(cli);
auto res = cli.Post(full_path.c_str(), merged_headers, form);
if (res)
{
HttpResponse r{res->status, res->body, res->headers, false};
result = r;
}
else
{
local_err = Impl::map_error();
}
#else
local_err = ErrorCode::SSL;
#endif
}
else
{
httplib::Client cli(impl_->opts.host.c_str(), impl_->opts.port);
impl_->apply_client_options(cli);
auto res = cli.Post(full_path.c_str(), merged_headers, form);
if (res)
{
HttpResponse r{res->status, res->body, res->headers, false};
result = r;
}
else
{
local_err = Impl::map_error();
}
}
auto end = std::chrono::steady_clock::now();
impl_->record_latency(std::chrono::duration<double, std::milli>(end - start).count());
if (!result)
{
impl_->stats.total_errors++;
if (err) *err = local_err;
return ntq::nullopt;
}
if (err) *err = ErrorCode::None;
return result;
}
std::future<ntq::optional<HttpResponse>> NetRequest::GetAsync(const std::string &path,
const httplib::Params &query,
const httplib::Headers &headers,
ErrorCode *err)
{
return std::async(std::launch::async, [this, path, query, headers, err]() mutable {
ErrorCode local;
auto r = Get(path, query, headers, &local);
if (err) *err = local;
return r;
});
}
std::future<ntq::optional<HttpResponse>> NetRequest::PostJsonAsync(const std::string &path,
const std::string &json,
const httplib::Headers &headers,
ErrorCode *err)
{
return std::async(std::launch::async, [this, path, json, headers, err]() mutable {
ErrorCode local;
auto r = PostJson(path, json, headers, &local);
if (err) *err = local;
return r;
});
}
std::future<ntq::optional<HttpResponse>> NetRequest::PostFormAsync(const std::string &path,
const httplib::Params &form,
const httplib::Headers &headers,
ErrorCode *err)
{
return std::async(std::launch::async, [this, path, form, headers, err]() mutable {
ErrorCode local;
auto r = PostForm(path, form, headers, &local);
if (err) *err = local;
return r;
});
}
bool NetRequest::DownloadToFile(const std::string &path,
const std::string &local_file,
const httplib::Headers &headers,
bool resume,
size_t /*chunk_size*/,
ErrorCode *err)
{
ConcurrencyGate::Guard guard(impl_->gate);
impl_->stats.total_requests++;
auto start = std::chrono::steady_clock::now();
std::ios_base::openmode mode = std::ios::binary | std::ios::out;
size_t offset = 0;
if (resume)
{
std::ifstream in(local_file, std::ios::binary | std::ios::ate);
if (in)
{
offset = static_cast<size_t>(in.tellg());
}
mode |= std::ios::app;
}
else
{
mode |= std::ios::trunc;
}
std::ofstream out(local_file, mode);
if (!out)
{
if (err) *err = ErrorCode::IOError;
impl_->stats.total_errors++;
return false;
}
auto merged_headers = mergeHeaders(impl_->opts.default_headers, headers);
if (resume && offset > 0)
{
merged_headers.emplace("Range", "bytes=" + std::to_string(offset) + "-");
}
const auto full_path = impl_->build_full_path(path);
int status_code = 0;
ErrorCode local_err = ErrorCode::None;
auto content_receiver = [&](const char *data, size_t data_length) {
out.write(data, static_cast<std::streamsize>(data_length));
return static_cast<bool>(out);
};
bool ok = false;
if (impl_->opts.scheme == "https")
{
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
httplib::SSLClient cli(impl_->opts.host.c_str(), impl_->opts.port);
impl_->apply_client_options(cli);
auto res = cli.Get(full_path.c_str(), merged_headers, content_receiver);
if (res)
{
status_code = res->status;
ok = (status_code == 200 || status_code == 206);
}
else
{
local_err = Impl::map_error();
}
#else
local_err = ErrorCode::SSL;
#endif
}
else
{
httplib::Client cli(impl_->opts.host.c_str(), impl_->opts.port);
impl_->apply_client_options(cli);
auto res = cli.Get(full_path.c_str(), merged_headers, content_receiver);
if (res)
{
status_code = res->status;
ok = (status_code == 200 || status_code == 206);
}
else
{
local_err = Impl::map_error();
}
}
out.close();
auto end = std::chrono::steady_clock::now();
impl_->record_latency(std::chrono::duration<double, std::milli>(end - start).count());
if (!ok)
{
impl_->stats.total_errors++;
if (err) *err = local_err;
return false;
}
if (err) *err = ErrorCode::None;
return true;
}
NetRequest::Stats NetRequest::getStats() const
{
return impl_->stats;
}
// ------------------------- Quick helpers -------------------------
namespace {
struct ParsedURL {
std::string scheme;
std::string host;
int port = 0;
std::string path_and_query;
bool ok = false;
};
static ParsedURL parse_url(const std::string &url)
{
ParsedURL p; p.ok = false;
// very small parser: scheme://host[:port]/path[?query]
auto pos_scheme = url.find("://");
if (pos_scheme == std::string::npos) return p;
p.scheme = url.substr(0, pos_scheme);
size_t pos_host = pos_scheme + 3;
size_t pos_path = url.find('/', pos_host);
std::string hostport = pos_path == std::string::npos ? url.substr(pos_host)
: url.substr(pos_host, pos_path - pos_host);
auto pos_colon = hostport.find(':');
if (pos_colon == std::string::npos) {
p.host = hostport;
p.port = (p.scheme == "https") ? 443 : 80;
} else {
p.host = hostport.substr(0, pos_colon);
std::string port_str = hostport.substr(pos_colon + 1);
p.port = port_str.empty() ? ((p.scheme == "https") ? 443 : 80) : std::atoi(port_str.c_str());
}
p.path_and_query = (pos_path == std::string::npos) ? "/" : url.substr(pos_path);
p.ok = !p.host.empty();
return p;
}
}
ntq::optional<HttpResponse> NetRequest::QuickGet(const std::string &url,
const httplib::Headers &headers,
ErrorCode *err)
{
auto p = parse_url(url);
if (!p.ok) { if (err) *err = ErrorCode::InvalidURL; return std::nullopt; }
RequestOptions opt; opt.scheme = p.scheme; opt.host = p.host; opt.port = p.port;
NetRequest req(opt);
return req.Get(p.path_and_query, {}, headers, err);
}
ntq::optional<HttpResponse> NetRequest::QuickPostJson(const std::string &url,
const std::string &json,
const httplib::Headers &headers,
ErrorCode *err)
{
auto p = parse_url(url);
if (!p.ok) { if (err) *err = ErrorCode::InvalidURL; return std::nullopt; }
RequestOptions opt; opt.scheme = p.scheme; opt.host = p.host; opt.port = p.port;
NetRequest req(opt);
return req.PostJson(p.path_and_query, json, headers, err);
}
ntq::optional<HttpResponse> NetRequest::QuickPostForm(const std::string &url,
const httplib::Params &form,
const httplib::Headers &headers,
ErrorCode *err)
{
auto p = parse_url(url);
if (!p.ok) { if (err) *err = ErrorCode::InvalidURL; return std::nullopt; }
RequestOptions opt; opt.scheme = p.scheme; opt.host = p.host; opt.port = p.port;
NetRequest req(opt);
return req.PostForm(p.path_and_query, form, headers, err);
}
}

View File

@@ -0,0 +1,693 @@
#include "Netra.hpp"
namespace QCL
{
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
TcpServer::TcpServer(int port)
: port_(port), running_(false), serverSock_(-1) {}
/**
* @brief 析构函数中调用stop()确保服务器资源被释放
*/
TcpServer::~TcpServer()
{
stop();
}
/**
* @brief 启动服务器:
* 1. 创建监听socketTCP
* 2. 绑定端口
* 3. 监听端口
* 4. 启动监听线程acceptThread_
*
* @return 成功返回true失败返回false
*/
bool TcpServer::start()
{
// 创建socket
serverSock_ = socket(AF_INET, SOCK_STREAM, 0);
if (serverSock_ < 0)
{
std::cerr << "Socket 创建失败\n";
return false;
}
// 设置socket地址结构
sockaddr_in serverAddr;
std::memset(&serverAddr, 0, sizeof(serverAddr));
serverAddr.sin_family = AF_INET;
serverAddr.sin_port = htons(port_); // 端口转网络字节序
serverAddr.sin_addr.s_addr = INADDR_ANY; // 监听所有网卡IP
// 允许端口重用,防止服务器异常关闭后端口被占用
int opt = 1;
setsockopt(serverSock_, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
// 绑定端口
if (bind(serverSock_, (sockaddr *)&serverAddr, sizeof(serverAddr)) < 0)
{
std::cerr << "绑定失败\n";
return false;
}
// 开始监听最大等待连接数为5
if (listen(serverSock_, 5) < 0)
{
std::cerr << "监听失败\n";
return false;
}
// 设置运行标志为true
running_ = true;
// 启动专门接受客户端连接的线程
acceptThread_ = std::thread(&TcpServer::acceptClients, this);
std::cout << "服务器启动,监听端口:" << port_ << std::endl;
return true;
}
/**
* @brief 停止服务器:
* 1. 设置运行标志为false通知线程退出
* 2. 关闭监听socket
* 3. 关闭所有客户端socket清理客户端列表
* 4. 等待所有线程退出
*/
void TcpServer::stop()
{
running_ = false;
if (serverSock_ >= 0)
{
close(serverSock_);
serverSock_ = -1;
}
{
// 线程安全关闭所有客户端socket
std::lock_guard<std::mutex> lock(clientsMutex_);
for (int sock : clientSockets_)
{
close(sock);
}
clientSockets_.clear();
}
// 等待监听线程退出
if (acceptThread_.joinable())
acceptThread_.join();
// 等待所有客户端处理线程退出
for (auto &t : clientThreads_)
{
if (t.joinable())
t.join();
}
std::cout << "服务器已停止\n";
}
/**
* @brief acceptClients函数循环监听客户端连接请求
* 每当accept成功
* 1. 打印客户端IP和Socket信息
* 2. 线程安全地将客户端Socket加入clientSockets_列表
* 3. 创建新线程调用handleClient处理该客户端收发
*/
void TcpServer::acceptClients()
{
while (running_)
{
sockaddr_in clientAddr;
socklen_t clientLen = sizeof(clientAddr);
int clientSock = accept(serverSock_, (sockaddr *)&clientAddr, &clientLen);
if (clientSock < 0)
{
if (running_)
std::cerr << "接受连接失败\n";
continue;
}
// 将客户端IP转换成字符串格式打印
char clientIP[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &(clientAddr.sin_addr), clientIP, INET_ADDRSTRLEN);
std::cout << "客户端连接IP: " << clientIP << ", Socket: " << clientSock << std::endl;
{
// 加锁保护共享的clientSockets_容器
std::lock_guard<std::mutex> lock(clientsMutex_);
clientSockets_.push_back(clientSock);
}
}
}
/**
* @brief 发送消息给指定客户端
* @param clientSock 客户端socket
* @param message 发送消息内容
*/
void TcpServer::sendToClient(int clientSock, const std::string &message)
{
send(clientSock, message.c_str(), message.size(), 0);
}
/**
* @brief 单次接收指定客户端数据
* @param clientSock 客户端socket
*/
std::string TcpServer::receiveFromClient(int clientSock, bool flag)
{
char buffer[1024];
std::memset(buffer, 0, sizeof(buffer));
int flags = flag ? 0 : MSG_DONTWAIT;
ssize_t bytesReceived = recv(clientSock, buffer, sizeof(buffer) - 1, flags);
if (bytesReceived <= 0)
return {};
return std::string(buffer, bytesReceived);
}
/**
* @brief 获取当前所有客户端Socket副本线程安全
* @return 包含所有客户端socket的vector副本
*/
std::vector<int> TcpServer::getClientSockets()
{
std::lock_guard<std::mutex> lock(clientsMutex_);
return clientSockets_;
}
void TcpServer::removeClient(int clientSock)
{
std::lock_guard<std::mutex> lock(clientsMutex_);
for (auto it = clientSockets_.begin(); it != clientSockets_.end(); ++it)
{
if (*it == clientSock)
{
close(*it);
clientSockets_.erase(it);
break;
}
}
}
bool TcpServer::isClientDisconnected(int clientSock)
{
char tmp;
ssize_t n = recv(clientSock, &tmp, 1, MSG_PEEK | MSG_DONTWAIT);
if (n == 0)
return true; // 对端有序关闭
if (n < 0)
{
if (errno == EAGAIN || errno == EWOULDBLOCK)
return false; // 只是暂时无数据
return true; // 其它错误视为断开
}
return false; // 有数据可读
}
/**
* @brief 获取连接客户端的IP和端口
* @param clientSock 客户端Socket描述符
*/
char *TcpServer::getClientIPAndPort(int clientSock)
{
struct sockaddr_in addr;
socklen_t addr_size = sizeof(addr);
// 获取客户端地址信息
if (getpeername(clientSock, (struct sockaddr *)&addr, &addr_size) == -1)
{
perror("getpeername failed");
return NULL;
}
// 分配内存存储结果(格式: "IP:PORT")
char *result = (char *)malloc(INET_ADDRSTRLEN + 10);
if (!result)
return NULL;
// 转换IP和端口
char *ip = inet_ntoa(addr.sin_addr);
unsigned short port = ntohs(addr.sin_port);
snprintf(result, INET_ADDRSTRLEN + 10, "%s:%d", ip, port);
return result;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
WriteFile::WriteFile(const std::string &filePath)
: filePath_(filePath) {}
/**
* @brief 覆盖写文本(线程安全)
*/
bool WriteFile::overwriteText(const std::string &content)
{
std::lock_guard<std::mutex> lock(writeMutex_); // 加锁
return writeToFile(content, std::ios::out | std::ios::trunc);
}
/**
* @brief 追加写文本(线程安全)
*/
bool WriteFile::appendText(const std::string &content)
{
std::lock_guard<std::mutex> lock(writeMutex_);
return writeToFile(content, std::ios::out | std::ios::app);
}
/**
* @brief 覆盖写二进制(线程安全)
*/
bool WriteFile::overwriteBinary(const std::vector<char> &data)
{
std::lock_guard<std::mutex> lock(writeMutex_);
return writeBinary(data, std::ios::out | std::ios::trunc | std::ios::binary);
}
/**
* @brief 追加写二进制(线程安全)
*/
bool WriteFile::appendBinary(const std::vector<char> &data)
{
std::lock_guard<std::mutex> lock(writeMutex_);
return writeBinary(data, std::ios::out | std::ios::app | std::ios::binary);
}
/**
* @brief 通用文本写入(私有)
*/
bool WriteFile::writeToFile(const std::string &content, std::ios::openmode mode)
{
std::ofstream file(filePath_, mode);
if (!file.is_open())
return false;
file << content;
file.close();
return true;
}
/**
* @brief 通用二进制写入(私有)
*/
bool WriteFile::writeBinary(const std::vector<char> &data, std::ios::openmode mode)
{
std::ofstream file(filePath_, mode);
if (!file.is_open())
return false;
file.write(data.data(), data.size());
file.close();
return true;
}
size_t WriteFile::countBytesPattern(const std::string &pattern, bool includePattern)
{
std::lock_guard<std::mutex> lock(writeMutex_);
if (pattern.empty())
return 0;
std::ifstream file(filePath_, std::ios::binary);
if (!file.is_open())
return 0;
const size_t chunkSize = 4096;
std::string buffer;
buffer.reserve(chunkSize * 2);
size_t totalRead = 0;
char chunk[chunkSize];
while (file.read(chunk, chunkSize) || file.gcount() > 0)
{
size_t bytesRead = file.gcount();
buffer.append(chunk, bytesRead);
size_t pos = buffer.find(pattern);
if (pos != std::string::npos)
{
size_t absolutePos = totalRead + pos; // 关键:加上 totalRead
return includePattern ? (absolutePos + pattern.size()) : absolutePos;
}
if (buffer.size() > pattern.size())
buffer.erase(0, buffer.size() - pattern.size());
totalRead += bytesRead; // 读完后再累计
}
return 0;
}
bool WriteFile::writeAfterPatternOrAppend(const std::string &pattern, const std::string &content)
{
std::lock_guard<std::mutex> lock(writeMutex_);
// 读取整个文件
std::ifstream in(filePath_, std::ios::binary);
if (!in.is_open())
return false;
std::string fileData((std::istreambuf_iterator<char>(in)), {});
in.close();
size_t pos = fileData.find(pattern);
if (pos != std::string::npos)
{
// 模式存在,插入位置在模式结尾
pos += pattern.size();
// 删除模式后所有内容
if (pos < fileData.size())
fileData.erase(pos);
// 插入新内容
fileData.insert(pos, content);
}
else
{
// 模式不存在,直接追加到文件末尾
if (!fileData.empty() && fileData.back() != '\n')
fileData += '\n'; // 保证换行
fileData += content;
}
// 写回文件
std::ofstream out(filePath_, std::ios::binary | std::ios::trunc);
if (!out.is_open())
return false;
out.write(fileData.data(), fileData.size());
return true;
}
bool WriteFile::overwriteAtPos(const std::string &content, size_t pos, size_t length)
{
std::lock_guard<std::mutex> lock(writeMutex_);
// 打开文件读取
std::ifstream in(filePath_, std::ios::binary);
if (!in.is_open())
return false;
std::string fileData((std::istreambuf_iterator<char>(in)), {});
in.close();
// 边界检查
if (pos >= fileData.size())
return false; // pos 超过文件范围,无法覆盖
// 生成要覆盖的实际数据块
std::string overwriteBlock;
if (content.size() >= length)
{
overwriteBlock = content.substr(0, length);
}
else
{
overwriteBlock = content;
overwriteBlock.append(length - content.size(), '\0'); // 补齐
}
// 计算实际可写范围
size_t maxWritable = std::min(length, fileData.size() - pos);
// 覆盖
fileData.replace(pos, maxWritable, overwriteBlock.substr(0, maxWritable));
// 写回文件
std::ofstream out(filePath_, std::ios::binary | std::ios::trunc);
if (!out.is_open())
return false;
out.write(fileData.data(), fileData.size());
return true;
}
bool WriteFile::insertAfterPos(const std::string &content, size_t pos, size_t length)
{
std::lock_guard<std::mutex> lock(writeMutex_);
// 打开文件读取
std::ifstream in(filePath_, std::ios::binary);
if (!in.is_open())
return false;
std::string fileData((std::istreambuf_iterator<char>(in)), {});
in.close();
// 边界检查
if (pos > fileData.size())
pos = fileData.size(); // 如果 pos 超出范围,就视为文件末尾
// 生成要插入的实际数据块
std::string insertBlock;
if (content.size() >= length)
{
insertBlock = content.substr(0, length); // 只取前 length 个字节
}
else
{
insertBlock = content; // 全部内容
insertBlock.append(length - content.size(), '\0'); // 补足空字节
}
// 插入到 pos 后面
fileData.insert(pos + 1, insertBlock);
// 写回文件
std::ofstream out(filePath_, std::ios::binary | std::ios::trunc);
if (!out.is_open())
return false;
out.write(fileData.data(), fileData.size());
return true;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
ReadFile::ReadFile(const std::string &filename) : filename_(filename) {}
ReadFile::~ReadFile()
{
std::lock_guard<std::mutex> lock(mtx_);
Close();
}
bool ReadFile::Open()
{
std::lock_guard<std::mutex> lock(mtx_);
if (file_.is_open())
file_.close();
file_.open(filename_, std::ios::in | std::ios::binary);
return file_.is_open();
}
void ReadFile::Close()
{
if (file_.is_open())
{
std::lock_guard<std::mutex> lock(mtx_);
file_.close();
}
}
bool ReadFile::IsOpen() const
{
std::lock_guard<std::mutex> lock(mtx_);
return file_.is_open();
}
std::string ReadFile::ReadAllText()
{
std::lock_guard<std::mutex> lock(mtx_);
if (!file_.is_open() && !Open())
return "";
std::ostringstream ss;
ss << file_.rdbuf();
return ss.str();
}
std::vector<char> ReadFile::ReadAllBinary()
{
std::lock_guard<std::mutex> lock(mtx_);
if (!file_.is_open() && !Open())
return {};
return ReadBytes(GetFileSize());
}
std::vector<std::string> ReadFile::ReadLines()
{
// std::lock_guard<std::mutex> lock(mtx_);
// if (!file_.is_open() && !Open())
// return {};
// std::vector<std::string> lines;
// std::string line;
// while (std::getline(file_, line))
// {
// lines.push_back(line);
// }
// return lines;
// std::lock_guard<std::mutex> lock(mtx_);
if (!file_.is_open()) {
file_.open(filename_, std::ios::in | std::ios::binary);
if (!file_.is_open()) return {};
}
file_.clear();
file_.seekg(0, std::ios::beg);
std::vector<std::string> lines;
std::string line;
while (std::getline(file_, line)) lines.push_back(line);
return lines;
}
std::vector<char> ReadFile::ReadBytes(size_t count)
{
std::lock_guard<std::mutex> lock(mtx_);
if (!file_.is_open() && !Open())
return {};
std::vector<char> buffer(count);
file_.read(buffer.data(), count);
buffer.resize(file_.gcount());
return buffer;
}
size_t ReadFile::GetBytesBefore(const std::string &marker, bool includeMarker)
{
std::lock_guard<std::mutex> lock(mtx_);
if (!file_.is_open() && !Open())
return 0;
file_.clear(); // 清除EOF和错误状态
file_.seekg(0, std::ios::beg); // 回到文件开头
const size_t chunkSize = 4096;
std::string buffer;
buffer.reserve(chunkSize * 2);
size_t totalRead = 0;
char chunk[chunkSize];
while (file_.read(chunk, chunkSize) || file_.gcount() > 0)
{
buffer.append(chunk, file_.gcount());
size_t pos = buffer.find(marker);
if (pos != std::string::npos)
{
// 如果 includeMarker 为 true返回包含 marker 的长度
if (includeMarker)
return pos + marker.size();
else
return pos;
}
// 保留末尾部分,避免 buffer 无限增长
if (buffer.size() > marker.size())
buffer.erase(0, buffer.size() - marker.size());
totalRead += file_.gcount();
}
return 0;
}
std::vector<char> ReadFile::ReadBytesFrom(size_t pos, size_t count)
{
std::lock_guard<std::mutex> lock(mtx_);
if (!file_.is_open() && !Open())
return {};
size_t filesize = GetFileSize();
if (pos >= filesize)
return {}; // 起始位置超出文件大小
file_.clear(); // 清除 EOF 和错误状态
file_.seekg(pos, std::ios::beg);
if (!file_)
return {};
size_t bytes_to_read = count;
if (count == 0 || pos + count > filesize)
bytes_to_read = filesize - pos; // 读取到文件末尾
std::vector<char> buffer(bytes_to_read);
file_.read(buffer.data(), bytes_to_read);
// 实际读取的字节数可能少于请求的数量
buffer.resize(file_.gcount());
return buffer;
}
bool ReadFile::FileExists() const
{
return std::filesystem::exists(filename_);
}
size_t ReadFile::GetFileSize() const
{
if (!FileExists())
return 0;
return std::filesystem::file_size(filename_);
}
void ReadFile::Reset()
{
std::lock_guard<std::mutex> lock(mtx_);
if (file_.is_open())
{
file_.clear();
file_.seekg(0, std::ios::beg);
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// 屏蔽所有信号
void blockAllSignals()
{
// 忽略全部的信号
for (int ii = 1; ii <= 64; ii++)
signal(ii, SIG_IGN);
}
std::string Ltrim(const std::string &s)
{
size_t start = 0;
while (start < s.size() && std::isspace(static_cast<unsigned char>(s[start])))
{
++start;
}
return s.substr(start);
}
std::string Rtrim(const std::string &s)
{
if (s.empty())
return s;
size_t end = s.size();
while (end > 0 && std::isspace(static_cast<unsigned char>(s[end - 1])))
{
--end;
}
return s.substr(0, end);
}
std::string LRtrim(const std::string &s)
{
return Ltrim(Rtrim(s));
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
}

View File

@@ -0,0 +1,91 @@
#include "encrypt.hpp"
#include <vector>
namespace encrypt
{
string MD5(const string &info)
{
auto leftrotate = [](uint32_t x, uint32_t c) -> uint32_t { return (x << c) | (x >> (32 - c)); };
static const uint32_t s[64] = {
7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22,
5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20,
4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23,
6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21
};
static const uint32_t K[64] = {
0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee, 0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501,
0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be, 0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821,
0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa, 0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8,
0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed, 0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a,
0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c, 0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70,
0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x04881d05, 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665,
0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039, 0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1,
0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1, 0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391
};
uint32_t a0 = 0x67452301;
uint32_t b0 = 0xefcdab89;
uint32_t c0 = 0x98badcfe;
uint32_t d0 = 0x10325476;
std::vector<uint8_t> msg(info.begin(), info.end());
uint64_t bit_len = static_cast<uint64_t>(msg.size()) * 8ULL;
msg.push_back(0x80);
while ((msg.size() % 64) != 56) msg.push_back(0x00);
for (int i = 0; i < 8; ++i) msg.push_back(static_cast<uint8_t>((bit_len >> (8 * i)) & 0xff));
for (size_t offset = 0; offset < msg.size(); offset += 64)
{
uint32_t M[16];
for (int i = 0; i < 16; ++i)
{
size_t j = offset + i * 4;
M[i] = static_cast<uint32_t>(msg[j]) |
(static_cast<uint32_t>(msg[j + 1]) << 8) |
(static_cast<uint32_t>(msg[j + 2]) << 16) |
(static_cast<uint32_t>(msg[j + 3]) << 24);
}
uint32_t A = a0, B = b0, C = c0, D = d0;
for (uint32_t i = 0; i < 64; ++i)
{
uint32_t F, g;
if (i < 16) { F = (B & C) | ((~B) & D); g = i; }
else if (i < 32) { F = (D & B) | ((~D) & C); g = (5 * i + 1) % 16; }
else if (i < 48) { F = B ^ C ^ D; g = (3 * i + 5) % 16; }
else { F = C ^ (B | (~D)); g = (7 * i) % 16; }
F = F + A + K[i] + M[g];
A = D;
D = C;
C = B;
B = B + leftrotate(F, s[i]);
}
a0 += A; b0 += B; c0 += C; d0 += D;
}
uint8_t digest[16];
auto u32_to_le = [](uint32_t v, uint8_t out[4]) {
out[0] = static_cast<uint8_t>(v & 0xff);
out[1] = static_cast<uint8_t>((v >> 8) & 0xff);
out[2] = static_cast<uint8_t>((v >> 16) & 0xff);
out[3] = static_cast<uint8_t>((v >> 24) & 0xff);
};
u32_to_le(a0, digest + 0);
u32_to_le(b0, digest + 4);
u32_to_le(c0, digest + 8);
u32_to_le(d0, digest + 12);
static const char *hex = "0123456789abcdef";
std::string out;
out.resize(32);
for (int i = 0; i < 16; ++i)
{
out[i * 2] = hex[(digest[i] >> 4) & 0x0f];
out[i * 2 + 1] = hex[digest[i] & 0x0f];
}
return out;
}
}

Binary file not shown.

View File

@@ -1,7 +1,7 @@
/*
本程序用于视频分流
1.推流摄像头画面,使用UDP原生协议进行推流,交由YOLO模型进行处理
2.接收YOLO传来的坐标,深度,警报等级等等数据
2.接收YOLO传来的坐标和度数据
3.根据获取到的数据绘制边框和相应数据
4.将绘制完毕的视频流继续推流至RTSP服务器用于输出
*/
@@ -16,7 +16,10 @@
#include <condition_variable>
#include <atomic>
#include "Netra.hpp"
using namespace std;
using namespace QCL;
using namespace cv;
using namespace chrono_literals;
@@ -26,6 +29,7 @@ Mat handleFrame; // 存放处理后的帧
const string mqtt_url = "tcp://192.168.12.1:1883";
const string clientId = "video_subData";
const string Topic = "/video/PersonData";
const string filePath = "../../InitAuth/conf/.env"; // 配置保存路径
const int Qos = 0;
mqtt::async_client client(mqtt_url, clientId);
@@ -36,6 +40,14 @@ struct Dection
double distance;
};
// 保存报警距离
struct dangerDistance
{
int danger;
int warn;
int safe;
} dis;
mutex detMutex; // 保护latestDection的互斥锁
vector<Dection> latestDection; // 保存最新接收到的检测结果
@@ -60,6 +72,8 @@ void mainLoop(VideoCapture &cap, FILE *pipe);
void getMsgCallback(mqtt::const_message_ptr msg);
// 绘制矩形方框和深度信息
void drawRect(int x, int y, int w, int h, double distance);
// 获取报警距离
bool GetDistance();
int main()
{
@@ -88,11 +102,53 @@ int main()
return 0;
}
// 获取报警距离
bool GetDistance()
{
// 获取距离信息
ReadFile rf(filePath);
if (rf.Open() == false)
{
cerr << "文件打开失败" << endl;
return false;
}
auto lines = rf.ReadLines();
string str;
for (auto &line : lines)
{
if (line.find("NEAR_THRESHOLD=") != string::npos)
dis.danger = stoi(line.substr(sizeof("NEAR_THRESHOLD=") - 1));
else if (line.find("MID_THRESHOLD=") != string::npos)
dis.warn = stoi(line.substr(sizeof("MID_THRESHOLD=") - 1));
else if (line.find("MAX_DISTANCE=") != string::npos)
dis.safe = stoi(line.substr(sizeof("MAX_DISTANCE=") - 1));
}
rf.Close();
return true;
}
// 绘制矩形方框和深度信息
void drawRect(int x, int y, int w, int h, double distance)
{
Rect r(x, y, w, h);
rectangle(handleFrame, r, Scalar(0, 255, 0), 2);
Scalar sca;
if (GetDistance() == false)
{
sca = Scalar(0, 0, 0);
}
if (distance <= dis.danger)
sca = Scalar(0, 0, 255);
else if (distance <= dis.warn)
sca = Scalar(0, 255, 255);
else
sca = Scalar(0, 255, 0);
rectangle(handleFrame, r, sca, 2);
putText(handleFrame,to_string(distance),Point(x,y),FONT_HERSHEY_SIMPLEX,0.35,Scalar(0,0,0));
}
// mqtt初始化

View File

@@ -3,8 +3,11 @@ all: video
PKG_CFLAGS := $(shell pkg-config --cflags opencv4)
PKG_LIBS := $(shell pkg-config --libs opencv4)
Lib=/home/orangepi/RKApp/VideoProsessing/NetraLib/src/Netra.cpp
Dir=/home/orangepi/RKApp/VideoProsessing/NetraLib/include
video: main.cpp
g++ -g -o video main.cpp $(PKG_CFLAGS) $(PKG_LIBS) -lpaho-mqttpp3 -lpaho-mqtt3a -lpthread
g++ -g -o video main.cpp $(Lib) -I$(Dir) $(PKG_CFLAGS) $(PKG_LIBS) -lpaho-mqttpp3 -lpaho-mqtt3a -lpthread
mv video ../bin/
clean:

Binary file not shown.

View File

@@ -36,7 +36,7 @@ bp::child net_proc; // 开启网络进程
// 文件设置路径
string filepath = "../../InitAuth/conf/.env";
string cameraPath = "/opt/rknn-yolov11/.env";
// string filepath = "/opt/rknn-yolov11/.env";
// string passwd = "/home/orangepi/InitAuth/pwd/.env";
// 云端Web认证接口
@@ -170,7 +170,7 @@ void getMsgCallback(mqtt::const_message_ptr msg)
string buffer = payload;
if (buffer.empty())
return;
// SET_DISTANCES: 解析 JSON 并更新 cameraPath 文件
// SET_DISTANCES: 解析 JSON 并更新 filepath 文件
if (buffer.find("SET_DISTANCES") != string::npos)
{ //设置报警距离
auto res = nlohmann::json::parse(buffer);
@@ -191,9 +191,9 @@ void getMsgCallback(mqtt::const_message_ptr msg)
double warn = toDouble(warning_json);
double safe = toDouble(safe_json);
ReadFile rf(cameraPath);
ReadFile rf(filepath);
if (!rf.Open()) {
cerr << "文件打开失败: " << cameraPath << "\n";
cerr << "文件打开失败: " << filepath << "\n";
} else {
auto lines = rf.ReadLines();
for (auto &line : lines) {
@@ -208,19 +208,19 @@ void getMsgCallback(mqtt::const_message_ptr msg)
if (i + 1 < lines.size()) out += "\n";
}
rf.Close();
WriteFile wf(cameraPath);
WriteFile wf(filepath);
wf.overwriteText(out);
}
return;
}
// media: 更新 cameraPath 的 MEDIA_* 设置
// media: 更新 filepath 的 MEDIA_* 设置
if (buffer.find("media") != string::npos)
{//设置摄像头参数
CalculateInfo(media, buffer);
ReadFile rf(cameraPath);
ReadFile rf(filepath);
if (!rf.Open()) {
cerr << "文件打开失败: " << cameraPath << "\n";
cerr << "文件打开失败: " << filepath << "\n";
} else {
auto lines = rf.ReadLines();
for (auto &line : lines) {
@@ -235,7 +235,7 @@ void getMsgCallback(mqtt::const_message_ptr msg)
if (i + 1 < lines.size()) out += "\n";
}
rf.Close();
WriteFile wf(cameraPath);
WriteFile wf(filepath);
wf.overwriteText(out);
}
return;