Fundamentally, I/O involves the transfer of data to and from contiguous regions of memory, called buffers. These buffers can be simply expressed as a tuple consisting of a pointer and a size in bytes. However, to allow the development of efficient network applications, Asio includes support for scatter-gather operations. These operations involve one or more buffers:
Therefore we require an abstraction to represent a collection of buffers. The approach used in Asio is to define a type (actually two types) to represent a single buffer. These can be stored in a container, which may be passed to the scatter-gather operations.
In addition to specifying buffers as a pointer and size in bytes, Asio makes a distinction between modifiable memory (called mutable) and non-modifiable memory (where the latter is created from the storage for a const-qualified variable). These two types could therefore be defined as follows:
typedef std::pair<void*, std::size_t> mutable_buffer; typedef std::pair<const void*, std::size_t> const_buffer;
Here, a mutable_buffer would be convertible to a const_buffer, but conversion in the opposite direction is not valid.
However, Asio does not use the above definitions as-is, but instead defines
two classes: mutable_buffer
and const_buffer
. The goal
of these is to provide an opaque representation of contiguous memory, where:
mutable_buffer
is convertible to
a const_buffer
, but
the opposite conversion is disallowed.
boost::array
or std::vector
of POD elements, or from a
std::string
.
data()
member function. In general an application should never need to do
this, but it is required by the library implementation to pass the
raw memory to the underlying operating system functions.
Finally, multiple buffers can be passed to scatter-gather operations (such
as read() or write())
by putting the buffer objects into a container. The MutableBufferSequence
and ConstBufferSequence
concepts have been defined so that containers such as std::vector
,
std::list
, std::array
or boost::array
can be used.
The class asio::basic_streambuf
is derived from std::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. These character array
objects are internal to the streambuf object, but direct access to the
array elements is provided to permit them to be used with I/O operations,
such as the send or receive operations of a socket:
ConstBufferSequence
requirements.
MutableBufferSequence
requirements.
The streambuf constructor accepts a size_t
argument specifying the maximum of the sum of the sizes of the input sequence
and output sequence. Any operation that would, if successful, grow the
internal data beyond this limit will throw a std::length_error
exception.
The buffers_iterator<>
class template allows buffer sequences (i.e. types meeting MutableBufferSequence
or ConstBufferSequence
requirements) to
be traversed as though they were a contiguous sequence of bytes. Helper
functions called buffers_begin() and buffers_end() are also provided, where
the buffers_iterator<> template parameter is automatically deduced.
As an example, to read a single line from a socket and into a std::string
, you may write:
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);
Some standard library implementations, such as the one that ships with Microsoft Visual C++ 8.0 and later, provide a feature called iterator debugging. What this means is that the validity of iterators is checked at runtime. If a program tries to use an iterator that has been invalidated, an assertion will be triggered. For example:
std::vector<int> v(1) std::vector<int>::iterator i = v.begin(); v.clear(); // invalidates iterators *i = 0; // assertion!
Asio takes advantage of this feature to add buffer debugging. Consider the following code:
void dont_do_this() { std::string msg = "Hello, world!"; asio::async_write(sock, asio::buffer(msg), my_handler); }
When you call an asynchronous read or write you need to ensure that the
buffers for the operation are valid until the completion handler is called.
In the above example, the buffer is the std::string
variable msg
. This variable
is on the stack, and so it goes out of scope before the asynchronous operation
completes. If you're lucky then the application will crash, but random
failures are more likely.
When buffer debugging is enabled, Asio stores an iterator into the string until the asynchronous operation completes, and then dereferences it to check its validity. In the above example you would observe an assertion failure just before Asio tries to call the completion handler.
This feature is automatically made available for Microsoft Visual Studio
8.0 or later and for GCC when _GLIBCXX_DEBUG
is defined. There is a performance cost to this checking, so buffer debugging
is only enabled in debug builds. For other compilers it may be enabled
by defining ASIO_ENABLE_BUFFER_DEBUGGING
.
It can also be explicitly disabled by defining ASIO_DISABLE_BUFFER_DEBUGGING
.
buffer, buffers_begin, buffers_end, buffers_iterator, const_buffer, const_buffers_1, mutable_buffer, mutable_buffers_1, streambuf, ConstBufferSequence, MutableBufferSequence, buffers example (C++03), buffers example (c++11).