当前位置: 首页 > 工具软件 > beast > 使用案例 >

Boost Beast要点解析(四)

公孙霖
2023-12-01

四、Beast中的network

        由于http、websocket仅涉及tcp,因此在beast范围内,也仅涉及tcp协议,Beast的网络操作基于Asio,但Beast的一个目标似乎是做一个完整的系统(猜测),因此beast将涉及到的网络数据操作都“重写”的一遍(一些是简单包装,一些是扩展),例如Asio空间被重新命名为net,对std:bind也扩展为bind_handlerbind_front_handler

        beast设计了两套stream,一是用于http的tcp_stream,另一个是用于websocket的stream,它们之间没有直接关系。
tcp_stream的相关定义如下:

template< class Protocol, class Executor = net::executor,class RatePolicy = unlimited_rate_policy>
class basic_stream
using tcp_stream = basic_stream< net::ip::tcp, net::executor, unlimited_rate_policy >;

从实现上看,beast并没有利用asio中的streambuf,而是采用其中的概念,与buffer类结合,重新写了一大套接收/发送操作,与Asio中类似。

        在实现websocket时,beast作者试图体现网络分层的概念,采用get_lowest_layer(),get_next_layer()来获得更下层的实现类,websocket中的stream定义如下:

template<
    class NextLayer,
    bool deflateSupported>
class stream

其中deflateSupported是websocket协议扩展,表示是否支持报文压缩如果定义一个ws流:

websocket::stream< boost::beast::tcp_stream > ws(ioc);

则上面那个ws的next layer就是boost::beast::tcp_stream。

同样,beast重新写了一大套接收/发送操作。

实际上,在basic_stream的实现代码中也可看到类似layer的身影,但并没有在文档中出现,猜测作者认为还不成熟,暂且在websocket中实验,估计在以后的版本中,网络分层的概念的会被强化。

五、Beast中的bind

bind_handler没有当内置handler是成员函数时的情况,其他与std:bind似乎没有什么区别。

bind_front_handler功能基本同std:bind,所不同的是handler参数的写法,这里不能使用std::placeholdershandler函数所有原来需要std::placeholders的参数只能设计在后面(还是看下面的例子吧,注意两种方式写法的不同)。bind_front_handler显得简洁一些。

class AA
{
public:
	void handler(int i, float f)
	{
		printf("i=%d f=%f\n", i, f);
	}
	void trigger(std::function<void(float)> h)
	{
		h(1.1);
	}
	void run()
	{
		trigger(boost::beast::bind_front_handler(&AA::handler, this, 2));
		trigger(std::bind(&AA::handler, this, 2, std::placeholders::_1));
	}
};

六、Beast中的timeout

Asio中基本的socket并没有附带超时操作(iostream有),但实际的网络操作都不可避免的要设置超时,因此beast在其网络数据流基类basic_stream中提供了超时设置功能:expires_after,expires_at以及expires_never,对于客户端,显得十分方便,但对于服务器,如果有很多连接,设置如此多的定时器,必然会增加机器的负担,使用时应该权衡。

在beast实现代码中,设置了读和写两个定时器,其本意大概是可设置读超时和写超时,但却没有分开设置的接口,现在使用的方式是在操作之前设置,如在读操作前设置一次超时,对读操作有效,在写操作之前设置一次超时,对写操作有效。

七、Websocket

Websocket API比较少,感觉作者的重心不在此处。

Websocket通信是由http通信“upgrade”而来,因此Websocket通信的第一步依然是构造一个http request parser,parser检查到需要upgrade到websocket协议后,构造websocket流,它的定义如下:

websocket::stream< boost::beast::tcp_stream > ws(ioc);

然后,ws接受连接(accept),通过ws接收和发送数据。

       Websocket读写没有相应的request/response类,因此与一般的tcp流使用类似,自己写解析与应答。

        Websocket采用set_option来进行设置,一个通常的设置是超时(作者建议采用这种方法,而不用更下层的方法,并且在构造之前需要disable下层设置的超时)。Websocke有三个超时:握手时超时,正常通信时超时,当达到正常通信时超时半数时是否发送ping信息而保持连接。以下是一个示例(先disable掉tcpstream的超时似乎有点不简洁)。

tcpstream.expires_never();
websocket::stream< boost::beast::tcp_stream > ws(std::move(tcpstream));
stream_base::timeout opt{
    std::chrono::seconds(30),   // handshake timeout
    stream_base::none(),      // disable idle timeout
    false                  //not set ping
};
ws.set_option(opt);

八、总结:

1.  Beast运用了boost的很多东西,很好地运用了template的特性,但使得学习成本高,看一个东西牵扯其他很多东西,对库的开发者来说,这不是问题,但对普通应用者来说,就是大问题。

2. 作为偏向协议解析的库,Beast涉及很多网络操作,反而显得与协议解析部分绑定的较紧,例如,

read(stream, buffer, request);

这样的功能感觉上分两步更灵活:read stream into buffer和 parse buffer into request,其中第一步由Asio或其他别的网络库来完成(以目前的实现,如果不采用Asio,真不知如何)。Beast中提供的API,涉及网络及相关的操作不算少数,并且提出了next layer, decorator等概念,目标比较宏大,但却有点偏离协议解析这个最基本的目标。实际的应用中,离不开诸如url encode,querystring parsing等功能,做web服务器应用时,还需要url routing,这些实用的功能,beast反而没有提供,所以有时会迷惑,beast到底定位成什么库呢。
3.    TCP数据就是流式数据,无论request还是response都是char序列(流),如果细分,还可以是text流或binary流,接收和发送由网络库负责,接收到时,由request卸载;发送前由response装载,负荷是其他具体类型类(与协议对应)的deserialization /serialization,以上是通常的思路,beast将类型确定提前了,由类型类才能构造出request/response,这种思路是否更好,见仁见智吧,但使用beas写代码时,应该适应这种思路。
4.    Beast提供了协议解析的一些新思路,形式的优美与运行的高效之间如何平衡,作为库作者,一般是强调前者,作为项目的作者,则一般强调后者。希望beast能找到一个兼容的方案。
5.    Asio已发展了若干版本,从每次boost版本的更新文档中,都可以看到Asio每次不断的努力,beast定位比较宏大,感觉还有很长的路走。

 类似资料: