ASIO для C++: полное руководство с примерами

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Введение в библиотеку ASIO для C++

ASIO (Asynchronous Input/Output) — это кроссплатформенная библиотека для C++, предоставляющая мощные средства для асинхронного ввода-вывода. Она позволяет разработчикам создавать высокопроизводительные сетевые приложения, работающие с сокетами, таймерами, последовательными портами и другими источниками асинхронных событий. Библиотека является основой для многих современных C++ проектов, включая Boost.ASIO (часть Boost) и standalone ASIO (независимая версия).

Зачем нужна ASIO?

В традиционном синхронном программировании каждый вызов блокирует поток до завершения операции. Это неэффективно для I/O-bound задач (работа с сетью, файлами). ASIO решает эту проблему, предоставляя:

  • Асинхронность: Операции запускаются и выполняются в фоне, не блокируя основной поток.
  • Масштабируемость: Один поток может обслуживать тысячи одновременных соединений (модель Reactor/Proactor).
  • Кроссплатформенность: Работает на Windows (IOCP), Linux (epoll), macOS (kqueue) и других ОС.
  • Интеграция с C++: Использует современные возможности C++11/14/17/20 (лямбды, умные указатели, корутины).

Установка ASIO

ASIO существует в двух формах: как часть Boost (Boost.ASIO) и как standalone-библиотека. Для standalone версии установка проста:

Вариант 1: Заголовочная версия (Header-only)

Скачайте последнюю версию с официального сайта и распакуйте. Подключите в проекте:

#define ASIO_STANDALONE
#include "asio.hpp"

В CMakeLists.txt:

add_definitions(-DASIO_STANDALONE)
include_directories(/path/to/asio/include)

Вариант 2: Через пакетный менеджер (vcpkg)

vcpkg install asio

Вариант 3: Boost.ASIO

#include <boost/asio.hpp>
// Линковка: -lboost_system -lpthread (на Linux)

Важно: Для компиляции примеров потребуется флаг -lpthread на Linux/macOS и линковка с ws2_32.lib на Windows.

Быстрый старт: Первый асинхронный таймер

Начнем с классического примера — асинхронный таймер, который выводит сообщение через 5 секунд:

#define ASIO_STANDALONE
#include <iostream>
#include "asio.hpp"

int main() {
    asio::io_context io; // Объект, управляющий асинхронными операциями

    asio::steady_timer timer(io, asio::chrono::seconds(5));
    
    // Асинхронное ожидание с лямбда-функцией
    timer.async_wait([&](const asio::error_code& ec) {
        if (!ec) {
            std::cout << "Прошло 5 секунд!" << std::endl;
        }
    });

    std::cout << "Запуск таймера..." << std::endl;
    io.run(); // Блокирует выполнение, пока есть асинхронные операции
    return 0;
}

Объяснение:

  • asio::io_context — ядро библиотеки, управляет очередью событий.
  • asio::steady_timer — таймер, работающий в монотонном времени.
  • async_wait — запускает асинхронное ожидание. Когда таймер срабатывает, вызывается переданная функция.
  • io.run() — запускает цикл обработки событий. Блокирует поток до завершения всех операций.

Основные методы и функции ASIO (10 штук)

1. asio::io_context::run()

Что делает: Запускает основной цикл обработки событий. Блокирует текущий поток, обрабатывая все асинхронные операции, пока не будут выполнены все обработчики или не будет вызван stop().

Параметры: Нет.

Возвращает: Количество выполненных обработчиков.

Пример:

asio::io_context io;
io.post([](){ std::cout << "Hello"; });
io.post([](){ std::cout << " World"; });
std::size_t count = io.run(); // Выведет "Hello World", count = 2
std::cout << " Выполнено обработчиков: " << count;

2. asio::io_context::post()

Что делает: Добавляет функцию в очередь обработчиков для асинхронного выполнения в контексте io_context. Не требует запуска I/O операции.

Параметры: Функция или вызываемый объект (лямбда, std::function).

Пример:

asio::io_context io;
io.post([](){
    std::cout << "Это выполнится асинхронно!" << std::endl;
});
io.run(); // Обязательно вызвать для выполнения

3. asio::steady_timer::async_wait()

Что делает: Асинхронно ожидает истечения таймера. После срабатывания вызывает переданный обработчик с кодом ошибки.

Параметры: Обработчик вида void(const asio::error_code&).

Пример:

