Reading and Writing
Learn the different ways to read and write data with Asio coroutines.
Overview
Asio provides several read/write functions with different behaviors:
| Function | Behavior |
|---|---|
|
Read some data (may be less than buffer size) |
|
Read exact amount (loops internally) |
|
Read until delimiter found |
|
Write some data (may be less than buffer size) |
|
Write all data (loops internally) |
Reading Some Data
async_read_some is the lowest-level read. It returns whatever data is
available, which may be less than your buffer size:
awaitable<void> read_some_example(asio::ip::tcp::socket& socket)
{
char buf[1024];
// May return 1 byte or 1024 bytes or anywhere in between
std::size_t n = co_await socket.async_read_some(
asio::buffer(buf), asio::use_awaitable);
std::cout << "Received " << n << " bytes\n";
// Process buf[0..n)
}
Use async_read_some when:
-
You want to process data as it arrives
-
You don’t know how much data to expect
-
You’re implementing a streaming protocol
Reading Exact Amount
async_read keeps reading until the buffer is full:
awaitable<void> read_exact_example(asio::ip::tcp::socket& socket)
{
char header[4]; // Read exactly 4 bytes
co_await asio::async_read(
socket, asio::buffer(header), asio::use_awaitable);
// header is now completely filled
uint32_t length = /* parse header */;
std::vector<char> body(length);
co_await asio::async_read(
socket, asio::buffer(body), asio::use_awaitable);
}
Use async_read when:
-
You need exactly N bytes (e.g., fixed-size headers)
-
You’re reading length-prefixed messages
Reading Until Delimiter
async_read_until reads until a delimiter is found:
awaitable<std::string> read_line(asio::ip::tcp::socket& socket)
{
std::string data;
std::size_t n = co_await asio::async_read_until(
socket,
asio::dynamic_buffer(data),
'\n',
asio::use_awaitable);
// data[0..n) includes the delimiter
std::string line = data.substr(0, n - 1); // Remove \n
data.erase(0, n); // Remove consumed data
co_return line;
}
You can also use string delimiters:
// Read until double newline (HTTP header end)
std::size_t n = co_await asio::async_read_until(
socket,
asio::dynamic_buffer(data),
"\r\n\r\n",
asio::use_awaitable);
Or regex:
#include <boost/asio/read_until.hpp>
#include <boost/regex.hpp>
std::size_t n = co_await asio::async_read_until(
socket,
asio::dynamic_buffer(data),
boost::regex("\\r?\\n"), // \n or \r\n
asio::use_awaitable);
Writing Some Data
async_write_some writes some data, possibly less than provided:
awaitable<void> write_some_example(asio::ip::tcp::socket& socket)
{
std::string data = "Hello, World!";
// May write less than data.size()
std::size_t n = co_await socket.async_write_some(
asio::buffer(data), asio::use_awaitable);
std::cout << "Wrote " << n << " of " << data.size() << " bytes\n";
}
You rarely need async_write_some directly.
Writing All Data
async_write ensures all data is written:
awaitable<void> write_all_example(asio::ip::tcp::socket& socket)
{
std::string data = "Hello, World!";
// Writes all data, retrying as needed
co_await asio::async_write(
socket, asio::buffer(data), asio::use_awaitable);
// All of data has been sent
}
Use async_write for almost all writes. It handles partial writes internally.
Scatter/Gather I/O
Write multiple buffers in one operation (gather):
awaitable<void> gather_write(asio::ip::tcp::socket& socket)
{
std::string header = "HTTP/1.1 200 OK\r\n\r\n";
std::string body = "Hello!";
std::array<asio::const_buffer, 2> buffers = {
asio::buffer(header),
asio::buffer(body)
};
co_await asio::async_write(socket, buffers, asio::use_awaitable);
}
Read into multiple buffers (scatter):
awaitable<void> scatter_read(asio::ip::tcp::socket& socket)
{
char header[4];
char body[100];
std::array<asio::mutable_buffer, 2> buffers = {
asio::buffer(header),
asio::buffer(body)
};
// Fills header first, then body
co_await asio::async_read(socket, buffers, asio::use_awaitable);
}
Completion Conditions
async_read accepts a completion condition to customize when it stops:
// Read at least 100 bytes, but accept more
std::size_t n = co_await asio::async_read(
socket,
asio::buffer(buf),
asio::transfer_at_least(100),
asio::use_awaitable);
// Read exactly 100 bytes (default behavior)
co_await asio::async_read(
socket,
asio::buffer(buf, 100),
asio::transfer_exactly(100),
asio::use_awaitable);
// Read until buffer is full
co_await asio::async_read(
socket,
asio::buffer(buf),
asio::transfer_all(),
asio::use_awaitable);
Handling Short Reads and Writes
With async_read_some, you typically loop until you have what you need:
awaitable<void> read_message(asio::ip::tcp::socket& socket)
{
std::vector<char> message;
char buf[1024];
while (message.size() < expected_size)
{
auto [ec, n] = co_await socket.async_read_some(
asio::buffer(buf),
asio::experimental::as_tuple(asio::use_awaitable));
if (ec == asio::error::eof)
break;
if (ec)
throw boost::system::system_error(ec);
message.insert(message.end(), buf, buf + n);
}
}
Or just use async_read which does this for you.
EOF Handling
When reading, asio::error::eof means the peer closed the connection:
awaitable<void> read_until_close(asio::ip::tcp::socket& socket)
{
std::string data;
char buf[1024];
for (;;)
{
auto [ec, n] = co_await socket.async_read_some(
asio::buffer(buf),
asio::experimental::as_tuple(asio::use_awaitable));
if (ec == asio::error::eof)
{
std::cout << "Connection closed, received total: "
<< data.size() << " bytes\n";
break;
}
if (ec)
throw boost::system::system_error(ec);
data.append(buf, n);
}
}
Common Mistakes
Using async_read_some expecting full reads — It may return less data
than requested. Use async_read for exact amounts.
Forgetting buffer lifetime — The buffer must remain valid until the operation completes.
Not handling EOF — EOF is normal, not an error. Handle it appropriately.
Mixing reads and writes on same socket without care — Operations can overlap, but make sure you understand the protocol.
Next Steps
-
Buffers — Buffer types in detail
-
Error Handling — Handle read/write errors
-
TCP Server — Full server example