欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Boost Beast要点解析(一)

程序员文章站 2022-06-01 09:18:30
...

【注】本文不是关于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_五个指针来管理内存分配和各区域的大小,如下图所示。

Boost Beast要点解析(一)

        要注意的是,单纯设置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个字符。输出如下:

Boost Beast要点解析(一)

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个字节写入第二块,最后将写入的内容打印出来,运行输出如下:

Boost Beast要点解析(一)

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());

输出为:
Boost Beast要点解析(一)