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();