# Implementing Reconnection Logic with ASIO async_connect: A Complete Guide

> Master ASIO async_connect reconnection logic. Learn how to use steady_timer and shared_ptr for robust retries in asynchronous C++ network applications.

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

---

**Use `asio::async_connect` with an `asio::steady_timer` to schedule retries, capturing your socket and timer in `std::shared_ptr` to maintain their lifetimes across asynchronous operations, and re-invoke `async_connect` in the timer's completion handler after detecting connection failures.**

ASIO's `async_connect` function provides a robust foundation for implementing reconnection logic in network applications. This guide examines the implementation details in the `chriskohlhoff/asio` repository to show you how to build reliable retry mechanisms using composed asynchronous operations.

## Understanding the async_connect Composed Operation

`asio::async_connect` is a **composed asynchronous operation** that attempts to open a socket by trying each endpoint in a sequence until one succeeds. According to the ASIO source code in [`include/asio/connect.hpp`](https://github.com/chriskohlhoff/asio/blob/main/include/asio/connect.hpp), the library provides two generic initiating function overloads:

- **Range-based overload** (lines 45-52): Works with any container providing `begin()`/`end()`, such as `tcp::resolver::results_type`. This delegates to `detail::initiate_async_range_connect`.
- **Iterator-based overload** (lines 90-98): Works directly with arbitrary iterators, delegating to `detail::initiate_async_iterator_connect`.

Both overloads forward the actual work to [`include/asio/impl/connect.hpp`](https://github.com/chriskohlhoff/asio/blob/main/include/asio/impl/connect.hpp), where the core logic resides in two operation classes:

- `detail::range_connect_op`: Drives range-based connections, storing the socket, endpoint range, current index, and user handler.
- `detail::iterator_connect_op`: Implements the same logic for arbitrary iterator pairs.

These classes inherit from `base_from_cancellation_state` to honor ASIO's cancellation types and `base_from_connect_condition` to optimize away overhead when using the default condition.

## How Reconnection Logic Works with async_connect

When `async_connect` completes, your handler receives an `asio::error_code` and the connected endpoint (or a default-constructed endpoint on failure). Because `async_connect` already loops through the endpoint list internally, you only need to implement reconnection when the entire sequence fails or when handling transient network errors.

The standard pattern involves three steps:

1. **Detect failure** in the completion handler by checking `error != asio::error::operation_aborted`.
2. **Schedule a retry** using an `asio::steady_timer` to prevent tight loops and reduce server load.
3. **Re-invoke `async_connect`** with the same endpoint range (or a refreshed one after a new DNS resolve).

## Basic Reconnection Implementation

The following example demonstrates a fixed-interval retry loop using `std::shared_ptr` to manage object lifetimes across asynchronous operations:

```cpp
void reconnect(asio::io_context& ctx,
               const tcp::resolver::results_type& eps,
               std::size_t max_attempts = 0)
{
    auto sock = std::make_shared<tcp::socket>(ctx);
    auto tmr  = std::make_shared<asio::steady_timer>(ctx);
    std::size_t attempts = 0;

    std::function<void()> try_connect = [=, &try_connect]() mutable {
        if (max_attempts && attempts >= max_attempts) {
            std::cerr << "Giving up after " << attempts << " attempts.\n";
            return;
        }
        ++attempts;

        asio::async_connect(*sock, eps,
            [=](const asio::error_code& ec, const tcp::endpoint& ep) mutable {
                if (!ec) {
                    std::cout << "Connected to " << ep << "\n";
                    return;
                }

                std::cerr << "Attempt " << attempts
                          << " failed: " << ec.message() << "\n";

                tmr->expires_after(std::chrono::seconds(2));
                tmr->async_wait([=](const asio::error_code&) { try_connect(); });
            });
    };

    try_connect();          // First attempt.
}

```

### Managing Object Lifetimes

Notice that both the **socket** and **timer** are captured by `std::shared_ptr`. This ensures they remain alive during the entire reconnection sequence, even when the initial `reconnect` function scope exits. The `std::function` wrapper allows the lambda to recursively invoke itself.

## Advanced Reconnection Patterns

### Exponential Back-off

To implement exponential back-off, multiply the delay after each failure, capping at a maximum value:

```cpp
void reconnect_with_backoff(asio::io_context& ctx,
                            const tcp::resolver::results_type& eps,
                            asio::cancellation_signal& cancel_signal)
{
    auto sock = std::make_shared<tcp::socket>(ctx);
    auto tmr  = std::make_shared<asio::steady_timer>(ctx);
    std::chrono::seconds delay{1};

    std::function<void()> attempt = [=, &attempt]() mutable {
        if (cancel_signal.stop_requested())
            return;   // Early exit if cancelled.

        asio::async_connect(*sock, eps,
            [=](const asio::error_code& ec, const tcp::endpoint& ep) mutable {
                if (!ec) {
                    std::cout << "Connected to " << ep << "\n";
                    return;
                }

                std::cerr << "Connect failed: " << ec.message()
                          << " – retry in " << delay.count() << "s\n";

                tmr->expires_after(delay);
                tmr->async_wait([=](const asio::error_code&) {
                    // Double the delay, but cap at 30 seconds.
                    delay = std::min(delay * 2, std::chrono::seconds{30});
                    attempt();
                });
            });
    };

    // Bind the cancellation signal to the socket's async operations.
    sock->cancel(cancel_signal);
    attempt();
}

```

### Cancellation Support

The `range_connect_op` and `iterator_connect_op` classes in [`include/asio/impl/connect.hpp`](https://github.com/chriskohlhoff/asio/blob/main/include/asio/impl/connect.hpp) inherit from `base_from_cancellation_state`, meaning they respect `cancellation_type::partial` and `cancellation_type::terminal`. You can use `asio::cancellation_signal` to gracefully abort reconnection attempts.

### Dynamic Endpoint Refresh

For long-running applications, resolve fresh endpoints before retrying instead of reusing the original `endpoints` object. Inside the timer's completion handler, call `resolver.async_resolve` again and pass the new results to `async_connect`.

### Logging with Connect Conditions

Provide a custom **connect condition** to log each endpoint attempt before connection:

```cpp
struct logging_condition {
    bool operator()(const asio::error_code&, const tcp::endpoint& ep) const {
        std::cout << "Trying " << ep << "\n";
        return true;   // Continue with this endpoint.
    }
};

asio::async_connect(socket, endpoints, logging_condition(),
    [](const asio::error_code& ec, const tcp::endpoint& ep) { /* … */ });

```

The condition is cheap because the library uses empty-base optimization in `base_from_connect_condition` to fold it away when using the default condition.

## Key Source Files in the ASIO Repository

| File | Description |
|------|-------------|
| [`include/asio/connect.hpp`](https://github.com/chriskohlhoff/asio/blob/main/include/asio/connect.hpp) | Public API for `connect` and `async_connect` (both range- and iterator-based overloads). Contains documentation, signatures, and initiation helpers. |
| [`include/asio/impl/connect.hpp`](https://github.com/chriskohlhoff/asio/blob/main/include/asio/impl/connect.hpp) | Core implementation defining `detail::range_connect_op`, `detail::iterator_connect_op`, and the internal logic for iterating through endpoints. |
| [`include/asio/basic_socket.hpp`](https://github.com/chriskohlhoff/asio/blob/main/include/asio/basic_socket.hpp) | Declaration of `basic_socket` and its member `async_connect` that the composed operation ultimately calls. |
| [`include/asio/steady_timer.hpp`](https://github.com/chriskohlhoff/asio/blob/main/include/asio/steady_timer.hpp) | Timer class used for back-off and retry scheduling. |
| [`include/asio/cancellation_signal.hpp`](https://github.com/chriskohlhoff/asio/blob/main/include/asio/cancellation_signal.hpp) | Provides cancellation support that can be bound to sockets or timers. |

## Summary

- `asio::async_connect` is a composed operation that automatically iterates through endpoint sequences, simplifying reconnection logic.
- The implementation uses `detail::range_connect_op` and `detail::iterator_connect_op` in [`include/asio/impl/connect.hpp`](https://github.com/chriskohlhoff/asio/blob/main/include/asio/impl/connect.hpp) to manage the connection loop.
- Implement reconnection by scheduling an `asio::steady_timer` in the error handler and re-invoking `async_connect` after the delay.
- Use `std::shared_ptr` to manage socket and timer lifetimes across retry attempts.
- Support advanced features like exponential back-off, cancellation via `cancellation_signal`, and dynamic endpoint refresh by combining timers with resolver operations.

## Frequently Asked Questions

### How does async_connect handle multiple endpoints?

`async_connect` attempts each endpoint in the provided sequence until one succeeds or all fail. The internal `range_connect_op` class stores the current iterator and issues `socket_.async_connect(*iter, ...)` for each endpoint, automatically advancing to the next on failure. This happens inside the composed operation, so your handler only receives the final result after all endpoints have been exhausted.

### Can I cancel an ongoing reconnection attempt?

Yes. The operation classes inherit from `base_from_cancellation_state` and respect ASIO's cancellation types. Create an `asio::cancellation_signal`, bind it to your socket using `sock->cancel(cancel_signal)`, and call `cancel_signal.emit()` to abort the current `async_connect` or timer wait. This is useful for graceful shutdowns when your application needs to exit.

### What is the difference between range-based and iterator-based async_connect?

The range-based overload accepts any container with `begin()`/`end()` (like the result of `tcp::resolver::resolve`), while the iterator-based overload accepts two iterators defining a sequence. Both eventually call the same underlying implementation in [`include/asio/impl/connect.hpp`](https://github.com/chriskohlhoff/asio/blob/main/include/asio/impl/connect.hpp), but the range-based version handles index tracking automatically, whereas the iterator-based version works with arbitrary forward iterators.

### Should I reuse endpoint results or resolve fresh addresses on retry?

For TCP connections, reusing the original `resolver::results_type` is efficient for immediate retries. However, for long-lived services or when dealing with dynamic DNS, resolve fresh endpoints before retrying by calling `resolver.async_resolve` inside your timer handler. This ensures you connect to currently available hosts if the IP addresses have changed since the last attempt.