【问题标题】:How to implement nested protocols with boost::asio?如何使用 boost::asio 实现嵌套协议?
【发布时间】:2016-08-03 12:38:46
【问题描述】:

我正在尝试编写一个通过协议 B 处理协议 A 的服务器。

协议A是HTTP或RTSP,协议B是简单的二进制包序列:

[packet length][...encrypted packet data...]

所以我想使用这样的东西:

boost::asio::async_read_until(socket, inputBuffer, "\r\n\r\n", read_handler);

但是,不要使用socket,而是使用一些连接到协议 B 处理程序的伪套接字。

我有一些想法:

  1. 忘记async_readasync_read_until等,为A和B写两个状态机。

  2. 混合方法:async_read_* 用于协议 B,状态机用于 A。

  3. 制作内部代理服务器。

我不喜欢 (1) 和 (2) 因为

  • 很难将 A 与 B 解耦(我希望能够禁用协议 B)。

  • 丑。

(3) 看起来很丑 :-)

所以问题是:我该如何实现呢?

【问题讨论】:

    标签: c++ sockets boost architecture network-programming


    【解决方案1】:

    我过去曾做过类似您的回答 (2) 的事情 - 使用 async_read 调用首先读取标题,然后使用另一个 async_read 读取长度并将剩余的内容转发到手写状态机。但是我不一定会向您推荐 - 您因此可能会获得协议 B 的零拷贝 IO,但是当您知道后面总是有数据时,执行读取 4-8 字节标头的 IO 调用是非常浪费的。问题是你对 2 层的网络抽象会有所不同 - 所以你提到的解耦问题确实存在。

    使用固定长度的缓冲区,仅调用 async_read 然后使用 2 个嵌套状态机处理数据(就像您在答案 (1) 中基本上提出的那样)效果很好。您的每个状态机将简单地推送一些新接收到的数据(直接来自套接字或来自较低状态机)并处理它。这意味着A不会在这里耦合到B,因为如果输入/输出数据格式匹配,您可以直接从asio将数据推送到A状态机。

    与此类似的是在 Netty 和 Facebook Wangle 库中使用的模式,在这些库中,您的处理程序从管道中的较低处理程序获取数据,根据该输入执行其操作并将其解码的数据输出到下一个处理程序。这些处理程序可以是状态机,但根据协议的复杂性不一定必须是。您可以从中获得一些灵感,例如查看一些 Wangle 文档:https://github.com/facebook/wangle/blob/master/tutorial.md

    如果您不想将数据从一个协议处理程序推送到另一个协议处理程序,而是主动读取它(很可能以异步方式),您还可以自己设计一些接口(例如实现 async_read(... ) 方法或 PacketReader,它允许读取完整的消息而不是字节),通过您的代码(和 ByteReader 也通过 asio)实现它们并在更高级别使用它们。因此,您正在从数据处理的推式方法转向拉式方法,这有一些优点和缺点。

    【讨论】:

    • 感谢您提供到 Wangle 的链接!看起来(1)是最好的方法。如果我理解正确的话,它就是 asio::io_service 加上一个用于制作处理程序链的框架。
    【解决方案2】:

    我不会讨论 boost::asio,因为这看起来更像是一种设计模式而不是网络模式。 我会使用State Pattern。这样您就可以随时更改协议。

    class net_protocol {
    protected:
        socket sock;
    
    public:
        net_protocol(socket _sock) : sock(_sock) {}
    
        virtual net_protocol* read(Result& r) = 0;
    };
    
    class http_protocol : public net_protocol {
    public:
        http_protocol(socket _sock) : net_protocol(_sock) {}
    
        net_protocol* read(Result& r) {
            boost::asio::async_read_until(socket, inputBuffer, "\r\n\r\n", read_handler);
            // set result, or have read_handler set it
            return this;
        }
    };
    
    class binary_protocol : public net_protocol {
    public:
        binary_protocol(socket _sock) : net_protocol(_sock) {}
    
        net_protocol* read(Result& r) {
            // read 4 bytes as int size and then size bytes in a buffer. using boost::asio::async_read
            // set result, or have read_handler set it
    
            // change strategy example
            //if (change_strategy)
            //  return new http_strategy(sock);
    
            return this;
        }
    };
    

    你会用

    初始化起始协议
    std::unique_ptr<net_protocol> proto(new http_protocol(sock));
    

    那么你会读到:

    //Result result;
    proto.reset(proto->read(result));
    

    编辑:if() 返回新策略实际上是一个状态机

    如果您担心那些异步读取,因此无法确定哪些返回策略,请让策略类在其 read_handler 中调用通知方法

    class caller {
        std::unique_ptr<net_protocol> protocol;
        boost::mutex io_mutex;
    
    public:
        void notify_new_strategy(const net_protocol* p) { 
            boost::unique_lock<boost::mutex> scoped_lock(mutex);
            protocol.reset(p);
        }
    
        void notify_new_result(const Result r) { ... }
    };
    

    如果您不需要即时更改使用的协议,则不需要 State,因此 read() 将返回 Result(或者,如果异步,则为 void 并调用 caller::notify_new_result(const Result))。您仍然可以使用相同的方法(2 个具体类和一个虚拟类),它可能非常接近 Strategy Pattern

    【讨论】:

    • 感谢您的回答,但不清楚这如何通过二进制协议读取 http。主要问题是如何对 http/rtsp 部分的二进制协议进行抽象。如果您有 http OR 二进制文件,则动态协议更改非常容易。只需使用协议处理程序作为策略,问题就解决了。
    猜你喜欢
    • 2021-10-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-01-07
    • 1970-01-01
    • 2016-08-09
    • 1970-01-01
    • 2011-06-19
    相关资源
    最近更新 更多