Buffers in Boost.Asio
本文最后更新于:December 1, 2021 am
介绍了Boost.Asio中的缓冲区
Buffers
基本上,I/O操作往往包括从一片连续的内存空间中搬入和搬出数据,这片内存空间就叫做缓冲区Buffer。这些缓冲区可以被简单理解为一个二元组,(pointer, size),前一个指针存储缓冲区的首地址,而第二个size存储缓冲区的大小。
为了能够编写更高效地网络应用,Boost.Asio支持发散聚合操作scatter-gather operations。这种操作包括一至多个缓冲区:
scatter-read操作将数据读入多个缓冲区gather-write将数据送入多个缓冲区
因此,我们需要一层抽象来表示一些缓冲区的集合。Boost.Asio中采用的方案是,定义一种类型来代表单个缓冲区(实际上有两种类型,下文会提到)。这些缓冲区们可以被存入容器,然后我们再将容器传给发散聚合操作scatter-gather operations。
除了能够通过二元组的方式来声明一个缓冲区以外,Boost.Asio区分了可变内存mutable和不可变内存non-modifiable。因此,可以像如下的方式来定义这两种缓冲区。
1 | |
显然,mutable_buffer可以被转换成const_buffer,反之不可。
然而,Boost.Asio并不像这样定义,而是通过定义两种类mutable_buffer和const_buffer。这样做可以对外提供一个不透明内存连续读写功能,同时达到:
在类型转换中,这个类表现得像std::pair一样。也就是
mutable_buffer只能单向转换到const_buffer。提供了缓冲区泛滥保护。给定一个buffer实例,在创建一个新的缓冲区时,用户所指定的内存空间至多和这个实例一致,或者是这个实例的内存空间的一个子集。
为了进一步保证安全,库也有从一个数组(比如
boost::array,std::vector,std::string)自动决定缓冲区大小的机制。外部通过
data()来显示获取底层的数据。一般情况下,应用层面的代码不会需要做这一步,但是在库的实现上,需要用户代码将裸的内存传给内部的操作系统函数。
最后,通过将多个缓冲区加入容器的方式,很多种缓冲区都可以被传给发散聚合操作scatter-gather operation。定义MutableBufferSequence和ConstBufferSequence这两种概念,它们适用于很多容器,比如std::vector,std::list,std::array。
Streambuf for Integration with Iostreams
The class
boost::asio::basic_streambufis derived fromstd::basic_streambufto associate the input sequence and output sequence with one or more objects of some character array type, whose elements store arbitrary values.
boost::asio::basic_streambuf从std::basic_streambuf派生,目的是将输入和输出的序列input sequence and output sequence同一个或者多个某种字符数组类型的对象相关联(其中对象内部存储任意值)。
这些字符数组对象是streambuf对象内部的,但是也提供了对于这些数组对象的元素的直接访问接口,用以允许在I/O操作中使用它们,比如对一个socket执行send或者receive操作。
streambuf的输入序列通过data()成员函数访问。这个函数的返回值类型满足ConstBufferSequence的约束。streambuf的输出序列通过成员函数prepare()访问。返回值类型满足MutableBufferSequence的约束。- 数据从
output sequence的头部被转移到input sequence的尾部。通过调用commit()函数来实现。 - 数据可以被从
input sequence的头部移除。通过调用consume()函数实现。
streambuf的构造器接受一个size_t的变量来表示 输入序列和输出序列大小的和的最大值。任何会导致两者之和超出范围的操作都会抛出异常std::length_error
Bytewise Traversal of Buffer Sequences
可以通过buffers_iterator<>模板类来对缓冲区序列(比如满足MutableBufferSequence或者ConstBufferSequence约束的类)进行“连续性”访问。帮助函数buffer_begin()和buffer_end()用于返回头尾的迭代器。
如果要从一个socket读一行内容到std::string中,可以这样写:
1 | |
Buffer Debugging
某些标准库的实现,提供一种叫做iterator debugging的特性。这意味着迭代器的有效性在运行时可以得到检查。如果程序试图使用某个失效的迭代器,断言将会被触发,比如
1 | |
Boost.Asio利用这一特性,实现了缓冲区Debug。考虑下面一段代码:
1 | |
当调用异步读写操作之前,我们必须先确保用于操作的缓冲区在直到换成回调completion handler被调用时都是有效的。在这段代码里面,这个缓冲区是msg,它被分配在栈上,当回调被调用时,已经超出了这个变量的作用域。如果你运气好,应用会直接崩掉,但是更可能出现的是随机错误。
当启用buffer debugging时,Boost.Asio将会存储一个指向string内部的迭代器,并且在异步操作完成时试图解引用来检验它的有效性。在上面这个例子中,在调用完成回调之前就会观察到断言错误。
这个特性在Microsoft Visual Studio 8.0及以后的版本是自动生效的,或者对于gcc,当_VLIBCXX_DEBUG被定义时。这样的检查会有性能代价,因此只在debug build中使用这一特性。对于其它编译器,可以通过定义 BOOST_ENABLE_BUFFER_DEBUGGING来使用,或者通过BOOST_ASIO_DISABLE_DEBUGGING来关闭。
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!