【问题标题】:Is it right way to make text-protocol interpretation program?制作文本协议解释程序是否正确?
【发布时间】:2014-12-17 15:40:51
【问题描述】:

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

每个数据包都可以用delimiter(';')进行分割
[包1];[包2];

让我们分解 packet1。

[动作],[参数1],[参数2],...;

动作:[SET]
参数:[延迟]

如果您发送“SET,DELAY,300;”到服务器,
服务器将更改“延迟”参数并发送“SET,DELAY,300;”给客户。

操作:[GET]
参数:[延迟] [模式]

如果您发送“GET,DELAY,MODE;”到服务器,
服务器将发送“GET,DELAY,300,MODE,2;”给客户。

我成功的任何方式。
(The code is here。因为太长了,这里不能加)
但是即使只有很少的参数和动作,代码也很长很复杂。

我使用 'boost::algorithm::split' 来拆分数据包。 而且我只使用了'if','else if','else'来调用正确的任务对应的'action'和'parameter'。

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

协议翻译程序的制作方法不对吗?

如果你知道更好的方法,请与我分享。

【问题讨论】:

    标签: parsing boost text split protocols


    【解决方案1】:

    是的。更好的方法是制作语法,为其编写解析器并解析为 AST(抽象语法树,或只是数据包的强类型表示)。

    这样的 Spirit 语法如下所示:

    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 很简单,我们可以制作一个假请求处理器,它使用请求上下文(在本例中是包含参数当前值的映射)来实际处理请求:

      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;
    

    【讨论】:

      猜你喜欢
      • 2020-04-29
      • 2013-07-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-02-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多