asio::steady_timer timer(io, asio::chrono::seconds(1));
timer.async_wait([](const asio::error_code& ec){
    if (!ec) std::cout << "Таймер сработал!";
});

4. asio::steady_timer::expires_after()

Что делает: Устанавливает новое время истечения таймера относительно текущего момента.

Параметры: Продолжительность (std::chrono::duration).

Пример:

asio::steady_timer timer(io);
timer.expires_after(std::chrono::seconds(3)); // Сработает через 3 секунды
timer.async_wait(handler);

5. asio::ip::tcp::socket::async_connect()

Что делает: Асинхронно устанавливает TCP-соединение с удаленным хостом.

Параметры: Конечная точка (endpoint), обработчик void(const asio::error_code&).

Пример:

asio::ip::tcp::socket sock(io);
asio::ip::tcp::endpoint ep(
    asio::ip::address::from_string("93.184.216.34"), 80); // example.com
sock.async_connect(ep, [](const asio::error_code& ec){
    if (!ec) std::cout << "Подключено!";
});

6. asio::ip::tcp::socket::async_read_some()

Что делает: Асинхронно читает часть данных из сокета в буфер. Не гарантирует чтение полного запрошенного объема.

Параметры: Буфер (mutable_buffer), обработчик void(const asio::error_code&, size_t).

Пример:

char data[1024];
socket.async_read_some(asio::buffer(data), 
    [](const asio::error_code& ec, size_t len){
        if (!ec) std::cout << "Прочитано " << len << " байт";
    });

7. asio::async_read() (свободная функция)

Что делает: Асинхронно читает точное количество байт из потока (сокета). Использует CompletionToken (лямбда, корутина).

Параметры: Поток (Stream), буфер (MutableBufferSequence), CompletionToken.

Пример:

char data[128];
asio::async_read(socket, asio::buffer(data, 128),
    [](const asio::error_code& ec, size_t len){
        if (!ec) std::cout << "Прочитано ровно 128 байт";
    });

8. asio::async_write() (свободная функция)

Что делает: Асинхронно записывает точное количество байт в поток.

Параметры: Поток, буфер (ConstBufferSequence), CompletionToken.

Пример:

std::string msg = "Hello, ASIO!";
asio::async_write(socket, asio::buffer(msg),
    [](const asio::error_code& ec, size_t len){
        if (!ec) std::cout << "Отправлено " << len << " байт";
    });

9. asio::ip::tcp::acceptor::async_accept()

Что делает: Асинхронно принимает входящее TCP-соединение.

Параметры: Сокет для нового соединения, обработчик void(const asio::error_code&).

Пример:

asio::ip::tcp::acceptor acceptor(io, 
    asio::ip::tcp::endpoint(asio::ip::tcp::v4(), 8080));
asio::ip::tcp::socket socket(io);
acceptor.async_accept(socket, [](const asio::error_code& ec){
    if (!ec) std::cout << "Новое соединение!";
});

10. asio::strand<Executor>::wrap()

Что делает: Оборачивает обработчик в strand — гарантирует, что обработчики, обернутые в один strand, не будут выполняться параллельно. Критично для thread-safe приложений.

Параметры: Обработчик.

Пример:

asio::io_context io;
asio::strand<asio::io_context::executor_type> strand(io.get_executor());

// Безопасно для многопоточности
socket.async_read_some(asio::buffer(data), 
    strand.wrap([](auto& ec, size_t len){ /* обработка */ }));

Реальный практический пример: Простой HTTP-сервер

Создадим асинхронный HTTP-сервер, который отвечает "Hello World" на любой запрос. Это демонстрирует комбинацию acceptor, socket, async_read и async_write.

#define ASIO_STANDALONE
#include <iostream>
#include <memory>
#include <string>
#include "asio.hpp"

using asio::ip::tcp;

class Session : public std::enable_shared_from_this<Session> {
public:
    Session(tcp::socket socket) : socket_(std::move(socket)) {}

    void start() {
        do_read();
    }

private:
    void do_read() {
        auto self(shared_from_this());
        socket_.async_read_some(asio::buffer(data_, max_length),
            [this, self](asio::error_code ec, size_t length) {
                if (!ec) {
                    do_write(length);
                }
            });
    }

    void do_write(size_t length) {
        auto self(shared_from_this());
        std::string response = "HTTP/1.1 200 OK\r\nContent-Length: 12\r\n\r\nHello World!";
        asio::async_write(socket_, asio::buffer(response),
            [this, self](asio::error_code ec,                    

Рекомендаций