草庐IT

c++ - 更好的基于请求的协议(protocol)实现结构

coder 2023-05-03 原文

我正在使用一个协议(protocol),它基本上是一个基于 TCP 的请求和响应协议(protocol),类似于其他基于行的协议(protocol)(SMTP、HTTP 等)。

该协议(protocol)有大约 130 种不同的请求方法(例如登录、用户添加、用户更新、日志获取、文件信息、文件信息等)。所有这些方法都不能很好地映射到 HTTP 中使用的广泛方法(GET、POST、PUT 等)。如此广泛的方法会导致实际含义的一些无关紧要的扭曲。

但是协议(protocol)方法可以按类型分组(例如用户管理、文件管理、 session 管理等)。

当前服务器端实现使用 class Worker与方法 ReadRequest() (读取请求,由方法加参数列表组成),HandleRequest() (见下文)和 WriteResponse() (写入响应代码和实际响应数据)。
HandleRequest()将为实际请求方法调用一个函数 - 使用方法名称的哈希映射到指向实际处理程序的成员函数指针。

实际的处理程序是一个普通的成员函数,每个协议(protocol)方法都有一个:每个方法都验证其输入参数,执行它必须执行的任何操作并设置响应代码(成功是/否)和响应数据。

示例代码:

class Worker {
    typedef bool (Worker::*CommandHandler)();
    typedef std::map<UTF8String,CommandHandler> CommandHandlerMap;

    // handlers will be initialized once
    //   e.g. m_CommandHandlers["login"] = &Worker::Handle_LOGIN;
    static CommandHandlerMap m_CommandHandlers;

    bool HandleRequest() {
        CommandHandlerMap::const_iterator ihandler;
        if( (ihandler=m_CommandHandlers.find(m_CurRequest.instruction)) != m_CommandHandler.end() ) {
            // call actual handler
            return (this->*(ihandler->second))();
        }
        // error case:
        m_CurResponse.success = false;
        m_CurResponse.info = "unknown or invalid instruction";
        return true;
    }

    //...


    bool Handle_LOGIN() {
        const UTF8String username = m_CurRequest.parameters["username"];
        const UTF8String password = m_CurRequest.parameters["password"];

        // ....

        if( success ) {

            // initialize some state...
            m_Session.Init(...);
            m_LogHandle.Init(...);
            m_AuthHandle.Init(...);

            // set response data
            m_CurResponse.success = true;
            m_CurResponse.Write( "last_login", ... );
            m_CurResponse.Write( "whatever", ... );
        } else {
            m_CurResponse.Write( "error", "failed, because ..." );
        }
        return true;
    }


};

