Как асинхронно отправить данные на сервер, используя C++?

Ответ

В своих проектах я использовал Boost.Asio для асинхронного сетевого ввода-вывода. Вот пример асинхронной отправки данных по TCP:

#include <boost/asio.hpp>
#include <iostream>

using boost::asio::ip::tcp;

class AsyncSender {
public:
    AsyncSender(boost::asio::io_context& io_context, const std::string& host, const std::string& port)
        : resolver_(io_context), socket_(io_context) {
        connect(host, port);
    }

    void send_data(const std::string& data) {
        // Копируем данные, чтобы они жили до завершения операции
        auto shared_data = std::make_shared<std::string>(data);

        boost::asio::async_write(socket_, boost::asio::buffer(*shared_data),
            [this, shared_data](boost::system::error_code ec, std::size_t length) {
                if (!ec) {
                    std::cout << "Sent " << length << " bytes asynchronously.n";
                } else {
                    std::cerr << "Send failed: " << ec.message() << "n";
                }
            });
    }

private:
    void connect(const std::string& host, const std::string& port) {
        resolver_.async_resolve(host, port,
            [this](const boost::system::error_code& ec, tcp::resolver::results_type endpoints) {
                if (!ec) {
                    boost::asio::async_connect(socket_, endpoints,
                        [this](boost::system::error_code ec, const tcp::endpoint&) {
                            if (!ec) {
                                std::cout << "Connected!n";
                            }
                        });
                }
            });
    }

    tcp::resolver resolver_;
    tcp::socket socket_;
};

// Использование:
// boost::asio::io_context io;
// AsyncSender sender(io, "example.com", "http");
// sender.send_data("POST /data HTTP/1.1rn...");
// io.run(); // Запуск цикла обработки событий

Ключевые моменты:

  • Операция async_write не блокирует вызывающий поток.
  • Завершение операции обрабатывается в лямбда-функции (обратном вызове).
  • Необходимо гарантировать время жизни отправляемых данных до завершения операции (часто используют std::shared_ptr).
  • Для HTTP-запросов можно поверх Asio использовать библиотеки вроде Boost.Beast.

Ответ 18+ 🔞

Слушай, вот сидишь ты такой, пишешь свой код, и тут на тебе — надо сетевую хуйню сделать. Асинхронную, чтобы всё летало, а не тормозило. Ну, я в своих проектах брал Boost.Asio, потому что, ёпта, что ещё взять-то? Классная штука, хоть и мозги иногда выносит.

Вот смотри, как я асинхронную отправку по TCP делал. Главное — не обосраться с временем жизни данных, а то будет тебе хиросима, а не отправка.

#include <boost/asio.hpp>
#include <iostream>

using boost::asio::ip::tcp;

class AsyncSender {
public:
    AsyncSender(boost::asio::io_context& io_context, const std::string& host, const std::string& port)
        : resolver_(io_context), socket_(io_context) {
        connect(host, port);
    }

    void send_data(const std::string& data) {
        // Копируем данные, чтобы они жили до завершения операции
        auto shared_data = std::make_shared<std::string>(data);

        boost::asio::async_write(socket_, boost::asio::buffer(*shared_data),
            [this, shared_data](boost::system::error_code ec, std::size_t length) {
                if (!ec) {
                    std::cout << "Sent " << length << " bytes asynchronously.n";
                } else {
                    std::cerr << "Send failed: " << ec.message() << "n";
                }
            });
    }

private:
    void connect(const std::string& host, const std::string& port) {
        resolver_.async_resolve(host, port,
            [this](const boost::system::error_code& ec, tcp::resolver::results_type endpoints) {
                if (!ec) {
                    boost::asio::async_connect(socket_, endpoints,
                        [this](boost::system::error_code ec, const tcp::endpoint&) {
                            if (!ec) {
                                std::cout << "Connected!n";
                            }
                        });
                }
            });
    }

    tcp::resolver resolver_;
    tcp::socket socket_;
};

// Использование:
// boost::asio::io_context io;
// AsyncSender sender(io, "example.com", "http");
// sender.send_data("POST /data HTTP/1.1rn...");
// io.run(); // Запуск цикла обработки событий

А теперь, блядь, ключевые моменты, которые в голове держать надо:

  • Эта операция async_write — она не блокирует поток. То есть твоя программа не встанет колом, как дурак, пока данные прутся по проводам. Удивление пиздец, да?
  • Вся магия творится в этих лямбдах, колбэках, или как там их. Там ты и обрабатываешь, что получилось — улетело или накрылось медным тазом.
  • Вот это — самый важный пункт, ебать мои старые костыли. Данные, которые ты отправляешь, должны жить до тех пор, пока операция не завершится. Asio их просто так не скопирует. Поэтому все умные ребята заворачивают строку в std::shared_ptr. Иначе будет тебе пизда рулю — прочитаешь из очищенной памяти какую-нибудь дичь.
  • Если тебе надо HTTP, а не голый TCP, то поверх Asio есть Boost.Beast. Это уже, блядь, ёперный театр, но зато мощно. Но это уже другая история, там своих граблей овердохуища.

Короче, принцип такой: написал запрос, бросил в асинхронную очередь, и пошёл дальше код гнуть. А когда операция сделается — тебе твоя же лямбда отзовётся и скажет, мол, чувак, всё окей или пошло всё в пизду. Главное — за временем жизни данных следи, а то сам от себя охуеешь, когда начнёт падать в случайные моменты.