命令解析其实应用到很多地方,常用的例如:
- 管理命令:GM(game manager),直播管理命令;一般是运营,管理人员,管理场景,管理房间等使用。
- 调试命令:例如实时查看进程信息,机器资源使用情况,打印详细日志等等。
- 权限命令:例如实时开通白名单/黑名单,给用户分配临时权限等等。
- 调度命令:人为实时更改函数调度,进程调度,机器调度等,流量分发、流量限制等,服务升降级等等。
作者云课堂:http://m.study.163.com/provider/480000001930416/index.htm?share=2&shareId=480000001930416
命令的应用场景,好处,无须多讲。下面说说这个命令解析的格式,大致跟mysql client 连接差不多:
mysql -h test.command.com -P 6379 -u root -p
当然,有一些更简单的一些处理方法,例如:直接根据空格来进行参数分割。比如下面这个游戏gm命令,增加类型为1的货币1000。
addmoney 1 1000
这样看起来很简单,很清晰。但是,如果操作这个命令,需要加上操作日志呢?
addmoney 1 1000 "重启 补偿"
这时候以空格来分割就有问题了。因为日志字符串中存在空格。当然有些东西,你可以通过妥协来解决。再例如,需要增加一个新命令,减少货币。
submoney 1 1000
其实从货币的角度,增加,减少本质只是对同种事物的不同操作,很多时候,我们设计代码的时候,就会设计成
money 1 1 1000 // 增加 类型1 1000
money 2 1 1000 // 减小 类型1 1000
当然,这仅仅是增加、减少;还有货币转换、货币清空、货币转移等等。如果用同一个命令money,根据这种格式,扩展性就会显得很无力。
所以,这里实现了开头所说的格式,虽然看起来相对并不太人性化。但是,兼容性,扩展性,是好于空格分割的。
接下来,上代码,核心入口函数,就是parseArgs。
#include <iostream>
#include <sstream>
using namespace std;
struct ArgInfo
{
void clear()
{
sOpt.clear();
sVal.clear();
}
// 值能为空,键不能为空
inline bool empty()
{
return sOpt.empty();
}
string sOpt;
string sVal;
};
enum
{
IN_VALUE = 1,
AFTER_VALUE,
};
#define LOG() cout
inline void skipSpace(const char *sArgs, size_t lArgsLen, size_t &i)
{
for (; i < lArgsLen; ++i)
{
switch (sArgs[i])
{
case ' ':
case '\t':
break;
default:
return;
}
}
return;
}
bool parseOpt(const char *sArgs, size_t lArgsLen, size_t &i)
{
for (size_t lOld = i; i < lArgsLen; ++i)
{
switch (sArgs[i])
{
case ' ':
case '\t':
case '\n':
if (i == lOld) return false;
return true;
case '\'':
case '\"':
case '-':
return false;
default:
break;
}
}
return true;
}
bool parseVal(const char *sArgs, size_t lArgsLen, size_t &i)
{
for (size_t lOld = i; i < lArgsLen; ++i)
{
switch (sArgs[i])
{
case ' ':
case '\t':
case '\n':
if (i == lOld) return false;
return true;
case '-':
return false;
default:
break;
}
}
return true;
}
bool parseQuotesVal(const char *sArgs, size_t lArgsLen, size_t &i)
{
for (; i < lArgsLen; ++i)
{
switch (sArgs[i])
{
case '\"':
return true;
case '\\':
++i;
break;
default:
break;
}
}
return false;
}
bool parseArgs(const string &sArgs, vector<ArgInfo> &vArgs)
{
if (sArgs.empty()) return true;
ArgInfo tInfo;
size_t i = 0;
skipSpace(sArgs.c_str(), sArgs.size(), i);
while (i < sArgs.size())
{
size_t lEnd = i;
const char c = sArgs[i];
if ( c == '-')
{
++i;
if (!parseOpt(sArgs.c_str(), sArgs.size(), ++lEnd))
{
return false;
}
if (!tInfo.empty())
{
vArgs.emplace_back(tInfo);
tInfo.clear();
}
tInfo.sOpt = string(sArgs, i, lEnd - i);
}
else
{
if (tInfo.sOpt.empty())
{
return false;
}
if (c == '\"')
{
++i;
if (!parseQuotesVal(sArgs.c_str(), sArgs.size(), ++lEnd))
{
return false;
}
}
else
{
if (!parseVal(sArgs.c_str(), sArgs.size(), ++lEnd))
{
return false;
}
}
tInfo.sVal = string(sArgs, i, lEnd - i);
vArgs.emplace_back(tInfo);
tInfo.clear();
}
skipSpace(sArgs.c_str(), sArgs.size(), ++lEnd);
i = lEnd;
}
if (!tInfo.empty())
{
vArgs.emplace_back(tInfo);
}
return true;
}
void unitTest(const vector<string> &vTest)
{
for (auto & sTest : vTest)
{
vector<ArgInfo> vRet;
if (parseArgs(sTest, vRet))
{
ostringstream oss;
oss << " " << sTest << " => ";
for (auto & tRet : vRet)
{
oss << " " << tRet.sOpt << "#" << tRet.sVal;
}
LOG() << oss.str() << endl;
}
else
{
LOG() << " Format error:" << sTest << endl;
}
}
}
void testRightFormat()
{
vector<string> vTest = {
"",
"-abc",
"-a 123 -b abc",
"-a -b 123abc",
"-a 123abc -b",
"-a \"12'ab\" -b \"cd 34\"",
"-a -b \"123abc\"",
"-a \"123abc\" -b",
"-a \"123-abc\" -b",
"-a \"\\\"123-abc\\\"\" -b"
};
unitTest(vTest);
}
void testWrongFormat()
{
vector<string> vTest = {
"a",
"- a",
"-a- 123 -b abc",
"--a -b 123abc",
"-\"a 123abc -b",
"-\"a\" 123abc -b",
"-a \"12'ab -b \"cd 34\"",
"-a \"123abc\" -b ab cd",
"-a \"123abc\" -b ab-cd",
"-a 123 -b \"ab\"c"
};
unitTest(vTest);
}
int main()
{
testRightFormat();
//testWrongFormat();
return 0;
}