我正在尝试制作解析文本协议的程序 (我选择了文本协议,因为我听说二进制数据包解析比较困难) 目前,命令和参数确实很少。
每个数据包可以通过分隔符(&#39 ;;')来分割 [packet1]; [packet2];
让我们打破packet1。
[动作],[参数1],[参数2],...;
动作:[SET]
参数:[DELAY]
如果您发送" SET,DELAY,300;"到服务器,
服务器将改变延迟'参数并发送" SET,DELAY,300;"对客户。
行动:[GET]
参数:[DELAY] [MODE]
如果您发送" GET,DELAY,MODE;"到服务器,
服务器将发送" GET,DELAY,300,MODE,2;"对客户。
我以任何方式取得成功。
(The code is here。因为它很长,我无法在这里添加它。
但即使只有少数参数和动作,代码也很长而复杂。
我使用了' boost :: algorithm :: split'拆分包。 我只使用了'如果'否则,如果','其他'调用正确的任务相应的行动'和'参数'。
但我会添加更多动作和参数。 但是按照这个速度,我无法调试或修改代码,因为代码的复杂性将更加严重。
制作协议翻译程序是不对的?
如果您了解更好的方式,请与我分享。
答案 0 :(得分:3)
是。更好的方法是创建一个语法,为它编写一个解析器并解析成AST(抽象语法树,或者只是数据包的强类型表示)。
精神语法如下:
我总是从AST类型开始:
namespace ast {
struct nil {
friend std::ostream& operator<<(std::ostream& os, nil) { return os << "<nil>"; }
};
using value = boost::variant<nil, double, std::string>;
struct parameter {
std::string _key;
value _val;
};
enum class action {
get,
set,
};
using parameters = std::vector<parameter>;
struct packet {
action _action;
parameters _params;
};
using packets = std::vector<packet>;
}
为了简单起见,我
GET
和SET
个请求使用了相同的数据包定义(GET
个请求只列出了所列参数的nil
个值。接下来,我们使用Boost Spirit Qi定义语法:
template <typename It, typename Skipper=qi::space_type>
struct grammar : qi::grammar<It, ast::packets(), Skipper> {
grammar():grammar::base_type(start) {
using qi::raw;
using qi::no_case;
param_key_.add
("delay")
("mode");
start = *(packet_ >> ';');
packet_ =
(no_case["get"] >> qi::attr(ast::action::get) >> *(',' >> get_param_))
| (no_case["set"] >> qi::attr(ast::action::set) >> *(',' >> set_param_))
;
get_param_ = raw[no_case[param_key_]] >> qi::attr(ast::nil());
set_param_ = raw[no_case[param_key_]] >> "," >> value_;
value_ = qi::double_ | string_;
string_ = '"' >> *~qi::char_('"') >> '"';
BOOST_SPIRIT_DEBUG_NODES((start)(packet_)(get_param_)(set_param_)(value_)(string_))
}
// ... field declarations
};
这里有一点学习曲线,但需要注意的一点是,可以创建 also debuggable (see here for BOOST_SPIRIT_DEBUG
enabled output) 的可维护代码。
最后,因为AST很简单,我们可以制作一个假请求处理器,它使用请求上下文(在本例中是一个包含参数当前值的map)来实际处理请求:
struct request_context {
std::map<std::string, ast::value> properties;
request_context()
: properties { { "MODE", 2 }, { "DELAY", 300 } } // defaults
{
}
boost::optional<ast::packet> process_request(ast::packet packet) {
switch (packet._action) {
case ast::action::get:
for(auto& param : packet._params) {
param._val = properties[param._key];
}
return packet;
case ast::action::set:
for(auto& param : packet._params) {
std::cout << "DEBUG: setting property '" << param._key << "' to value '" << param._val << "'\n";
properties[param._key] = param._val;
}
return boost::none;
default:
throw std::runtime_error("bad packet"); // TODO proper exception type
};
}
};
想象一下,如果您将其与解析代码混合在一起,或者所有内容 stringly typed
<强> Live On Coliru 强>
//#define BOOST_SPIRIT_DEBUG
#include <boost/fusion/adapted/struct.hpp>
#include <boost/spirit/include/qi.hpp>
#include <map>
namespace qi = boost::spirit::qi;
namespace ast {
struct nil {
friend std::ostream& operator<<(std::ostream& os, nil) { return os << "<nil>"; }
};
using value = boost::variant<nil, double, std::string>;
struct parameter {
std::string _key;
value _val;
};
enum class action {
get,
set,
};
using parameters = std::vector<parameter>;
struct packet {
action _action;
parameters _params;
};
using packets = std::vector<packet>;
static std::ostream& operator<<(std::ostream& os, action a) {
switch(a) {
case action::get: return os << "GET";
case action::set: return os << "SET";
}
return os << "(other)";
}
}
BOOST_FUSION_ADAPT_STRUCT(ast::parameter,(std::string,_key)(ast::value,_val))
BOOST_FUSION_ADAPT_STRUCT(ast::packet,(ast::action,_action)(ast::parameters,_params))
template <typename It, typename Skipper=qi::space_type>
struct grammar : qi::grammar<It, ast::packets(), Skipper> {
grammar():grammar::base_type(start) {
using qi::raw;
using qi::no_case;
param_key_.add
("delay")
("mode");
start = *(packet_ >> ';');
packet_ =
(no_case["get"] >> qi::attr(ast::action::get) >> *(',' >> get_param_))
| (no_case["set"] >> qi::attr(ast::action::set) >> *(',' >> set_param_))
;
get_param_ = raw[no_case[param_key_]] >> qi::attr(ast::nil());
set_param_ = raw[no_case[param_key_]] >> "," >> value_;
value_ = qi::double_ | string_;
string_ = '"' >> *~qi::char_('"') >> '"';
BOOST_SPIRIT_DEBUG_NODES((start)(packet_)(get_param_)(set_param_)(value_)(string_))
}
private:
qi::symbols<char, std::string> param_key_;
qi::rule<It, ast::parameter(), Skipper> set_param_, get_param_;
qi::rule<It, ast::packets(), Skipper> start;
qi::rule<It, ast::packet(), Skipper> packet_;
qi::rule<It, ast::value(), Skipper> value_;
qi::rule<It, std::string()> string_;
};
struct request_context {
std::map<std::string, ast::value> properties;
request_context()
: properties { { "MODE", 2 }, { "DELAY", 300 } } // defaults
{
}
boost::optional<ast::packet> process_request(ast::packet packet) {
switch (packet._action) {
case ast::action::get:
for(auto& param : packet._params) {
param._val = properties[param._key];
}
return packet;
case ast::action::set:
for(auto& param : packet._params) {
std::cout << "DEBUG: setting property '" << param._key << "' to value '" << param._val << "'\n";
properties[param._key] = param._val;
}
return boost::none;
default:
throw std::runtime_error("bad packet"); // TODO proper exception type
};
}
};
int main()
{
std::string const input =
"GET,DELAY,MODE;"
"SET,DELAY,0,MODE,\"we can have string values too\";GET,MODE;SET,MODE,42;GET,MODE,DELAY;";
using It = std::string::const_iterator;
It f(input.begin()), l(input.end());
grammar<It> p;
ast::packets parsed;
bool ok = qi::phrase_parse(f,l,p,qi::space,parsed);
if (ok) {
std::cout << parsed.size() << " packets successfully parsed\n";
request_context ctx;
for(auto& packet : parsed)
{
auto response = ctx.process_request(packet);
if (response) {
std::cout << "response: " << response->_action;
for(auto& kv : packet._params) {
std::cout << "," << kv._key << "," << kv._val;
}
std::cout << ";\n";
}
}
} else {
std::cout << "Parse error\n";
}
if (f!=l)
std::cout << "Remaining unparsed input: '" << std::string(f,l) << "'\n";
}
打印:
5 packets successfully parsed
response: GET,DELAY,300,MODE,2;
DEBUG: setting property 'DELAY' to value '0'
DEBUG: setting property 'MODE' to value 'we can have string values too'
response: GET,MODE,we can have string values too;
DEBUG: setting property 'MODE' to value '42'
response: GET,MODE,42,DELAY,0;