这是制作文本协议解释程序的正确方法吗?

时间:2014-12-17 15:40:51

标签: parsing boost text split protocols

我正在尝试制作解析文本协议的程序 (我选择了文本协议,因为我听说二进制数据包解析比较困难) 目前,命令和参数确实很少。

  

每个数据包可以通过分隔符(&#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'拆分包。 我只使用了'如果'否则,如果','其他'调用正确的任务相应的行动'和'参数'。

但我会添加更多动作和参数。 但是按照这个速度,我无法调试或修改代码,因为代码的复杂性将更加严重。

制作协议翻译程序是不对的?

如果您了解更好的方式,请与我分享。

1 个答案:

答案 0 :(得分:3)

是。更好的方法是创建一个语法,为它编写一个解析器并解析成AST(抽象语法树,或者只是数据包的强类型表示)。

精神语法如下:

  1. 我总是从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>;
    }
    

    为了简单起见,我

    • 假设参数(模式/延迟)将具有数字或字符串值。
    • GETSET个请求使用了相同的数据包定义(GET个请求只列出了所列参数的nil个值。
  2. 接下来,我们使用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) 的可维护代码。

  3. 最后,因为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

  4. ,那会更麻烦

    <强> 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;