ZLToolKit 1.0 命令行工具实战:基于CMD模块封装3种自定义命令
ZLToolKit 1.0 命令行工具实战基于CMD模块封装3种自定义命令在C开发中命令行工具是开发者日常工作中不可或缺的利器。ZLToolKit作为一款基于C11的高性能服务器框架其内置的CMD模块提供了强大的命令行解析功能能够帮助开发者快速构建灵活的命令行工具。本文将深入探讨如何利用ZLToolKit的CMD模块通过实际案例演示三种典型命令无参数命令、带参数命令、带默认值命令的实现方法。1. ZLToolKit CMD模块核心架构解析ZLToolKit的CMD模块采用了经典的命令模式设计通过四个核心类实现了完整的命令行解析功能Option类封装单个命令行选项的元数据class Option { public: enum ArgType { ArgNone, ArgRequired, ArgOptional }; Option(char shortOpt, const char *longOpt, ArgType argType, const char *defaultValue, bool mustExist, const char *des); // 其他成员函数... };OptionParser类负责实际解析命令行参数class OptionParser { public: void operator()(mINI allArg, int argc, char *argv[]); // 其他成员函数... };CMD类命令基类开发者需要继承此类实现具体命令class CMD : public mapstring, string { public: void operator()(int argc, char *argv[]); // 其他成员函数... };CMDRegister类管理多个命令的单例类class CMDRegister { public: void registCMD(const char *name, const std::shared_ptrCMD cmd); // 其他成员函数... };这种架构设计使得命令的定义、解析和执行完全解耦开发者只需关注业务逻辑的实现。CMD模块支持的特性包括特性说明示例长短选项支持单字符短选项和多字符长选项-h和--help参数类型支持无参、必选参数和可选参数--file config.ini默认值可为参数设置默认值--port 8080必须参数标记某些参数为必须提供数据库连接参数帮助系统自动生成帮助信息help命令2. 开发环境准备与项目配置在开始实现自定义命令前需要先搭建ZLToolKit的开发环境。以下是基于Linux系统的配置步骤获取ZLToolKit源码git clone https://github.com/ZLMediaKit/ZLToolKit.git cd ZLToolKit编译安装./build_for_linux.sh sudo make install创建示例项目mkdir cmd_demo cd cmd_demo touch main.cpp CMakeLists.txt配置CMakeLists.txtcmake_minimum_required(VERSION 3.10) project(cmd_demo) set(CMAKE_CXX_STANDARD 11) find_package(ZLToolKit REQUIRED) add_executable(cmd_demo main.cpp) target_link_libraries(cmd_demo PRIVATE ZLToolKit)验证环境#include iostream #include Util/CMD.h int main() { std::cout ZLToolKit环境配置成功! std::endl; return 0; }提示Windows环境下可使用Visual Studio 2017或更高版本通过CMake生成解决方案文件进行编译。3. 实现无参数命令系统信息查询我们首先实现一个简单的无参数命令sysinfo用于显示系统基本信息。以下是完整实现#include iostream #include Util/CMD.h #include Util/util.h #include Poller/EventPoller.h class SysInfoCMD : public CMD { public: SysInfoCMD() { _parser Option(h, help, Option::ArgNone, nullptr, false, 显示帮助信息); } virtual void operator()(int argc, char *argv[]) override { try { CMD::operator()(argc, argv); if (hasKey(help)) { showHelp(); return; } showSystemInfo(); } catch (std::exception ex) { std::cerr ex.what() std::endl; showHelp(); } } private: void showSystemInfo() { std::cout 系统信息 std::endl; std::cout CPU核心数: thread::hardware_concurrency() std::endl; std::cout 当前时间: getTimeStr() std::endl; std::cout 运行线程数: EventPollerPool::Instance().size() std::endl; } void showHelp() { std::cout 用法: sysinfo [选项] std::endl; std::cout 选项: std::endl; _parser.traverse([this](const Option opt) { std::cout - opt._shortOpt -- opt._longOpt (opt._mustExist ? (必选) : ) : opt._des std::endl; }); } };这个实现展示了无参数命令的基本结构构造函数中定义命令支持的选项这里只有-h/--help重载operator()实现命令逻辑提供帮助信息显示功能实现具体的命令功能showSystemInfo()注册并使用这个命令int main(int argc, char *argv[]) { CMDRegister::Instance().registCMD(sysinfo, std::make_sharedSysInfoCMD()); if (argc 2) { std::cerr 请指定命令使用 help 查看可用命令 std::endl; return -1; } try { CMDRegister::Instance()(argv[1], argc - 1, argv 1); } catch (std::exception ex) { std::cerr ex.what() std::endl; return -1; } return 0; }4. 实现带参数命令文件处理器接下来我们实现一个更复杂的带参数命令fileproc用于处理文件操作。这个命令将支持以下参数参数类型必须描述-i / --input有参是输入文件路径-o / --output有参否输出文件路径-r / --replace无参否是否替换现有文件-v / --verbose无参否显示详细处理信息实现代码如下class FileProcCMD : public CMD { public: FileProcCMD() { _parser Option(i, input, Option::ArgRequired, nullptr, true, 输入文件路径) Option(o, output, Option::ArgRequired, nullptr, false, 输出文件路径) Option(r, replace, Option::ArgNone, nullptr, false, 替换现有文件) Option(v, verbose, Option::ArgNone, nullptr, false, 显示详细输出) Option(h, help, Option::ArgNone, nullptr, false, 显示帮助信息); } virtual void operator()(int argc, char *argv[]) override { try { CMD::operator()(argc, argv); if (hasKey(help)) { showHelp(); return; } processFile(); } catch (std::exception ex) { std::cerr 错误: ex.what() std::endl; showHelp(); } } private: void processFile() { string input (*this)[input]; string output hasKey(output) ? (*this)[output] : input .bak; bool replace hasKey(replace); bool verbose hasKey(verbose); if (verbose) { std::cout 开始处理文件... std::endl; std::cout 输入文件: input std::endl; std::cout 输出文件: output std::endl; } if (!File::exists(input)) { throw std::runtime_error(输入文件不存在: input); } if (File::exists(output) !replace) { throw std::runtime_error(输出文件已存在使用 -r 参数强制替换); } // 实际文件处理逻辑 if (verbose) std::cout 正在复制文件... std::endl; File::copy(input, output); if (verbose) { std::cout 文件处理完成 std::endl; std::cout 文件大小: File::fileSize(output) 字节 std::endl; } } void showHelp() { std::cout 用法: fileproc -i 输入文件 [选项] std::endl; std::cout 选项: std::endl; _parser.traverse([this](const Option opt) { std::cout - opt._shortOpt -- opt._longOpt (opt._argType ! Option::ArgNone ? 参数 : ) (opt._mustExist ? (必选) : ) : opt._des std::endl; }); std::cout \n示例: std::endl; std::cout fileproc -i input.txt -o output.txt -r std::endl; } };这个实现展示了带参数命令的关键点在构造函数中定义多个选项包括必选参数和可选参数通过hasKey()检查选项是否存在通过(*this)[option]获取选项值实现完整的参数验证逻辑提供详细的帮助信息和示例5. 实现带默认值命令网络连接测试器最后我们实现一个带默认值的命令netcheck用于测试网络连接。这个命令将包含以下特性设置默认服务器地址和端口支持自定义测试次数支持设置超时时间提供详细的统计信息实现代码如下class NetCheckCMD : public CMD { public: NetCheckCMD() { _parser Option(s, server, Option::ArgRequired, 127.0.0.1, false, 服务器地址) Option(p, port, Option::ArgRequired, 8080, false, 服务器端口) Option(c, count, Option::ArgRequired, 5, false, 测试次数) Option(t, timeout, Option::ArgRequired, 3000, false, 超时时间(毫秒)) Option(v, verbose, Option::ArgNone, nullptr, false, 显示详细输出) Option(h, help, Option::ArgNone, nullptr, false, 显示帮助信息); } virtual void operator()(int argc, char *argv[]) override { try { CMD::operator()(argc, argv); if (hasKey(help)) { showHelp(); return; } runNetworkTest(); } catch (std::exception ex) { std::cerr 错误: ex.what() std::endl; showHelp(); } } private: void runNetworkTest() { string server (*this)[server]; uint16_t port (*this)[port]; int count (*this)[count]; int timeout (*this)[timeout]; bool verbose hasKey(verbose); if (verbose) { std::cout 开始网络测试... std::endl; std::cout 服务器: server : port std::endl; std::cout 测试次数: count std::endl; std::cout 超时时间: timeout ms std::endl; } // 模拟网络测试 int success 0; int failed 0; vectorint latencies; for (int i 0; i count; i) { if (verbose) std::cout 测试 # (i1) ...; try { int latency simulateNetworkTest(server, port, timeout); latencies.push_back(latency); success; if (verbose) std::cout 成功! 延迟: latency ms std::endl; } catch (const std::exception ex) { failed; if (verbose) std::cout 失败: ex.what() std::endl; } } // 输出统计信息 printStatistics(success, failed, latencies); } int simulateNetworkTest(const string server, uint16_t port, int timeout) { // 模拟网络延迟(0-100ms) int latency rand() % 100; // 模拟10%的失败率 if (rand() % 10 0) { throw std::runtime_error(连接超时); } return latency; } void printStatistics(int success, int failed, const vectorint latencies) { std::cout \n 测试结果 std::endl; std::cout 成功: success 失败: failed std::endl; if (!latencies.empty()) { int total accumulate(latencies.begin(), latencies.end(), 0); int avg total / latencies.size(); int max *max_element(latencies.begin(), latencies.end()); int min *min_element(latencies.begin(), latencies.end()); std::cout 延迟统计(ms): std::endl; std::cout 平均: avg 最大: max 最小: min std::endl; } } void showHelp() { std::cout 用法: netcheck [选项] std::endl; std::cout 选项: std::endl; _parser.traverse([this](const Option opt) { std::cout - opt._shortOpt -- opt._longOpt (opt._argType ! Option::ArgNone ? 参数 : ) 默认: (opt._defaultValue ? *opt._defaultValue : 无) : opt._des std::endl; }); } };这个实现展示了带默认值命令的特点在Option构造函数中指定默认值当用户不提供相应参数时自动使用默认值提供完整的统计信息输出包含模拟网络测试的逻辑帮助信息中显示各参数的默认值6. 命令注册与集成测试完成三个命令的实现后我们需要将它们注册到系统中并进行集成测试int main(int argc, char *argv[]) { // 注册所有命令 CMDRegister::Instance().registCMD(sysinfo, std::make_sharedSysInfoCMD()); CMDRegister::Instance().registCMD(fileproc, std::make_sharedFileProcCMD()); CMDRegister::Instance().registCMD(netcheck, std::make_sharedNetCheckCMD()); // 注册help命令 CMDRegister::Instance().registCMD(help, std::make_sharedDefaultCMD(help)); if (argc 2) { std::cerr 请指定命令可用命令: ; CMDRegister::Instance().printHelp(); return -1; } try { // 执行命令 CMDRegister::Instance()(argv[1], argc - 1, argv 1); } catch (std::exception ex) { std::cerr 错误: ex.what() std::endl; CMDRegister::Instance().printHelp(); return -1; } return 0; }编译并测试各种命令组合# 编译项目 cmake -B build cmake --build build # 测试sysinfo命令 ./build/cmd_demo sysinfo ./build/cmd_demo sysinfo -h # 测试fileproc命令 ./build/cmd_demo fileproc -i input.txt ./build/cmd_demo fileproc -i input.txt -o output.txt -r -v ./build/cmd_demo fileproc -h # 测试netcheck命令 ./build/cmd_demo netcheck ./build/cmd_demo netcheck -s 192.168.1.100 -p 80 -c 10 -t 5000 -v ./build/cmd_demo netcheck -h # 测试help命令 ./build/cmd_demo help7. 高级技巧与最佳实践在实际项目中使用ZLToolKit的CMD模块时以下技巧和最佳实践可以帮助提高开发效率和代码质量命令分组管理对于大型项目可以将相关命令分组到不同命名空间namespace NetworkCommands { class PingCMD : public CMD { /*...*/ }; class TraceCMD : public CMD { /*...*/ }; }参数验证实现严格的参数验证逻辑void validatePort(int port) { if (port 1 || port 65535) { throw std::invalid_argument(端口号必须在1-65535范围内); } }日志记录集成ZLToolKit的日志系统void processCommand() { DebugL 开始处理命令; try { // 命令逻辑... InfoL 命令执行成功; } catch (const std::exception ex) { ErrorL 命令执行失败: ex.what(); throw; } }性能优化对于耗时命令使用线程池异步执行void executeAsync() { auto task []() { // 耗时操作... }; auto poller EventPollerPool::Instance().getPoller(); poller-async(std::move(task)); }单元测试为每个命令编写测试用例TEST_CASE(FileProc命令测试) { FileProcCMD cmd; const char *argv[] {fileproc, -i, test.txt, -o, output.txt}; cmd(5, const_castchar**(argv)); REQUIRE(File::exists(output.txt)); }国际化支持为帮助信息和输出添加多语言支持#ifdef LANGUAGE_CN #define HELP_TEXT 帮助信息 #else #define HELP_TEXT Help information #endif通过本文的实战演示我们全面掌握了使用ZLToolKit CMD模块开发命令行工具的方法。从简单的无参数命令到复杂的带默认值命令ZLToolKit提供了灵活而强大的API能够满足各种命令行工具的开发需求。在实际项目中可以根据具体业务需求扩展这些基础命令构建功能丰富的命令行工具集。