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_streambuf
is derived fromstd::basic_streambuf
to 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 协议 ,转载请注明出处!