Manejo Asincrónico de Señales UNIX con Boost.Asio

Introducción a las señales UNIX

En sistemas UNIX, las señales son un mecanismo de comunicación asíncrona entre procseos. La biblioteca Boost.Asio ofrece la clase signal_set para gestionar estas señales de forma asíncrona, integrándose con operaciones de E/S no bloqueantes.

Clase signal_set

La definición básica de la clase es la siguiente:

class signal_set {
public:
    explicit signal_set(io_service& servicio_io);
    signal_set(io_service& servicio_io, int senal1, ...);
    void agregar(int numero_senal);
    void eliminar(int numero_senal);
    void limpiar();
    void cancelar();
    void espera_asincrona(manejador_senales gestor);
};

El constructor requiere un objeto io_service para operaciones asíncronas. El método cancelar() anula todos los manejadores registrados, pasándoles el error boost::asio::error::operacion_abortada. Con espera_asincrona(), se registra un gestor de forma no bloqueante; su firma es:

void gestor(const error_code& codigo_error, int numero_senal);

Ejemplo básico de manejo de señales

using namespace boost::asio;
io_service servicio;
signal_set conjunto_senales(servicio, SIGINT, SIGUSR1);

auto gestor1 = [&](const error_code& ec, int senal) {
    if (ec) {
        std::cout << "Error: " << ec.message() << std::endl;
        return;
    }
    if (senal == SIGINT) {
        std::cout << "Gestor1 recibió señal: " << senal << std::endl;
    }
};

using tipo_gestor = void(const error_code&, int);
std::function<tipo_gestor> gestor2 = [&](const error_code& ec, int senal) {
    if (senal == SIGUSR1) {
        std::cout << "Gestor2 recibió señal: " << senal << std::endl;
    }
};

conjunto_senales.espera_asincrona(gestor1);
conjunto_senales.espera_asincrona(gestor2);
servicio.run();
std::cout << "Ciclo de eventos finalizado" << std::endl;

Al enviar la señal SIGUSR1 (por ejemplo, con kill -10 PID), se ejecuta el gestor2. Para capturar señales continuamente, se debe re-registrar el gestor dentro de sí mismo:

std::function<tipo_gestor> gestor_continuo = [&](const error_code& ec, int senal) {
    // Procesar la señal...
    conjunto_senales.espera_asincrona(gestor_continuo);
};

Para habilitar el registro de seguimiento, defina BOOST_ASIO_ENABLE_HANDLER_TRACKING antes de incluir la biblioteca.

Manejo de tiempo de espera

Se puede usar un temporizador para operaciones con límite de tiempo:

steady_timer temporizador(servicio, std::chrono::milliseconds(500));
temporizador.espera_asincrona(
    [&](const error_code& ec) {
        // Lógica al expirar el tiempo...
    }
);

Comunicación con flujo TCP

Para un servidor TCP basado en flujo:

io_service servicio_io;
ip::tcp::endpoint punto_fin(ip::tcp::v4(), 6688);
ip::tcp::acceptor aceptador(servicio_io, punto_fin);
while (true) {
    ip::tcp::iostream flujo_tcp;
    aceptador.accept(*flujo_tcp.rdbuf());
    flujo_tcp << "Salida desde servidor TCP";
}

El cliente se conecta y lee datos:

for (int i = 0; i < 3; ++i) {
    ip::tcp::iostream flujo_tcp("127.0.0.1", "6688");
    std::string linea;
    std::getline(flujo_tcp, linea);
    std::cout << "Recibido: " << linea << std::endl;
}

Comunicación UDP sin conexión

El protocolo UDP utiliza direcciones de extremo para envío y recepción directos.

Servidor UDP:

int main() {
    io_service servicio;
    ip::udp::socket socket_udp(servicio, ip::udp::endpoint(ip::udp::v4(), 6699));
    while (true) {
        std::array<char, 1> buffer;
        ip::udp::endpoint extremo_remoto;
        error_code ec;
        socket_udp.receive_from(boost::asio::buffer(buffer), extremo_remoto, 0, ec);
        if (!ec || ec == boost::asio::error::message_size) {
            std::cout << "Envío a: " << extremo_remoto.address() << std::endl;
            socket_udp.send_to(boost::asio::buffer("Respuesta UDP"), extremo_remoto);
        }
    }
    return 0;
}

Cliente UDP:

int main() {
    io_service servicio;
    ip::udp::endpoint extremo_envio(ip::address::from_string("127.0.0.1"), 6699);
    ip::udp::socket socket_udp(servicio, ip::udp::v4());
    std::array<char, 1> dato_envio = {0};
    socket_udp.send_to(boost::asio::buffer(dato_envio), extremo_envio);

    std::vector<char> datos_recibidos(100, 0);
    ip::udp::endpoint extremo_recepcion;
    socket_udp.receive_from(boost::asio::buffer(datos_recibidos), extremo_recepcion);
    std::cout << "Recibido de " << extremo_recepcion.address() << ": "
              << datos_recibidos.data() << std::endl;
    return 0;
}

Descriptores de archivos UNIX

Boost.Asio permite operaciones asíncronas en descriptores UNIX, como entradas estándar, usando posix::stream_descriptor.

using namespace boost::asio;
io_service servicio;
posix::stream_descriptor descriptor(servicio, STDIN_FILENO);
std::vector<char> buffer_lectura(30);

using tipo_gestor_fd = void(const error_code&, std::size_t);
std::function<tipo_gestor_fd> gestor_fd = [&](const error_code& ec, std::size_t bytes_leidos) {
    if (ec) return;
    if (bytes_leidos < buffer_lectura.size()) {
        buffer_lectura[bytes_leidos] = '\0';
    }
    std::cout << buffer_lectura.data();
    descriptor.async_read_some(buffer(buffer_lectura), gestor_fd);
};

descriptor.async_read_some(buffer(buffer_lectura), gestor_fd);
servicio.run();

También se pueden usar corutinas para simplificar el código:

io_service servicio;
posix::stream_descriptor descriptor(servicio, STDIN_FILENO);
std::vector<char> buffer_corutina(30);

spawn(servicio, [&](yield_context yield) {
    while (true) {
        error_code ec;
        std::size_t len = descriptor.async_read_some(buffer(buffer_corutina), yield[ec]);
        if (ec) break;
        if (len < buffer_corutina.size()) {
            buffer_corutina[len] = '\0';
        }
        std::cout << buffer_corutina.data();
    }
});
servicio.run();

Etiquetas: asio C++ boost-asio signals unix

Publicado el 6-8 03:47