# How to Use asio buffered_stream and buffered_read_stream: A Complete Guide with Examples

> Learn to use asio buffered_stream and buffered_read_stream to optimize I/O. This guide explains how these wrappers reduce system calls for efficient network programming.

- Repository: [chriskohlhoff/asio](https://github.com/chriskohlhoff/asio)
- Tags: how-to-guide
- Published: 2026-07-17

---

**`asio::buffered_stream` and `asio::buffered_read_stream` are convenience wrappers that add internal buffering to any underlying stream, reducing system calls by satisfying I/O operations from memory before hitting the socket.**

The `chriskohlhoff/asio` library provides these templates to optimize network I/O performance. Whether you need bidirectional buffering or read-only optimization, understanding how to use asio buffered_stream and buffered_read_stream correctly can significantly reduce latency in high-throughput applications.

## What Are asio::buffered_stream and asio::buffered_read_stream?

Both classes wrap an underlying "next layer" stream (such as a TCP socket, SSL stream, or custom I/O object) with an internal buffer managed by `asio::detail::buffered_stream_storage`. This ring-buffer abstraction supports `size()`, `consume()`, and direct data access via `data()`.

### Key Differences

| Feature | `buffered_stream` | `buffered_read_stream` |
|---------|-------------------|------------------------|
| Buffers both **read** and **write** operations | ✅ | ❌ |
| Buffers only **read** operations | ❌ | ✅ |
| Underlying type stored as `next_layer_type` | ✅ | ✅ |
| Accessible via `next_layer()` method | ✅ | ✅ |
| Implements `in_avail()`, `peek()`, `fill()`, `flush()` | ✅ | ✅ |

### Architecture Overview

In the source code at [`include/asio/buffered_stream.hpp`](https://github.com/chriskohlhoff/asio/blob/main/include/asio/buffered_stream.hpp), the `buffered_stream` class composes both a `buffered_read_stream` and a `buffered_write_stream`. Both wrapper classes inherit from `asio::detail::noncopyable` to prevent accidental copies of internal buffers.

The implementations follow a consistent pattern:

- **Synchronous operations** (`read_some`, `write_some`, `fill`, `flush`) first check the internal buffer. If the request can be satisfied from memory, no system call occurs. Otherwise, the operation forwards to the next layer.
- **Asynchronous operations** use the `async_initiate` helper with custom initiation classes (e.g., `initiate_async_buffered_fill`, `initiate_async_buffered_read_some`) to coordinate between the buffer and underlying async operations.

The actual algorithmic implementations reside in [`include/asio/impl/buffered_read_stream.hpp`](https://github.com/chriskohlhoff/asio/blob/main/include/asio/impl/buffered_read_stream.hpp) and [`include/asio/impl/buffered_write_stream.hpp`](https://github.com/chriskohlhoff/asio/blob/main/include/asio/impl/buffered_write_stream.hpp), which are automatically included by the public headers.

## Setting Up buffered_stream for Bidirectional I/O

Use `asio::buffered_stream` when you need to minimize system calls for both sending and receiving data. This is ideal for HTTP clients or custom protocol implementations.

```cpp
#include <asio.hpp>
#include <iostream>

int main()
{
    asio::io_context ctx;

    // Resolve the remote endpoint
    asio::ip::tcp::resolver resolver(ctx);
    auto endpoints = resolver.resolve("example.com", "http");

    // Create raw socket and wrap with buffered_stream
    asio::ip::tcp::socket socket(ctx);
    asio::buffered_stream<asio::ip::tcp::socket> stream(
        std::move(socket), 4096, 4096);
    // Constructor: underlying stream, read_buffer_size, write_buffer_size

    // Connect the underlying socket via next_layer()
    stream.lowest_layer().connect(*endpoints.begin());

    // Send HTTP GET request
    std::string request = "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n";
    stream.write_some(asio::buffer(request));
    stream.flush();  // Forces buffered data to socket

    // Fill read buffer from network, then extract response
    stream.fill();  // Reads from socket into internal buffer
    std::vector<char> resp_buf(1024);
    std::size_t n = stream.read_some(asio::buffer(resp_buf));
    std::cout.write(resp_buf.data(), n);

    stream.close();
}

```

**Key methods explained:**

- **`write_some()`**: Writes data into the internal write buffer. If the buffer fills up, it automatically flushes to the underlying socket.
- **`flush()`**: Explicitly pushes all buffered write data to the next layer. Always call this before waiting for a response to ensure the server receives the complete request.
- **`fill()`**: Reads from the underlying socket into the internal read buffer, making data available for subsequent `read_some()` calls without additional system calls.

## Using buffered_read_stream for Read-Only Optimization

When you only need to buffer incoming data (for example, when parsing a large file download or processing a streaming protocol), use `asio::buffered_read_stream` defined in [`include/asio/buffered_read_stream.hpp`](https://github.com/chriskohlhoff/asio/blob/main/include/asio/buffered_read_stream.hpp). This avoids the overhead of maintaining a write buffer when you only read from the socket.

```cpp
#include <asio.hpp>
#include <iostream>

int main()
{
    asio::io_context ctx;
    asio::ip::tcp::socket sock(ctx);
    // ... connect socket ...

    // Wrap socket with read-only buffering
    asio::buffered_read_stream<asio::ip::tcp::socket> bstream(
        std::move(sock), 8192);

    // Process data in chunks
    std::vector<char> buf(512);
    while (true)
    {
        // Ensure buffer has data before reading
        bstream.fill();  // Blocking operation; use async_fill() for async code
        
        std::size_t n = bstream.read_some(asio::buffer(buf));
        if (n == 0) break;
        
        // Process buf[0..n) - no socket call made here
        std::cout.write(buf.data(), n);
    }
}

```

**Performance characteristics:**

- Only `fill()` (or `async_fill`) invokes the underlying socket's `read_some()`.
- Once data is in the internal buffer, `read_some()` copies directly from memory, dramatically reducing system calls when processing small chunks of a large stream.
- The underlying socket is accessible via `next_layer()` or `lowest_layer()` for connection management.

## Asynchronous Operations with Buffered Streams

Both classes provide full asynchronous support using the completion token pattern. The asynchronous implementations in [`asio/impl/buffered_read_stream.hpp`](https://github.com/chriskohlhoff/asio/blob/main/asio/impl/buffered_read_stream.hpp) use `initiate_async_buffered_fill` and `initiate_async_buffered_read_some` to manage the internal buffer state across multiple async operations.

```cpp
#include <asio.hpp>
#include <iostream>
#include <memory>

void start_async_echo(asio::buffered_stream<asio::ip::tcp::socket>& stream)
{
    auto buffer = std::make_shared<std::vector<char>>(1024);
    
    stream.async_read_some(asio::buffer(*buffer),
        [&stream, buffer](const asio::error_code& ec, std::size_t length)
        {
            if (ec) 
            {
                std::cerr << "Read error: " << ec.message() << "\n";
                return;
            }
            
            // Echo back the data
            stream.async_write_some(asio::buffer(buffer->data(), length),
                [&stream, buffer](const asio::error_code& ec2, std::size_t)
                {
                    if (ec2)
                    {
                        std::cerr << "Write error: " << ec2.message() << "\n";
                        return;
                    }
                    
                    // Ensure data reaches the network
                    stream.async_flush(
                        [](const asio::error_code& ec3, std::size_t)
                        {
                            if (ec3) std::cerr << "Flush error\n";
                        });
                });
        });
}

```

**Async workflow notes:**

- Always call `async_flush()` after `async_write_some()` to ensure buffered data is transmitted.
- The completion handler receives the number of bytes transferred from the internal buffer, not necessarily from the network.
- Multiple `async_read_some()` calls can be satisfied from a single `async_fill()` operation if the buffer contains sufficient data.

## Internal Architecture and Source Files

Understanding the source layout helps with debugging and extension:

| File | Description |
|------|-------------|
| [`include/asio/buffered_stream.hpp`](https://github.com/chriskohlhoff/asio/blob/main/include/asio/buffered_stream.hpp) | High-level wrapper composing read and write streams |
| [`include/asio/buffered_read_stream.hpp`](https://github.com/chriskohlhoff/asio/blob/main/include/asio/buffered_read_stream.hpp) | Read-side buffering logic and `fill()` implementation |
| [`include/asio/buffered_write_stream.hpp`](https://github.com/chriskohlhoff/asio/blob/main/include/asio/buffered_write_stream.hpp) | Write-side buffering logic and `flush()` implementation |
| [`include/asio/detail/buffered_stream_storage.hpp`](https://github.com/chriskohlhoff/asio/blob/main/include/asio/detail/buffered_stream_storage.hpp) | Ring buffer storage supporting `size()`, `consume()`, `data()` |
| [`include/asio/impl/buffered_read_stream.hpp`](https://github.com/chriskohlhoff/asio/blob/main/include/asio/impl/buffered_read_stream.hpp) | Async initiation classes and algorithmic implementations |
| [`include/asio/impl/buffered_write_stream.hpp`](https://github.com/chriskohlhoff/asio/blob/main/include/asio/impl/buffered_write_stream.hpp) | Async write and flush implementations |

The `buffered_stream_storage` class provides a circular buffer abstraction that both stream types use. Synchronous methods check `in_avail()` to determine if they can satisfy requests from the buffer, while asynchronous methods use the initiation classes to coordinate buffer state with the underlying stream's async operations.

## Summary

- **`asio::buffered_stream`** provides bidirectional buffering for both reads and writes, reducing system calls in high-frequency I/O scenarios.
- **`asio::buffered_read_stream`** optimizes read-only scenarios by buffering incoming data while forwarding write operations directly to the underlying stream.
- Use `fill()` to populate the read buffer from the network, and `flush()` to push write buffers to the socket.
- Access the underlying socket via `next_layer()` or `lowest_layer()` for connection management and options.
- Asynchronous operations follow the standard Asio patterns but require explicit `async_flush()` to ensure data transmission.
- All buffer management uses `asio::detail::buffered_stream_storage`, a ring buffer implementation supporting efficient memory reuse.

## Frequently Asked Questions

### What is the difference between buffered_stream and buffered_read_stream?

`asio::buffered_stream` combines both read and write buffering, composing a `buffered_read_stream` and `buffered_write_stream` internally. `asio::buffered_read_stream` only buffers incoming data, forwarding write operations directly to the underlying stream. Use `buffered_read_stream` when you only need to optimize read performance and want to avoid the memory overhead of maintaining a write buffer.

### How do I access the underlying socket from a buffered stream?

Both classes expose the underlying stream through `next_layer()` and `lowest_layer()` methods. `next_layer()` returns the immediate underlying stream (which might be an SSL stream), while `lowest_layer()` drills down to the base socket. In [`include/asio/buffered_stream.hpp`](https://github.com/chriskohlhoff/asio/blob/main/include/asio/buffered_stream.hpp), these are defined as `next_layer_type& next_layer()` and `lowest_layer_type& lowest_layer()` respectively.

### When should I call fill() versus read_some()?

Call `fill()` when you need to ensure data is available in the internal buffer before processing. It blocks (or initiates an async operation) to read from the socket into the buffer. Call `read_some()` when you want to extract data already in the buffer. According to the implementation in [`asio/impl/buffered_read_stream.hpp`](https://github.com/chriskohlhoff/asio/blob/main/asio/impl/buffered_read_stream.hpp), `read_some()` first checks if data is available via `in_avail()`; if not, it may internally call `fill()`, but explicit `fill()` calls give you control over when network I/O occurs.

### Does buffered_stream support SSL/TLS streams?

Yes. Both `buffered_stream` and `buffered_read_stream` are templates that accept any type meeting the Stream requirements, including `asio::ssl::stream`. When wrapping an SSL stream, `next_layer()` returns the SSL stream object, while `lowest_layer()` returns the underlying TCP socket. This allows you to buffer encrypted data before decryption (for reads) or after encryption (for writes), reducing the number of costly SSL operations.