Asio Buffers[通俗易懂]

Asio Buffers[通俗易懂]Buffers从根本上说,I/O涉及在内存的连续区域(称为缓冲区)之间传递数据。这些缓冲区可以简单地表示为由一个指针和一个以字节为单位的大小组成的元组。为了能够高效地开发网络应用程序,Asio包括对分散-收集操作(scatter-gather)的支持。这些操作涉及一个或多个缓冲区:分散-读(scatter-read)将数据接收到多个缓冲区中收集-写(gather-write)传输到多个缓冲区因此我们需要一个抽象来表示缓冲区的集合。Asio使用的方法是定义一个类型(实际是两个类型)来表示单个缓冲

Buffers

从根本上说,I/O涉及在内存的连续区域(称为缓冲区)之间传递数据。这些缓冲区可以简单地表示为由一个指针和一个以字节为单位的大小组成的元组。为了能够高效地开发网络应用程序,Asio包括对分散-聚集操作(scatter-gather)的支持。这些操作涉及一个或多个缓冲区:

  • 分散-读(scatter-read)将数据接收到多个缓冲区中
  • 聚集-写(gather-write) 传输多个缓冲区

因此我们需要一个抽象来表示缓冲区的集合。Asio使用的方法是定义一个类型(实际是两个类型)来表示单个缓冲区。这些缓冲区可以存储在容器中以传递给分散-聚集操作。
除了将缓冲区指定为一个指针和一个以字节为单位的大小,Asio还区分了可修改内存(称为mutable)和不可修改内存(后者是从const变量存储中创建的)。这两个类型可以定义如下:

typedef std::pair<void*, std::size_t> mutable_buffer;
typedef std::pair<const void*, std::size_t> const_buffer;
只听到从架构师办公室传来架构君的声音:
折菡巫山下,采荇洞庭腹。有谁来对上联或下联?

一个mutable_buffer可以转换为const_buffer,反过来则无效。
但是Asio并不原样使用上述定义,而是定义了两个类:mutable_buffer 和 const_buffer。这些目标是为了提供连续内存的不透明表示,其中

  • 类型在转换中的行为类似于std::pair。也就是说,mutable_buffer可转换为const_buffer,但不允许进行相反的转换。
  • 具有防止内存区溢出的保护功能。给定一个缓冲区实例,用户只能创建另一个表示相同内存范围或其子范围的缓冲区。为了提供进一步的安全性,该库还包括从POD元素的array、boost::array、std::vector,或者std::string自动确定缓冲区大小的机制。
  • 使用data()成员函数显示访问底层内存。通常,应用程序永远不需要执行此操作,但库实现需要将原始内存传递给底层操作系统函数。

最后,通过将缓冲区对象放入容器中可以将多个缓冲区传递给分散-聚集操作(例如read()和write())。MutableBufferSequence 和 ConstBufferSequence 概念已经定义,因此可以使用诸如 std::vector、std::list、std::array 或 boost::array 之类的容器。

Streambuf for Integration with Iostreams

类 asio::basic_streambuf派生自std::basic_streambuf,用于将输入序列和输出序列与某个字符数组类型的一个或多个对象相关联,这些对象的元素存储任意值。这些字符数组对象是streambuf对象的内部对象,但提供了对数组元素的直接访问以允许它们与I/O操作(如socket的发送或接收操作)一起使用:

  • streambuf的输入序列可通过data()成员函数访问。此函数的返回类型满足ConstBufferSequence要求。
  • streambuf的输出序列可通过prepare()成员函数访问。此函数的返回类型满足MutableBufferSequence要求。
  • 通过调用commit()成员函数,数据从输出队列的头部传输到输入队列的尾部。
  • 通过调用consume()成员函数从输入队列的头部删除数据

streambuf构造函数接收一个size_t参数,该参数指定输入序列和输出序列的大小之和的最大值。任何操作如果使数据增长超过此限制将抛出std::length_error异常

Bytewise Traversal of Buffer Sequences

buffer_iterator<>类模板允许缓冲区序列(即满足MutableBufferSequence或ConstBufferSequence要求的类型)像连续的字节序列一样遍历。还提供了buffers_begin()和buffers_end()帮助函数,其中buffers_iterator<>模板参数是自动推导的。
例如,要从socket中读取一行到std::string,你可以这样写:

此代码由Java架构师必看网-架构君整理
asio::streambuf sb; ... std::size_t n = asio::read_until(sock, sb, '\n'); asio::streambuf::const_buffers_type bufs = sb.data(); std::string line( asio::buffers_begin(bufs), asio::buffers_begin(bufs) + n);
Buffer Debugging

某些标准库实现(如 Microsoft Visual C++ 8.0 及更高版本附带的实现)提供了一项称为迭代器调试的功能。这意味着迭代器的有效性是在运行时检查的。如果程序尝试使用已失效的迭代器,则将触发断言。例如:

std::vector<int> v(1)
std::vector<int>::iterator i = v.begin();
v.clear(); // invalidates iterators
*i = 0; // assertion!

Asio 利用此功能添加缓冲区调试。请考虑以下代码:

此代码由Java架构师必看网-架构君整理
void dont_do_this() { std::string msg = "Hello, world!"; asio::async_write(sock, asio::buffer(msg), my_handler); }

调用异步读取或写入时,需要确保操作的缓冲区有效,直到调用完成处理程序。在上面的示例中,缓冲区是 std::string变量 msg。此变量位于栈上,因此在异步操作完成之前,它将超出范围。如果幸运的话,应用程序将崩溃,但随机故障的可能性更大。
启用缓冲区调试后,Asio 会将迭代器存储到字符串中,直到异步操作完成,然后解引用以检查其有效性。在上面的示例中,你将在 Asio 尝试调用完成处理程序之前观察到断言失败。
此功能在定义_GLIBCXX_DEBUG时自动可用于 Microsoft Visual Studio 8.0 或更高版本以及 GCC。此检查会产生性能成本,因此仅在调试版本中启用缓冲区调试。对于其他编译器,可以通过定义ASIO_ENABLE_BUFFER_DEBUGGING来启用它。也可以通过定义ASIO_DISABLE_BUFFER_DEBUGGING来显式禁用它。

原文:Buffers

本文来源丸子叮咚响,由架构君转载发布,观点不代表Java架构师必看的立场,转载请标明来源出处:https://javajgs.com/archives/210123
0

发表评论