Boost Beast要点解析(一)
【注】本文不是关于beast的全面描述,只涉及一些要点,主要资料来源于beast官方文档和实现代码。本文对应boost 的版本为1.73。
一、Beast中的Buffer
1.概念
软件程序中,缓存区是被广泛应用的概念。Beast中的缓存区大多来自于Asio,DynamicBuffer是指由大小可变的缓存区域,根据不同内存方式,有三种形态:
- 一块连续内存,大小可变
- 多块内存,每块大小相同,可添加块
- 多块内存,每块大小不同,可添加块
BufferSequence其实是一个容器,将若干内存类包装起来(一般用双向链表),本身只提供遍历操作就可以了,它又分为可写的MutableBufferSequence和只读的ConstBufferSequence。MutableBufferSequence用于接收过程(即从网络接收到字节流,写入缓存),ConstBufferSequence可用于发送过程(即读出缓存区中已生成的字节流,发送到网络)。
Asio实现了两个“轻量级”的内存区域定义const_buffer和mutable_buffer,其实就是对struct {const void *, size_t }和struct {void *, size_t }这样结构的封装,内存的分配与释放由使用者负责,const_buffer和mutable_buffer只是引用而已,个人猜想实际的应用场景太多样了,如果将更多的功能添加到buffer上,来构造一个“重量级”的缓存管理,会影响性能,这与基础库的宗旨是不符的,因此Asio中只有一个streambuf类。
作为偏应用级别的Beast,其中有不少关于buffer的类,它进行了较大扩展,但意味着牺牲了性能和灵活性。个人感觉有点过头了,实际应用有各种要求,一致化的读写方式,显得比较繁琐,还是留给应用比较好。
以下是beast涉及缓存的类和辅助类。
2.flat_buffer
flat_buffer的定义如下:
using flat_buffer = basic_flat_buffer< std::allocator< char > >;
就是basic_flat_buffer的一个特化。
flat_buffer就相当于一个装内容的容器,外部写入的内容,对容器来说,就是读入的内容,外部写入区域可mutable,确认(commit)之后,就变成读入区的末尾,并且是只读的(const),在外部消费(consume)之后,清理读入区。
在flat_buffer内部实现时,采用了begin_,in_,out_,last_,end_五个指针来管理内存分配和各区域的大小,如下图所示。
要注意的是,单纯设置max_size不会引起内存的重新分配,只有在确实需要时才会,因此,Prepre, shrink_to_fit可能会重新分配内存。每次commit,consume,clear之后,会有相关指针的移动,因此建议从flat_buffer得到的读入或写入区域的指针,应即得即用,不要缓存,除非确认读入/写入区不会发生变化。
以下是一个flat_buffer的示例。
boost::beast::flat_buffer buffer(100);
buffer.max_size(200);
auto buf=buffer.prepare(10); // buffer.prepare(201)会引发异常
char* const p = static_cast<char* const>(buf.data());
memcpy(p, "123456", 6);
buffer.commit(6);
auto cbuf = buffer.cdata();
char temp[10];
memcpy(temp, cbuf.data(), buffer.size());
for (int i = 0; i < buffer.size(); ++i) printf("[%c]", temp[i]); printf("\n");
上例中,先设定一个100字节的buffer,然后调整大小为200(此时并不重新分配,但会在后续操作中如此),如果申请(prepare)201大小的区域,则会产生异常。申请10字节大小写入区域,然后写入6个字符,最后查看读入区域,输出上述6个字符。输出如下:
3.multi_buffer
multi_buffer的定义如下:
using multi_buffer = basic_multi_buffer< std::allocator< char > >;
就是basic_multi_buffer的一个特化。
multi_buffer从功能上讲与flat_buffer是一样的,区别在于内存管理模式。flat_buffer的内存是一块连续区域(大小在运行时按需而变),multi_buffer的内存是一系列块(每块大小可以不相同),multi_buffer采用boost:: Intrusive库中的双向链表来管理,从整体上看,multi_buffer内存变得不连续了,操作上就麻烦多了,好处是不会发生内存大小调整了。
以下是一个multi_buffer的示例。
boost::beast::multi_buffer mbuffer(10);
auto buf1 = mbuffer.prepare(6);
for (auto it : buf1)
{
printf("writable:[%u]\n", it.size());
memset(it.data(),'a', it.size());
}
mbuffer.commit(6);
mbuffer.max_size(30);
auto buf2 = mbuffer.prepare(7);
for (auto it : buf2)
{
printf("writable:[%u]\n", it.size());
memset(it.data(),'A', it.size());
}
mbuffer.commit(7);
auto bufs = mbuffer.data();
size_t aa=bufs.buffer_bytes();
printf("totalsize:%u\n",aa);
char temp[100];
for(auto it : bufs)
{
memcpy(temp, it.data(), it.size());
for (int i = 0; i < it.size(); ++i) printf("[%c]", temp[i]); printf("\n");
}
上例中,先设定一个10字节的buffer,此时会有一个10字节的内存块,写入(提交)6字节,然后再将buffer增大到30,则会有两个内存块(大小分别为10和(30-10),此时并不分配,但会变得如此),第二次写入(提交)7字节,其中4个字节写入第一块,3个字节写入第二块,最后将写入的内容打印出来,运行输出如下:
4.flat_static_buffer
flat_static_buffer的定义如下:
template< std::size_t N>
class flat_static_buffer : public flat_static_buffer_base
flat_static_buffer可认为是max_size在编译时就已确定的flat_buffer,因此内存大小固定,不会发生重新分配。基类flat_static_buffer_base引用外部分配的内存,定义了一系列操作,flat_static_buffer则分配了一段内存,其操作方法与基类基本一致(多了base()和operator=)。
以下是一个使用示例,与上面的示例基本一样。
boost::beast::flat_static_buffer<10> buf;
boost::asio::mutable_buffer aa = buf.prepare(3);
memcpy(aa.data(), "aa\0", aa.size());
buf.commit(aa.size());
printf("capcity=%d read=%d\n", buf.capacity(),buf.size());
boost::asio::const_buffer bb = buf.data();
printf("%s[%d]\n", static_cast<const char *>(bb.data()), bb.size());
buf.consume(aa.size());
printf("capcity=%d read=%d\n", buf.capacity(), buf.size());
输出为: