diff --git a/ApCreate/NetraLib/include/Netra.hpp b/ApCreate/NetraLib/include/Netra.hpp index 85aba11..362ae28 100644 --- a/ApCreate/NetraLib/include/Netra.hpp +++ b/ApCreate/NetraLib/include/Netra.hpp @@ -1,411 +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 getClientSockets(); - - private: - /** - * @brief 监听并接受新的客户端连接(运行在独立线程中) - */ - void acceptClients(); - - private: - int serverSock_; ///< 服务器监听Socket描述符 - int port_; ///< 服务器监听端口 - std::atomic running_; ///< 服务器运行状态标志(线程安全) - std::vector clientThreads_; ///< 用于处理每个客户端的线程集合 - std::thread acceptThread_; ///< 负责监听新连接的线程 - std::mutex clientsMutex_; ///< 保护clientSockets_的互斥锁 - std::vector 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 &data); - - /** - * @brief 追加写二进制文件(线程安全) - * @param data 要写入的二进制数据 - * @return true 写入成功 - * @return false 写入失败 - */ - bool appendBinary(const std::vector &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); - - 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 &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 ReadAllBinary(); - - /** - * @brief 按行读取文本 - * @return 每行作为一个字符串的 vector - */ - std::vector ReadLines(); - - /** - * @brief 读取指定字节数 - * @param count 要读取的字节数 - * @return 实际读取到的字节数据 - */ - std::vector 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 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 - std::string to_string_any(const T &value) - { - std::ostringstream oss; - oss << value; - return oss.str(); - } - - // 递归获取 tuple 中 index 对应参数 - template - std::string get_tuple_arg(const Tuple &tup, std::size_t index) - { - if constexpr (I < std::tuple_size_v) - { - if (I == index) - return to_string_any(std::get(tup)); - else - return get_tuple_arg(tup, index); - } - else - { - throw std::runtime_error("Too few arguments for format string"); - } - } - - // format 函数 - template - std::string format(const std::string &fmt, const Args &...args) - { - std::ostringstream oss; - std::tuple 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(); - } - //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -} +#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 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 running_; ///< 服务器运行状态标志(线程安全) + std::vector clientThreads_; ///< 用于处理每个客户端的线程集合 + std::thread acceptThread_; ///< 负责监听新连接的线程 + std::mutex clientsMutex_; ///< 保护clientSockets_的互斥锁 + std::vector 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 &data); + + /** + * @brief 追加写二进制文件(线程安全) + * @param data 要写入的二进制数据 + * @return true 写入成功 + * @return false 写入失败 + */ + bool appendBinary(const std::vector &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 &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 ReadAllBinary(); + + /** + * @brief 按行读取文本 + * @return 每行作为一个字符串的 vector + */ + std::vector ReadLines(); + + /** + * @brief 读取指定字节数 + * @param count 要读取的字节数 + * @return 实际读取到的字节数据 + */ + std::vector 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 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 + std::string to_string_any(const T &value) + { + std::ostringstream oss; + oss << value; + return oss.str(); + } + + // 递归获取 tuple 中 index 对应参数 + template + std::string get_tuple_arg(const Tuple &tup, std::size_t index) + { + if constexpr (I < std::tuple_size_v) + { + if (I == index) + return to_string_any(std::get(tup)); + else + return get_tuple_arg(tup, index); + } + else + { + throw std::runtime_error("Too few arguments for format string"); + } + } + + // format 函数 + template + std::string format(const std::string &fmt, const Args &...args) + { + std::ostringstream oss; + std::tuple 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(); + } + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +} diff --git a/ApCreate/NetraLib/src/Netra.cpp b/ApCreate/NetraLib/src/Netra.cpp index 3160759..51b7379 100644 --- a/ApCreate/NetraLib/src/Netra.cpp +++ b/ApCreate/NetraLib/src/Netra.cpp @@ -1,664 +1,693 @@ -#include "Netra.hpp" - -namespace QCL -{ - //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - TcpServer::TcpServer(int port) - : port_(port), running_(false), serverSock_(-1) {} - - /** - * @brief 析构函数中调用stop()确保服务器资源被释放 - */ - TcpServer::~TcpServer() - { - stop(); - } - - /** - * @brief 启动服务器: - * 1. 创建监听socket(TCP) - * 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 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 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 TcpServer::getClientSockets() - { - std::lock_guard lock(clientsMutex_); - return clientSockets_; - } - - /** - * @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 lock(writeMutex_); // 加锁 - return writeToFile(content, std::ios::out | std::ios::trunc); - } - - /** - * @brief 追加写文本(线程安全) - */ - bool WriteFile::appendText(const std::string &content) - { - std::lock_guard lock(writeMutex_); - return writeToFile(content, std::ios::out | std::ios::app); - } - - /** - * @brief 覆盖写二进制(线程安全) - */ - bool WriteFile::overwriteBinary(const std::vector &data) - { - std::lock_guard lock(writeMutex_); - return writeBinary(data, std::ios::out | std::ios::trunc | std::ios::binary); - } - - /** - * @brief 追加写二进制(线程安全) - */ - bool WriteFile::appendBinary(const std::vector &data) - { - std::lock_guard 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 &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 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 lock(writeMutex_); - - // 读取整个文件 - std::ifstream in(filePath_, std::ios::binary); - if (!in.is_open()) - return false; - - std::string fileData((std::istreambuf_iterator(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 lock(writeMutex_); - - // 打开文件读取 - std::ifstream in(filePath_, std::ios::binary); - if (!in.is_open()) - return false; - - std::string fileData((std::istreambuf_iterator(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 lock(writeMutex_); - - // 打开文件读取 - std::ifstream in(filePath_, std::ios::binary); - if (!in.is_open()) - return false; - - std::string fileData((std::istreambuf_iterator(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 lock(mtx_); - Close(); - } - - bool ReadFile::Open() - { - std::lock_guard 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 lock(mtx_); - file_.close(); - } - } - - bool ReadFile::IsOpen() const - { - std::lock_guard lock(mtx_); - return file_.is_open(); - } - - std::string ReadFile::ReadAllText() - { - std::lock_guard lock(mtx_); - if (!file_.is_open() && !Open()) - return ""; - - std::ostringstream ss; - ss << file_.rdbuf(); - return ss.str(); - } - - std::vector ReadFile::ReadAllBinary() - { - std::lock_guard lock(mtx_); - if (!file_.is_open() && !Open()) - return {}; - - return ReadBytes(GetFileSize()); - } - - std::vector ReadFile::ReadLines() - { - // std::lock_guard lock(mtx_); - // if (!file_.is_open() && !Open()) - // return {}; - - // std::vector lines; - // std::string line; - // while (std::getline(file_, line)) - // { - // lines.push_back(line); - // } - // return lines; - - std::lock_guard 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 lines; - std::string line; - while (std::getline(file_, line)) lines.push_back(line); - return lines; - } - - std::vector ReadFile::ReadBytes(size_t count) - { - std::lock_guard lock(mtx_); - if (!file_.is_open() && !Open()) - return {}; - - std::vector 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 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 ReadFile::ReadBytesFrom(size_t pos, size_t count) - { - std::lock_guard 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 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 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(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(s[end - 1]))) - { - --end; - } - return s.substr(0, end); - } - - std::string LRtrim(const std::string &s) - { - return Ltrim(Rtrim(s)); - } - - //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#include "Netra.hpp" + +namespace QCL +{ + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + TcpServer::TcpServer(int port) + : port_(port), running_(false), serverSock_(-1) {} + + /** + * @brief 析构函数中调用stop()确保服务器资源被释放 + */ + TcpServer::~TcpServer() + { + stop(); + } + + /** + * @brief 启动服务器: + * 1. 创建监听socket(TCP) + * 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 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 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 TcpServer::getClientSockets() + { + std::lock_guard lock(clientsMutex_); + return clientSockets_; + } + + void TcpServer::removeClient(int clientSock) + { + std::lock_guard 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 lock(writeMutex_); // 加锁 + return writeToFile(content, std::ios::out | std::ios::trunc); + } + + /** + * @brief 追加写文本(线程安全) + */ + bool WriteFile::appendText(const std::string &content) + { + std::lock_guard lock(writeMutex_); + return writeToFile(content, std::ios::out | std::ios::app); + } + + /** + * @brief 覆盖写二进制(线程安全) + */ + bool WriteFile::overwriteBinary(const std::vector &data) + { + std::lock_guard lock(writeMutex_); + return writeBinary(data, std::ios::out | std::ios::trunc | std::ios::binary); + } + + /** + * @brief 追加写二进制(线程安全) + */ + bool WriteFile::appendBinary(const std::vector &data) + { + std::lock_guard 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 &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 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 lock(writeMutex_); + + // 读取整个文件 + std::ifstream in(filePath_, std::ios::binary); + if (!in.is_open()) + return false; + + std::string fileData((std::istreambuf_iterator(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 lock(writeMutex_); + + // 打开文件读取 + std::ifstream in(filePath_, std::ios::binary); + if (!in.is_open()) + return false; + + std::string fileData((std::istreambuf_iterator(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 lock(writeMutex_); + + // 打开文件读取 + std::ifstream in(filePath_, std::ios::binary); + if (!in.is_open()) + return false; + + std::string fileData((std::istreambuf_iterator(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 lock(mtx_); + Close(); + } + + bool ReadFile::Open() + { + std::lock_guard 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 lock(mtx_); + file_.close(); + } + } + + bool ReadFile::IsOpen() const + { + std::lock_guard lock(mtx_); + return file_.is_open(); + } + + std::string ReadFile::ReadAllText() + { + std::lock_guard lock(mtx_); + if (!file_.is_open() && !Open()) + return ""; + + std::ostringstream ss; + ss << file_.rdbuf(); + return ss.str(); + } + + std::vector ReadFile::ReadAllBinary() + { + std::lock_guard lock(mtx_); + if (!file_.is_open() && !Open()) + return {}; + + return ReadBytes(GetFileSize()); + } + + std::vector ReadFile::ReadLines() + { + // std::lock_guard lock(mtx_); + // if (!file_.is_open() && !Open()) + // return {}; + + // std::vector lines; + // std::string line; + // while (std::getline(file_, line)) + // { + // lines.push_back(line); + // } + // return lines; + + std::lock_guard 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 lines; + std::string line; + while (std::getline(file_, line)) lines.push_back(line); + return lines; + } + + std::vector ReadFile::ReadBytes(size_t count) + { + std::lock_guard lock(mtx_); + if (!file_.is_open() && !Open()) + return {}; + + std::vector 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 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 ReadFile::ReadBytesFrom(size_t pos, size_t count) + { + std::lock_guard 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 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 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(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(s[end - 1]))) + { + --end; + } + return s.substr(0, end); + } + + std::string LRtrim(const std::string &s) + { + return Ltrim(Rtrim(s)); + } + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// } \ No newline at end of file diff --git a/ApCreate/bin/wifi b/ApCreate/bin/wifi index ee867b2..1f50a7a 100755 Binary files a/ApCreate/bin/wifi and b/ApCreate/bin/wifi differ diff --git a/ApCreate/src/main.cpp b/ApCreate/src/main.cpp index 2517632..28952ad 100644 --- a/ApCreate/src/main.cpp +++ b/ApCreate/src/main.cpp @@ -10,6 +10,8 @@ #include #include +#include + #include #include @@ -34,7 +36,12 @@ string filepath = "/home/orangepi/InitAuth/conf/.env"; string cameraPath = "/opt/rknn-yolov11/.env"; string passwd = "/home/orangepi/InitAuth/pwd/.env"; -string url = "http://116.147.36.110:8095/device/validateDevice"; +// 云端Web认证接口 +const string url = "http://116.147.36.110:8095/device/validateDevice"; +const string mqtt_url = "tcp://192.168.12.1:1883"; +const string clientId = "RK3588_SubTest"; +const string Topic = "/Test"; +const int Qos = 1; std::atomic isRunning(true); // 全局运行标志 std::atomic confirm(true); // 发送卡和ID @@ -54,6 +61,8 @@ struct OutSignal bool inPutMode; // 触发输入模式 true--高电平,false -- 低电平 } algor{}; +mqtt::async_client client(mqtt_url, clientId); + // 确认是否已经进行过认证 bool ConfirmInit(); @@ -94,6 +103,12 @@ void StartNet(); // 开启服务 void StartService(); +// mqtt初始化 +void mqttInit(); + +// 接收消息回调 +void getMsgCallback(mqtt::const_message_ptr msg); + /* parames: argv[1] - SSID of the WiFi network to connect to @@ -110,6 +125,14 @@ int main(int argc, char *argv[]) blockAllSignals(); signal(SIGINT, Exit); // 捕获Ctrl+C信号 + // 初始化mqtt服务器 + mqttInit(); + + while (1) + { + this_thread::sleep_for(chrono::seconds(1)); + } + // 开启服务器 MyServer = new TcpServer(8848); @@ -149,6 +172,27 @@ int main(int argc, char *argv[]) return 0; } +void mqttInit() +{ + + client.set_connected_handler([](const string &cause) + { + cout << cause << endl; + cout << "Connected Successed!\n"; }); + client.set_message_callback(getMsgCallback); + + // 连接服务器 + client.connect()->wait(); + client.subscribe(Topic, Qos)->wait(); +} + +// 接收消息回调 +void getMsgCallback(mqtt::const_message_ptr msg) +{ + cout << "收到消息" << endl; + cout << "recv:" << msg->to_string() << endl; +} + // 开启服务 void StartService() { @@ -263,9 +307,11 @@ void ReceiveData() continue; } int index = client.size(); - for (int ii = 0; ii < index; ii++) + // cout << "index: " << index << endl; + for (int ii = 0; ii < index; ++ii) // 从新到旧,优先处理新连接 { buffer = MyServer->receiveFromClient(client[ii], false); // 非阻塞模式接受数据 + // cout << buffer << endl; if (buffer.empty() == false) { cout << "已收到" << buffer << endl; @@ -313,14 +359,15 @@ void ReceiveData() double safe = toDouble(safe_json); // 整文件读取,逐行替换,保持注释不变 - ReadFile rf(cameraPath); - if (!rf.Open()) + ReadFile *rf = new ReadFile(cameraPath); + if (!rf->Open()) { cerr << "文件打开失败: " << cameraPath << "\n"; } else { - auto lines = rf.ReadLines(); + auto lines = rf->ReadLines(); + delete rf; for (auto &line : lines) { if (line.rfind("NEAR_THRESHOLD=", 0) == 0) @@ -346,32 +393,90 @@ void ReceiveData() if (i + 1 < lines.size()) out += "\n"; } - WriteFile wf(cameraPath); - wf.overwriteText(out); + WriteFile *wf = new WriteFile(cameraPath); + wf->overwriteText(out); + delete wf; } } - else + else if (buffer.find("media") != string::npos) { - // 写摄像头画面设置 - string conf = ""; + // 写摄像头参数(与距离相同方式: 整体读取-逐行替换-整体写回) cout << buffer << endl; - if (buffer.find("media") != string::npos) - { // 写摄像头参数 - cout << buffer << endl; - WriteFile cam(cameraPath); - CalculateInfo(media, buffer); - conf = format("\nMEDIA_MIRROR={}\nMEDIA_FLIP={}\nMEDIA_OCCLUSION={}\n", media.mirror == 1 ? "true" : "false", media.flip == 1 ? "true" : "false", media.occlusion == 1 ? "true" : "false"); - cout << conf; - cam.writeAfterPatternOrAppend("***---***", conf); - } - if (buffer.find("algorithm") != string::npos) + CalculateInfo(media, buffer); + ReadFile *rf = new ReadFile(cameraPath); + if (!rf->Open()) { - // 写输入输出参数 - WriteFile mode(filepath); - int pos = mode.countBytesPattern("***---***", true); - CalculateInfo(algor, buffer); - conf = format("\noutPutMode:{}\ninPutMode:{}", algor.outPutMode == 1 ? "true" : "false", algor.inPutMode == 1 ? "true" : "false"); - mode.writeAfterPatternOrAppend("***---***", conf); + cerr << "文件打开失败: " << cameraPath << "\n"; + } + else + { + auto lines = rf->ReadLines(); + delete rf; + for (auto &line : lines) + { + if (line.rfind("MEDIA_MIRROR=", 0) == 0) + { + line = format("MEDIA_MIRROR={}", media.mirror ? "true" : "false"); + } + else if (line.rfind("MEDIA_FLIP=", 0) == 0) + { + line = format("MEDIA_FLIP={}", media.flip ? "true" : "false"); + } + else if (line.rfind("MEDIA_OCCLUSION=", 0) == 0) + { + line = format("MEDIA_OCCLUSION={}", media.occlusion ? "true" : "false"); + } + } + + string out; + out.reserve(4096); + for (size_t i = 0; i < lines.size(); ++i) + { + out += lines[i]; + if (i + 1 < lines.size()) + out += "\n"; + } + WriteFile *wf = new WriteFile(cameraPath); + wf->overwriteText(out); + delete wf; + } + } + else if (buffer.find("algorithm") != string::npos) + { + // 写输入输出参数(与距离相同方式: 整体读取-逐行替换-整体写回) + CalculateInfo(algor, buffer); + ReadFile *rf2 = new ReadFile(filepath); + if (!rf2->Open()) + { + cerr << "文件打开失败: " << filepath << "\n"; + } + else + { + auto lines = rf2->ReadLines(); + delete rf2; + for (auto &line : lines) + { + if (line.rfind("outPutMode:", 0) == 0) + { + line = format("outPutMode:{}", algor.outPutMode ? "true" : "false"); + } + else if (line.rfind("inPutMode:", 0) == 0) + { + line = format("inPutMode:{}", algor.inPutMode ? "true" : "false"); + } + } + + string out; + out.reserve(4096); + for (size_t i = 0; i < lines.size(); ++i) + { + out += lines[i]; + if (i + 1 < lines.size()) + out += "\n"; + } + WriteFile *wf2 = new WriteFile(filepath); + wf2->overwriteText(out); + delete wf2; } } } @@ -390,7 +495,8 @@ void OpenRTSP() // 视频推流 void VideoStream() { - string commnd = "ffmpeg -f v4l2 -i /dev/video0 -c:v h264_rkmpp -rtsp_transport tcp -f rtsp rtsp://192.168.12.1:8554/stream"; + // 静音ffmpeg的统计输出,保留错误;避免污染日志 + string commnd = "ffmpeg -nostats -hide_banner -loglevel error -f v4l2 -i /dev/video0 -c:v h264_rkmpp -rtsp_transport tcp -f rtsp rtsp://192.168.12.1:8554/stream 2>/dev/null"; video_proc = bp::child("/bin/bash", bp::args = {"-c", commnd}); } diff --git a/ApCreate/src/makefile b/ApCreate/src/makefile index 0437205..d97d9a5 100644 --- a/ApCreate/src/makefile +++ b/ApCreate/src/makefile @@ -1,7 +1,7 @@ all:wifi wifi:main.cpp - g++ -g -o wifi main.cpp /home/orangepi/RKApp/ApCreate/NetraLib/src/Netra.cpp /home/orangepi/RKApp/ApCreate/NetraLib/src/encrypt.cpp /home/orangepi/RKApp/ApCreate/NetraLib/src/NetRequest.cpp -lpthread -I/home/orangepi/RKApp/ApCreate/NetraLib/include + g++ -g -o wifi main.cpp /home/orangepi/RKApp/ApCreate/NetraLib/src/Netra.cpp /home/orangepi/RKApp/ApCreate/NetraLib/src/encrypt.cpp /home/orangepi/RKApp/ApCreate/NetraLib/src/NetRequest.cpp -I/home/orangepi/RKApp/ApCreate/NetraLib/include -lpaho-mqttpp3 -lpaho-mqtt3a -lpthread mv ./wifi ../bin/wifi clean: