Boost.Asio: асинхронный ввод-вывод

Тема дорожной карты · Modern C++ Developer

Boost.Asio — самая распространённая библиотека для сетевого и асинхронного программирования на C++. На ней построены Boost.Beast, множество серверов, торговых систем и игровых бэкендов; её модель executors легла в основу стандартного предложения по networking. Asio даёт единый переносимый интерфейс к epoll, kqueue и IOCP, позволяя писать серверы на десятки тысяч соединений без потока на каждое из них. Этот узел посвящён внутреннему устройству Asio, а не Boost в целом.

Как это работает

Центральный объект — io_context: очередь готовых обработчиков и обёртка над системным мультиплексором. Асинхронная операция (async_read, async_write, async_accept, async_wait у таймера) регистрирует интерес к событию и немедленно возвращает управление; когда операционная система сообщает о готовности, Asio кладёт обработчик завершения в очередь, а поток, вызвавший io_context::run(), выполняет его. Сколько потоков вызывают run(), столько и обработчиков может исполняться параллельно.

Обработчик завершения — это токен (completion token), и Asio поддерживает несколько его форм: обычный колбэк с сигнатурой (error_code, size_t), use_future для получения std::future, а с C++20 — use_awaitable, превращающий вызов в корутину co_await socket.async_read_some(buffer, use_awaitable). Корутины делают асинхронный код похожим на синхронный и избавляют от «лапши» колбэков.

Для защиты состояния без мьютексов служит strand — исполнитель, гарантирующий, что связанные с ним обработчики никогда не выполняются одновременно. Буферы описываются через const_buffer/mutable_buffer и не владеют памятью: программист обязан сохранять буфер живым до завершения операции. Ошибки возвращаются через boost::system::error_code, а не исключения, если использовать соответствующую перегрузку.

Когда применять

Asio — правильный выбор для любого сетевого сервиса на C++, где важна масштабируемость: HTTP/WebSocket-серверы, прокси, брокеры сообщений, клиенты бирж, шлюзы для IoT-устройств. Библиотека покрывает не только TCP и UDP, но и таймеры, последовательные порты, сигналы ОС и Unix-сокеты, поэтому её используют как общий событийный цикл приложения. Для простых утилит с одним соединением синхронный API Asio тоже удобен. Если нужен HTTP-клиент «здесь и сейчас», проще взять libcurl или cpp-httplib; если нужны gRPC — его собственный рантайм. Автономная версия Asio без Boost подходит проектам, которые не хотят тянуть весь Boost.

Типичные ошибки

Самая частая — разрушение объекта, пока на нём висит незавершённая операция: обработчик приходит в уже освобождённую память. Идиома shared_from_this() внутри обработчиков держит соединение живым. Вторая — временный буфер: async_write(socket, buffer(std::string("hi")), ...) передаёт указатель на строку, которая уничтожится до отправки. Третья — блокирующие вызовы или тяжёлые вычисления внутри обработчика, останавливающие весь событийный цикл; их нужно уносить в отдельный пул через post. Четвёртая — забытый executor_work_guard: run() завершается, как только очередь пустеет. И наконец, параллельные async_write на одном сокете без очереди перемешивают байты — нужна очередь исходящих сообщений и strand.

Связанные понятия

Полезные ресурсы

🔤 Английские термины в этой теме

Слова из этой темы, которые русскоязычные часто произносят неверно. Кнопка 🔊 произносит голосом браузера.

Весь словарь терминов →