所以。问题是:我的 worker 类现在有大约 130 个“命令处理程序方法”。每个人都需要访问:
  • 请求参数
  • 响应对象(写入响应数据)
  • 不同的其他 session 本地对象(如数据库句柄、授权/权限查询句柄、日志记录、服务器各种子系统的句柄等)

  • 更好地构建这些命令处理程序方法的好策略是什么?

    一个想法是每个命令处理程序有一个类,并使用对请求、响应对象等的引用对其进行初始化 - 但恕我直言 Not Acceptable 开销(实际上,它会为对 处理程序的所有内容的任何单一访问添加间接需要 :请求、响应、 session 对象……)。如果它能提供实际优势,这是可以接受的。然而,这听起来不太合理:
    class HandlerBase {
    protected:
        Request &request;
        Response &response;
        Session &session;
        DBHandle &db;
        FooHandle &foo;
        // ...
    public:
        HandlerBase( Request &req, Response &rsp, Session &s, ... )
        : request(req), response(rsp), session(s), ...
        {}
        //...
        virtual bool Handle() = 0;
    };
    
    class LoginHandler : public HandlerBase {
    public:
        LoginHandler( Request &req, Response &rsp, Session &s, ... )
        : HandlerBase(req,rsp,s,..)
        {}
        //...
        virtual bool Handle() {
            // actual code for handling "login" request ...
        }
    };
    

    好的,HandlerBase 可以只获取对工作对象本身的引用(或指针)(而不是对请求、响应等的引用)。但这也会增加另一个间接性(this->worker->session 而不是 this->session)。这种间接性是可以的,如果它毕竟会带来一些优势。

    关于整体架构的一些信息

    worker 对象代表一个单独的工作线程,用于与某个客户端的实际 TCP 连接。每个线程(因此,每个工作线程)都需要自己的数据库句柄、授权句柄等。这些“句柄”是每个线程对象,允许访问服务器的某个子系统。

    整个架构基于某种依赖注入(inject):例如要创建 session 对象,必须向 session 构造函数提供“数据库句柄”。然后 session 对象使用这个数据库句柄来访问数据库。它永远不会调用全局代码或使用单例。因此,每个线程都可以不受干扰地独立运行。

    但代价是,工作人员及其命令处理程序必须通过此类特定于线程的句柄访问系统的任何数据或其他代码,而不是仅仅调用单例对象。这些句柄定义了它的执行上下文。

    总结和澄清:我的实际问题

    我正在寻找当前(“具有大量处理程序方法列表的工作对象”)解决方案的优雅替代方案:它应该是可维护的,开销低并且不需要编写太多的胶水代码。此外,它必须仍然允许每个方法控制其执行的非常不同的方面(这意味着:如果一个方法“super flurry foo”想要在满月时失败,那么该实现必须有可能这样做) .这也意味着,我不希望在我的代码的这个架构层(它存在于我的代码的不同层)上有任何类型的实体抽象(创建/读取/更新/删除 XFoo 类型)。这个架构层是纯协议(protocol),没有别的。

    最后肯定会妥协,但我对任何想法都感兴趣!

    AAA 奖励:具有可互换协议(protocol)实现的解决方案(而不仅仅是当前的 class Worker ,它负责解析请求和写入响应)。可能有一个可互换的 class ProtocolSyntax ,处理那些协议(protocol)语法细节,但仍然使用我们新的 Shiny 的结构化命令处理程序。

    最佳答案

    你已经有了大部分正确的想法,这就是我将如何进行。

    让我们从您的第二个问题开始:可互换协议(protocol)。如果你有通用的请求和响应对象,你可以有一个接口(interface)来读取请求和写入响应:

    class Protocol {
      virtual Request *readRequest() = 0;
      virtual void writeResponse(Response *response) = 0;
    }
    

    你可以有一个名为 HttpProtocol 的实现例如。

    至于您的命令处理程序,“每个命令处理程序一个类”是正确的方法:
    class Command {
      virtual void execute(Request *request, Response *response, Session *session) = 0;
    }
    

    请注意,我将所有常见的 session 句柄(DB、Foo 等)汇总到一个对象中,而不是传递一大堆参数。还使用这些方法参数而不是构造函数参数意味着您只需要每个命令的一个实例。

    接下来,您将有一个 CommandFactory其中包含命令名称到命令对象的映射:
    class CommandFactory {
      std::map<UTF8String, Command *> handlers;
    
      Command *getCommand(const UTF8String &name) {
        return handlers[name];
      }
    }
    

    如果您已完成所有这些操作,Worker变得非常薄,只是协调一切:
    class Worker {
      Protocol *protocol;
      CommandFactory *commandFactory;
      Session *session;
    
      void handleRequest() {
        Request *request = protocol->readRequest();
        Response response;
    
        Command *command = commandFactory->getCommand(request->getCommandName());
        command->execute(request, &response, session);
    
        protocol->writeResponse(&response);
      }
    }
    

    关于c++ - 更好的基于请求的协议(protocol)实现结构,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/11856074/

    有关c++ - 更好的基于请求的协议(protocol)实现结构的更多相关文章

    1. ruby - 使用 ruby​​ 将 HTML 转换为纯文本并维护结构/格式 - 2

      我想将html转换为纯文本。不过,我不想只删除标签,我想智能地保留尽可能多的格式。为插入换行符标签,检测段落并格式化它们等。输入非常简单,通常是格式良好的html(不是整个文档,只是一堆内容,通常没有anchor或图像)。我可以将几个正则表达式放在一起,让我达到80%,但我认为可能有一些现有的解决方案更智能。 最佳答案 首先,不要尝试为此使用正则表达式。很有可能你会想出一个脆弱/脆弱的解决方案,它会随着HTML的变化而崩溃,或者很难管理和维护。您可以使用Nokogiri快速解析HTML并提取文本:require'nokogiri'h

    2. ruby-on-rails - 如何优雅地重启 thin + nginx? - 2

      我的瘦服务器配置了nginx,我的ROR应用程序正在它们上运行。在我发布代码更新时运行thinrestart会给我的应用程序带来一些停机时间。我试图弄清楚如何优雅地重启正在运行的Thin实例,但找不到好的解决方案。有没有人能做到这一点? 最佳答案 #Restartjustthethinserverdescribedbythatconfigsudothin-C/etc/thin/mysite.ymlrestartNginx将继续运行并代理请求。如果您将Nginx设置为使用多个上游服务器,例如server{listen80;server

    3. ruby-on-rails - Rails HTML 请求渲染 JSON - 2

      在我的Controller中,我通过以下方式在我的index方法中支持HTML和JSON:respond_todo|format|format.htmlformat.json{renderjson:@user}end在浏览器中拉起它时,它会自然地以HTML呈现。但是,当我对/user资源进行内容类型为application/json的curl调用时(因为它是索引方法),我仍然将HTML作为响应。如何获取JSON作为响应?我还需要说明什么? 最佳答案 您应该将.json附加到请求的url,提供的格式在routes.rb的路径中定义。这

    4. ruby - 如何根据特征实现 FactoryGirl 的条件行为 - 2

      我有一个用户工厂。我希望默认情况下确认用户。但是鉴于unconfirmed特征,我不希望它们被确认。虽然我有一个基于实现细节而不是抽象的工作实现,但我想知道如何正确地做到这一点。factory:userdoafter(:create)do|user,evaluator|#unwantedimplementationdetailshereunlessFactoryGirl.factories[:user].defined_traits.map(&:name).include?(:unconfirmed)user.confirm!endendtrait:unconfirmeddoenden

    5. ruby-on-rails - 更好的替代方法 try( :output). try( :data). try( :name)? - 2

      “输出”是一个序列化的OpenStruct。定义标题try(:output).try(:data).try(:title)结束什么会更好?:) 最佳答案 或者只是这样:deftitleoutput.data.titlerescuenilend 关于ruby-on-rails-更好的替代方法try(:output).try(:data).try(:name)?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.c

    6. jquery - 我的 jquery AJAX POST 请求无需发送 Authenticity Token (Rails) - 2

      rails中是否有任何规定允许站点的所有AJAXPOST请求在没有authenticity_token的情况下通过?我有一个调用Controller方法的JqueryPOSTajax调用,但我没有在其中放置任何真实性代码,但调用成功。我的ApplicationController确实有'request_forgery_protection'并且我已经改变了config.action_controller.consider_all_requests_local在我的environments/development.rb中为false我还搜索了我的代码以确保我没有重载ajaxSend来发送

    7. ruby - 是否有用于序列化和反序列化各种格式的对象层次结构的模式? - 2

      给定一个复杂的对象层次结构,幸运的是它不包含循环引用,我如何实现支持各种格式的序列化?我不是来讨论实际实现的。相反,我正在寻找可能会派上用场的设计模式提示。更准确地说:我正在使用Ruby,我想解析XML和JSON数据以构建复杂的对象层次结构。此外,应该可以将该层次结构序列化为JSON、XML和可能的HTML。我可以为此使用Builder模式吗?在任何提到的情况下,我都有某种结构化数据-无论是在内存中还是文本中-我想用它来构建其他东西。我认为将序列化逻辑与实际业务逻辑分开会很好,这样我以后就可以轻松支持多种XML格式。 最佳答案 我最

    8. ruby-on-rails - 使用 ruby​​ 将多个实例变量转换为散列的更好方法? - 2

      我收到格式为的回复#我需要将其转换为哈希值(针对活跃商家)。目前我正在遍历变量并执行此操作:response.instance_variables.eachdo|r|my_hash.merge!(r.to_s.delete("@").intern=>response.instance_eval(r.to_s.delete("@")))end这有效,它将生成{:first="charlie",:last=>"kelly"},但它似乎有点hacky和不稳定。有更好的方法吗?编辑:我刚刚意识到我可以使用instance_variable_get作为该等式的第二部分,但这仍然是主要问题。

    9. 叮咚买菜基于 Apache Doris 统一 OLAP 引擎的应用实践 - 2

      导读:随着叮咚买菜业务的发展,不同的业务场景对数据分析提出了不同的需求,他们希望引入一款实时OLAP数据库,构建一个灵活的多维实时查询和分析的平台,统一数据的接入和查询方案,解决各业务线对数据高效实时查询和精细化运营的需求。经过调研选型,最终引入ApacheDoris作为最终的OLAP分析引擎,Doris作为核心的OLAP引擎支持复杂地分析操作、提供多维的数据视图,在叮咚买菜数十个业务场景中广泛应用。作者|叮咚买菜资深数据工程师韩青叮咚买菜创立于2017年5月,是一家专注美好食物的创业公司。叮咚买菜专注吃的事业,为满足更多人“想吃什么”而努力,通过美好食材的供应、美好滋味的开发以及美食品牌的孵

    10. 华为OD机试用Python实现 -【明明的随机数】 2023Q1A - 2

      华为OD机试题本篇题目:明明的随机数题目输入描述输出描述:示例1输入输出说明代码编写思路最近更新的博客华为od2023|什么是华为od,od薪资待遇,od机试题清单华为OD机试真题大全,用Python解华为机试题|机试宝典【华为OD机试】全流程解析+经验分享,题型分享,防作弊指南华为o

    随机推荐