Long polling vs WebSocket в C++: Битва за реальное время
Представьте: вы пишете чат на C++, биржевой терминал или систему мониторинга серверов. Клиент должен получать данные мгновенно. HTTP-запросы «спросил-ответил» не подходят — слишком медленно и расточительно. Выхода два: Long polling (старая школа) и WebSocket (современный стандарт). Что выбрать для C++ проекта? Давайте разбираться на реальных примерах.
Как работает Long polling в C++ (и почему он жив до сих пор)
Long polling — это эмуляция real-time поверх обычного HTTP. Клиент отправляет запрос, сервер не отвечает сразу, а «висит» до появления новых данных или таймаута. Как только данные появляются — сервер шлёт ответ, клиент сразу отправляет новый запрос. Создаётся иллюзия непрерывного потока.
В C++ такой подход часто реализуют через libcurl на клиенте и любой HTTP-сервер (например, cpp-httplib или Boost.Beast) на сервере. Вот как выглядит классическая серверная часть на Boost.Beast:
#include <boost/beast/core.hpp>
#include <boost/beast/http.hpp>
#include <boost/beast/version.hpp>
#include <boost/asio.hpp>
#include <queue>
#include <mutex>
#include <condition_variable>
namespace beast = boost::beast;
namespace http = beast::http;
namespace net = boost::asio;
class LongPollHandler {
std::queue<std::string> messages_;
std::mutex mtx_;
std::condition_variable cv_;
public:
// Потокобезопасный метод добавления сообщения
void push(const std::string& msg) {
std::lock_guard<std::mutex> lock(mtx_);
messages_.push(msg);
cv_.notify_one();
}
// Обработчик Long Polling запроса
void handle(http::request<http::string_body>& req,
http::response<http::string_body>& res) {
std::unique_lock<std::mutex> lock(mtx_);
// Ждём новое сообщение или таймаут 30 секунд
if (cv_.wait_for(lock, std::chrono::seconds(30),
[this](){ return !messages_.empty(); })) {
std::string msg = messages_.front();
messages_.pop();
res.result(http::status::ok);
res.body() = msg;
} else {
// Таймаут — возвращаем пустой ответ
res.result(http::status::no_content);
res.body() = "";
}
res.prepare_payload();
}
};
Клиент на libcurl будет выглядеть так:
#include <curl/curl.h>
#include <string>
#include <iostream>
size_t write_callback(void* contents, size_t size, size_t nmemb, void* userp) {
((std::string*)userp)->append((char*)contents, size * nmemb);
return size * nmemb;
}
void long_poll_loop(CURL* curl, const std::string& url) {
std::string response;
while (true) {
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 35L); // чуть больше серверного таймаута
CURLcode res = curl_easy_perform(curl);
if (res == CURLE_OK && !response.empty()) {
std::cout << "Получено: " << response << std::endl;
response.clear();
// сразу шлём новый запрос
continue;
}
// При ошибке ждём 5 секунд и повторяем
std::this_thread::sleep_for(std::chrono::seconds(5));
}
}
Плюсы Long polling в C++: Простота, совместимость со всеми прокси и фаерволлами, не требует специальных библиотек. Минусы: Огромное количество HTTP-заголовков, задержки на установку соединения (TCP handshake при каждом запросе), сервер держит много потоков в ожидании.
WebSocket в C++: Настоящий real-time на Boost.Beast
WebSocket — это полноценный двунаправленный канал поверх TCP. После рукопожатия (Upgrade-запрос) клиент и сервер обмениваются фреймами без оверхеда HTTP. Для C++ лучшая библиотека — Boost.Beast (часть Boost.Asio). Она даёт полный контроль над сокетами и асинхронностью.
Пример WebSocket-сервера на C++ с использованием Boost.Beast:
#include <boost/beast/core.hpp>
#include <boost/beast/websocket.hpp>
#include <boost/asio.hpp>
#include <memory>
#include <string>
#include <iostream>
namespace beast = boost::beast;
namespace websocket = beast::websocket;
namespace net = boost::asio;
using tcp = net::ip::tcp;
// Обработчик одного подключения
class WebSocketSession : public std::enable_shared_from_this<WebSocketSession> {
websocket::stream<beast::tcp_stream> ws_;
beast::flat_buffer buffer_;
public:
explicit WebSocketSession(tcp::socket&& socket)
: ws_(std::move(socket)) {}
void run() {
// Асинхронно принимаем WebSocket рукопожатие
ws_.async_accept(
beast::bind_front_handler(&WebSocketSession::on_accept, shared_from_this()));
}
void on_accept(beast::error_code ec) {
if (ec) return;
// Начинаем читать сообщения
do_read();
}
void do_read() {
ws_.async_read(buffer_,
beast::bind_front_handler(&WebSocketSession::on_read, shared_from_this()));
}
void on_read(beast::error_code ec, std::size_t bytes_transferred) {
if (ec) return;
// Эхо-ответ: отправляем полученное сообщение обратно
ws_.text(ws_.got_text());
ws_.async_write(buffer_.data(),
beast::bind_front_handler(&WebSocketSession::on_write, shared_from_this()));
}
void on_write(beast::error_code ec, std::size_t bytes_transferred) {
if (ec) return;
buffer_.consume(buffer_.size());
do_read(); // читаем следующее сообщение
}
// Метод для отправки данных из другого потока (например, из бизнес-логики)
void send(const std::string& msg) {
ws_.text(true);
ws_.write(net::buffer(msg));
}
};
// Асинхронный слушатель
class WebSocketServer {
net::io_context& ioc_;
tcp::acceptor acceptor_;
public:
WebSocketServer(net::io_context& ioc, short port)
: ioc_(ioc), acceptor_(ioc, tcp::endpoint(tcp::v4(), port)) {
do_accept();
}
void do_accept() {
acceptor_.async_accept(
[this](beast::error_code ec, tcp::socket socket) {
if (!ec) {
std::make_shared<WebSocketSession>(std::move(socket))->run();
}
do_accept();
});
}
};
Клиентская часть на C++ (Boost.Beast):
#include <boost/beast/core.hpp>
#include <