系列文章目录
《ZLToolKit源码学习笔记》(1)VS2019源码编译
《ZLToolKit源码学习笔记》(2)工具模块之日志功能分析
《ZLToolKit源码学习笔记》(3)工具模块之终端命令解析(本文)
《ZLToolKit源码学习笔记》(4)工具模块之消息广播器
《ZLToolKit源码学习笔记》(6)线程模块之整体框架概述
《ZLToolKit源码学习笔记》(7)线程模块之线程池组件:任务队列与线程组
《ZLToolKit源码学习笔记》(8)线程模块之线程负载计算器
《ZLToolKit源码学习笔记》(9)线程模块之任务执行器
《ZLToolKit源码学习笔记》(11)线程模块之工作线程池WorkThreadPool
《ZLToolKit源码学习笔记》(12)事件轮询模块之整体框架概述
《ZLToolKit源码学习笔记》(13)事件轮询模块之管道的简单封装
《ZLToolKit源码学习笔记》(14)事件轮询模块之定时器
《ZLToolKit源码学习笔记》(15)事件轮询模块之事件轮询器EventPoller
《ZLToolKit源码学习笔记》(16)网络模块之整体框架概述
《ZLToolKit源码学习笔记》(17)网络模块之基础接口封装类SockUtil
《ZLToolKit源码学习笔记》(18)网络模块之Buffer缓存
《ZLToolKit源码学习笔记》(19)网络模块之套接字封装
《ZLToolKit源码学习笔记》(20)网络模块之TcpServer
《ZLToolKit源码学习笔记》(21)网络模块之TcpClient与Session
《ZLToolKit源码学习笔记》(22)网络模块之UdpServer
目录
前言
终端命令解析相关功能对应cmd.h和cmd.cpp文件。
一、使用
使用示例可以参见test_pingpong.cpp和test_shell.cpp,实现自己的命令时,继承CMD类即可。test_pingpong示例了单个命令的使用,test_shell示例了多条命令的使用。
如test_shell.cpp运行后的程序使用示例:
> 欢迎进入命令模式,你可以输入"help"命令获取帮助
> help
> help -h
-h --help 无参 默认:null 选填 打印此信息
-c --cmd 无参 默认:null 选填 列出所有命令
> help --help
-h --help 无参 默认:null 选填 打印此信息
-c --cmd 无参 默认:null 选填 列出所有命令
> http --help
-h --help 无参 默认:null 选填 打印此信息
-s --server 有参 默认:www.baidu.com:80 选填 tcp服务器地址,以冒号分隔端口号
-d --disconnect 无参 默认:null 选填 是否断开连接
-c --connect 无参 默认:null 选填 发起tcp connect操作
-t --time_out 有参 默认:3 选填 连接超时间
-m --method 有参 默认:GET 选填 HTTP方法,譬如GET、POST
-p --path 有参 默认:/index.html 选填 HTTP url路径
-C --commit 无参 默认:null 选填 提交HTTP请求
>
二、源码结构分析
1、类图
用户使用时,继承CMD,实现自己的命令类,通过CMD成员对象parser(OptionParser类)定义该命令支持的所有选项。CMD本身继承自map,每次解析后,CMD中存储当前用户输入的选项信息以及该命令具有默认值的选项信息。
实际的选项信息,存储在Option中,一个选项对应一个Option实例。OptionParser中存储了当前命令所支持的所有选项,std::map<Option>。
如果需要支持多条命令,如test_shell.cpp,则通过CMDRegister类来管理多个CMD实例。
2、各模块分析
2.1、Option
选项类,存储一条选项相关信息。包括短选项名、长选项名、选项额外参数类型(无参、有参、可选参)、选项额外参数默认值、该选项是否必须给额外参数赋值、选项描述信息、选项对应回调。
Option(char shortOpt,
const char *longOpt,
enum ArgType argType,
const char *defaultValue,
bool mustExist,//该参数是否必须存在
const char *des,
const OptionHandler &cb);
/* 该选项被解析时,执行的回调 */
bool operator()(const std::shared_ptr<ostream> &stream, const string &arg){
return _cb ? _cb(stream,arg): true;
}
char _shortOpt:短选项名
string _longOpt:长选项名
std::shared_ptr<string> _defaultValue:
enum ArgType _argType:该选项是否带有参数;
ArgNone:不带参数,如--help;
ArgRequired:必须带有参数,如--prefix /root;
ArgOptional:参数是可选的。如--help或--prefix /root;
string _des:选项描述信息;
OptionHandler _cb:选项对应回调;
bool _mustExist:该选项是否必须给额外参数赋值,即选项后是否必须跟一个值,如tail -n 2 hello.c,显示最后两行,-n后跟一个2。另外,如果指明必须给额外参数赋值,但是却有默认值,也可以不用赋值,此时将使用默认值。
Option使用示例如下。
Option('T', "type", Option::ArgRequired, "1", true, "应用程序模式,0:传统模式,1:shell模式", nullptr);
Option('l', "listen", Option::ArgRequired, "10000", false, "服务器模式:监听端口",nullptr);
Option('c', "count", Option::ArgRequired, to_string(10).data(),false, "客户端模式:测试客户端个数", nullptr);
类比linux下ls命令,短选项-a,长选项--all。
[qinshixiao@localhost ~]$ ls --help
用法:ls [选项]... [文件]...
List information about the FILEs (the current directory by default).
Sort entries alphabetically if none of -cftuvSUX nor --sort is specified.
Mandatory arguments to long options are mandato
-a, --all 不隐藏任何以. 开始的项目
-A, --almost-all 列出除. 及.. 以外的任何项目
2.2、OptionParser
选项解析器。每个命令(CMD)都有一个选项解析器成员对象,OptionParser内部使用map存储了该命令支持的所有选项信息。
程序运行后,用户输入的命令信息最终会通过该类进行解析,在map中查找是否存在。
//构造函数,设置选项解析完成后执行的回调,以及是否允许非用户输入且没有参数的选项被解析到CMD中,具体使用参见operator ()
OptionParser(const OptionCompleted &cb = nullptr,bool enableEmptyArgs = true);
//添加选项
OptionParser &operator <<(Option &&option);
OptionParser &operator <<(const Option &option);
//删除选项
void delOption(const char *key);
//选项解析
void operator ()(mINI &allArg, int argc, char *argv[],const std::shared_ptr<ostream> &stream);
该类最主要的函数是operator()操作。
void OptionParser::operator ()(mINI &allArg, int argc, char *argv[],const std::shared_ptr<ostream> &stream) {
/*根据支持的选项,生成getopt_long的参数*/
vector<struct option> vec_longOpt;
string str_shortOpt;
do{
struct option tmp;
for (auto &pr : _map_options) {
auto &opt = pr.second;
//long opt
tmp.name = (char *) opt._longOpt.data();
tmp.has_arg = opt._argType;
tmp.flag = NULL;
tmp.val = pr.first;
vec_longOpt.emplace_back(tmp);
//short opt
if (!opt._shortOpt) {
continue;
}
str_shortOpt.push_back(opt._shortOpt);
switch (opt._argType) {
case Option::ArgRequired:
str_shortOpt.append(":");
break;
case Option::ArgOptional:
str_shortOpt.append("::");
break;
default:
break;
}
}
tmp.flag = 0;
tmp.name = 0;
tmp.has_arg = 0;
tmp.val = 0;
vec_longOpt.emplace_back(tmp);
}while(0);
/*查找用户输入的选项是否支持,如果支持,加入到allArg中,并调用选项的()操作*/
lock_guard<mutex> lck(s_mtx_opt);
int index;
optind = 0;
opterr = 0;
while ((index = getopt_long(argc, argv, &str_shortOpt[0], &vec_longOpt[0],NULL)) != -1) {
stringstream ss;
ss << " 未识别的选项,输入\"-h\"获取帮助.";
if(index < 0xFF){
//短参数
auto it = _map_charIndex.find(index);
if(it == _map_charIndex.end()){
throw std::invalid_argument(ss.str());
}
index = it->second;
}
auto it = _map_options.find(index);
if(it == _map_options.end()){
throw std::invalid_argument(ss.str());
}
auto &opt = it->second;
auto pr = allArg.emplace(opt._longOpt, optarg ? optarg : "");
if (!opt(stream, pr.first->second)) {//用户输入的选项执行失败则退出,否则继续向下执行
return;
}
optarg = NULL;
}
/*将有默认值且非用户指定的选项,加入到allArg中,因为程序中后续可能要使用默认值
此时,allArg中包括了用户指定的选项以及具备默认值但非用户指定的选项*/
for (auto &pr : _map_options) {
if(pr.second._defaultValue && allArg.find(pr.second._longOpt) == allArg.end()){
//有默认值,赋值默认值
allArg.emplace(pr.second._longOpt,*pr.second._defaultValue);
}
}
/*如果选项的值是必填的,但该选项不在allArg中,说明出错了。
也就是,规定该选项后边必须有值,但是用户没有指定该选项,且该选项也没有默认值,那么这个选项肯定无法执行了 */
for (auto &pr : _map_options) {
if(pr.second._mustExist){
if(allArg.find(pr.second._longOpt) == allArg.end() ){
stringstream ss;
ss << " 参数\"" << pr.second._longOpt << "\"必须提供,输入\"-h\"选项获取帮助";
throw std::invalid_argument(ss.str());
}
}
}
/*没有解析到用户输入的选项,也没有解析到具有默认值的选项。allArg为空*/
if(allArg.empty() && _map_options.size() > 1 && !_enableEmptyArgs){
_helper(stream,"");
return;
}
/*执行选项解析完成后的回调*/
if (_onCompleted) {
_onCompleted(stream, allArg);
}
}
2.3、
CMD
命令类,继承自map,每次调用operator()操作后,CMD本身存储的是用户当前输入的选项以及非用户输入但具有默认值的选项,而该命令支持的所有选项,存储在_parser中(OptionParser)的option Map中。
//选项解析,每次解析会先把CMD中存储的上一次的解析结果清空
void operator ()(int argc, char *argv[],const std::shared_ptr<ostream> &stream = nullptr);
//选项是否存在(包括解析到用户输入的以及非用户输入但是有默认值的选项)
bool hasKey(const char *key);
/* 分割选项值 */
vector<variant> splitedVal(const char *key,const char *delim= ":");
//删除选项
void delOption(const char *key);
2.4、CMDRegister
命令管理单例类。
//删除所有命令
void clear();
//注册命令
void registCMD(const char *name,const std::shared_ptr<CMD> &cmd);
/*删除命令*/
void unregistCMD(const char *name);
/*通过命令名称获取命令*/
std::shared_ptr<CMD> operator[](const char *name);
//通过命令名称解析选项
void operator()(const char *name,int argc,char *argv[],const std::shared_ptr<ostream> &stream = nullptr);
//打印当前所有命令信息
void printHelp(const std::shared_ptr<ostream> &streamTmp = nullptr);
/*通过用户输入信息解析命令*/
void operator()(const string &line,const std::shared_ptr<ostream> &stream = nullptr);