如何使用boost::asio实现嵌套协议

How to implement nested protocols with boost::asio?

本文关键字:嵌套 协议 实现 asio boost 何使用      更新时间:2023-10-16

我正在尝试编写一个处理协议a而不是协议B的服务器。

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

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

所以我想用这样的东西:

boost::asio::async_read_until(socket, inputBuffer, "rnrn", read_handler);

但是,使用一些连接到协议B处理程序的伪套接字来代替socket

我有一些想法:

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

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

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

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

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

  • 丑陋。

(3) 只是看起来很难看:-)

所以问题是:我该如何实现这一点?

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

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

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

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

我不会讨论boost::asio,因为这似乎更像是一种设计模式,而不是一种网络模式。我会使用状态模式。这样你就可以随时更改协议。

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, "rnrn", 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));

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

如果您关心那些异步读取,因此无法决定哪些返回策略,请让策略类在其readhandler 中调用notify方法

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(或者,如果是async,则为void并调用caller::notify_new_result(const Result))。尽管如此,您仍然可以使用相同的方法(2个具体类和一个虚拟类),它可能非常接近策略模式