Este artículo detalla la implementación de un servidor HTTP utilizando las bibliotecas Beast y Boost.Asio de C++. Se enfoca en la gestión de conexiones entrantes y el inicio del ciclo de vida de las mismas.
1. Configuración del Aceptador y el Contexto de E/S
Primero, definimos la estructura básica de nuestra clase CServer. Esta clase se encargará de aceptar las conexiones entrantes. Se incluyen los miembros necesarios para el aceptaodr, el contexto de E/S de Boost.Asio y el socket para las nuevas conexiones.
#include <boost/asio.hpp>
#include <boost/beast.hpp>
#include <memory>
#include <iostream>
namespace net = boost::asio;
using tcp = net::ip::tcp;
namespace beast = boost::beast;
// Forward declaration
class HttpConnection;
class CServer : public std::enable_shared_from_this<CServer>
{
public:
CServer(net::io_context& ioc, unsigned short port);
void StartAccepting(); // Renamed for clarity
private:
tcp::acceptor m_acceptor;
net::io_context& m_ioc;
tcp::socket m_socket;
};
2. Inicio del Ciclo de Aceptación de Conexiones
La función StartAccepting inicia el proceso asíncrono de aceptación de nuevas conexiones. Utiliza async_accept para esperar una conexión entrante. Cuando una conexión es establecida o ocurre un error, se ejecuta una función de callback (un lambda en este caso).
Dentro del callback, se manejan los posibles errores. Si la conexión es exitosa, se crea una nueva instancia de HttpConnection (que se encargará del manejo de la petición HTTP) y se le transfiere la propiedad del socket. El socket original m_socket se reutiliza para aceptar la siguiente conexión. Es crucial para la gestión de la vida útil del objeto CServer usar shared_from_this() dentro de los callbacks asíncronos para asegurar que la instancia del servidor no sea destruida prematuramente.
// Constructor implementation
CServer::CServer(net::io_context& ioc, unsigned short port)
: m_ioc(ioc),
m_acceptor(ioc, tcp::endpoint(tcp::v4(), port)),
m_socket(ioc)
{
std::cout << "Server listening on port " << port << std::endl;
}
// Function to start accepting connections
void CServer::StartAccepting()
{
auto self = shared_from_this(); // Ensure 'this' object is alive
m_acceptor.async_accept(m_socket,
[self](beast::error_code ec, tcp::endpoint endpoint) {
if (!ec)
{
// Connection successful, transfer socket ownership to HttpConnection
// and start processing the request.
std::make_shared<HttpConnection>(std::move(self->m_socket), self->m_ioc)->StartHandling();
}
else
{
std::cerr << "Accept error: " << ec.message() << std::endl;
}
// Continue accepting new connections regardless of success or failure
self->StartAccepting();
});
}
// Example placeholder for HttpConnection class
class HttpConnection : public std::enable_shared_from_this<HttpConnection>
{
public:
HttpConnection(tcp::socket socket, net::io_context& ioc)
: m_socket(std::move(socket)), m_ioc(ioc) {}
void StartHandling() {
// Logic to read HTTP request, process it, and send response
std::cout << "New connection accepted." << std::endl;
// Example: Read request
auto shared_self = shared_from_this();
beast::flat_buffer buffer;
m_socket.async_read_some(net::buffer(buffer.data(), buffer.size()),
[shared_self, &buffer](beast::error_code ec, std::size_t bytes_transferred) {
if (!ec) {
std::cout << "Read " << bytes_transferred << " bytes." << std::endl;
// Process buffer data...
// For now, just close the connection
beast::error_code ignored_ec;
shared_self->m_socket.shutdown(tcp::socket::shutdown_both, ignored_ec);
} else {
std::cerr << "Read error: " << ec.message() << std::endl;
}
});
}
private:
tcp::socket m_socket;
net::io_context& m_ioc;
};
3. Transferencia de Socket y Creación de Conexión HTTP
La línea clave std::make_shared<HttpConnection>(std::move(self->m_socket), self->m_ioc)->StartHandling(); realiza varias acciones importantes:
- Crea una nueva instancia de
HttpConnectionutilizandostd::make_shared, gestionándola con un puntero inteligente. - Transfiere la propiedad del
m_socket(que ahora contiene la conexión establecida) a la nueva instancia deHttpConnectionusandostd::move. Esto es necesario porqueasync_acceptcompleta la conexión en el socket proporcionado, y este socket debe ser manejado por el objeto de conexión. - Llama al método
StartHandling()en la nueva instancia deHttpConnectionpara iniciar el procesamiento de la solicitud HTTP.
Después de iniciar el manejo de la conexión actual, la llamada recursiva self->StartAccepting(); asegura que el servidor esté listo para aceptar la próxima conexión inmediatamente.