Introducción a Fast DDS: Desarrollo de una Aplicación de Publicación y Suscripción en C++

Conceptos Fundamentales de DDS y RTPS

DDS (Data Distribution Service) es un estándar middleware para sistemas distribuidos basado en datos, diseñado para permitir la comunicación eficiente entre aplicaciones distribuidas. Opera bajo el modelo DCPS (Data-Centric Publish-Subscribe), donde los componentes se comunican mediante temas (topics) sin necesidad de conocer directamente a sus contrapartes.

Los tres elementos centrales del modelo DCPS son:

  • Publicador (Publisher): Entidad que crea y gestiona DataWriter para enviar datos.
  • Suscriptor (Subscriber): Entidad que aloja DataReader para recibir datos de interés.
  • Tema (Topic): Canal lógico que vincula publicadores y suscriptores, identificado por nombre y tipo de dato.

Cada entidad DDS se configura mediante políticas de Calidad de Servicio (QoS), que definen comportamientos como persistencia, fiabilidad, latencia y durabilidad de los mensajes.

RTPS: El Protocolo detrás de DDS

El protocolo RTPS (Real-Time Publish-Subscribe) es la capa de transporte subyacente que soporta DDS. Está optimizado para redes UDP/IP, aunque también admite TCP y memoria compartida (SHM). RTPS opera dentro de dominios lógicos aislados, identificados por un domain ID, lo que permite múltiples espacios de comunicación independientes incluso en la misma red física.

Dentro de un dominio, los participantes (RTPSParticipant) intercambian datos a través de endpoints:

  • RTPSWriter: Emite cambios sobre un tema.
  • RTPSReader: Recibe y almacena cambios relevantes.

Los datos se transfieren como "cambios" (changes), que representan actualizaciones individuales. Ambos extremos mantienen un historial local para gestionar retransmisiones, confirmaciones y coherencia temporal.

Desarrollo de una Aplicación Básica en C++

A continuación se describe cómo construir una aplicación completa con un publicador y un suscriptor usando eProsima Fast DDS.

Estructura del Proyecto

La estructura final del espacio de trabajo será:


workspace_HelloWorld/
├── src/
│   ├── HelloWorld.idl
│   ├── HelloWorld.h
│   ├── HelloWorld.cxx
│   ├── HelloWorldPubSubTypes.h
│   ├── HelloWorldPubSubTypes.cxx
│   ├── PublisherApp.cpp
│   └── SubscriberApp.cpp
└── build/
    └── (archivos generados por CMake)

Paso 1: Definición del Tipo de Datos

Se utiliza un archivo IDL (Interface Definition Language) para definir la estructura de datos que se compartirá:

struct HelloWorld {
    unsigned long index;
    string message;
};

Este archivo se procesa con fastddsgen para generar código C++ compatible con C++11:

fastddsgen -typeros2 HelloWorld.idl

El comando genera automáticamente las clases necesarias para serialización, manejo de tipos y QoS.

Paso 2: Configuración del Entorno de Construcción

Un archivo CMakeLists.txt inicial configura el proyecto:

cmake_minimum_required(VERSION 3.12)
project(HelloWorldComm)

find_package(fastrtps REQUIRED)
find_package(fastcdr REQUIRED)

set(CMAKE_CXX_STANDARD 11)

file(GLOB SOURCES "src/*.cxx" "src/*.cpp")

add_executable(PublisherApp src/PublisherApp.cpp ${SOURCES})
target_link_libraries(PublisherApp fastrtps fastcdr)

add_executable(SubscriberApp src/SubscriberApp.cpp ${SOURCES})
target_link_libraries(SubscriberApp fastrtps fastcdr)

Paso 3: Implementación del Publicador

El publicador sigue este flujo:

  1. Crear un DomainParticipant con un ID de dominio.
  2. Registrar el tipo de dato personalizado (HelloWorld).
  3. Crear un Topic asociado a ese tipo.
  4. Instanciar un Publisher y un DataWriter.
  5. Enviar datos cuando haya al menos un suscriptor emparejado.

Un ejemplo simplificado del bucle principal:

bool PublisherApp::publish() {
    if (_listener.matched > 0) {
        _sample.index(_sample.index() + 1);
        _writer->write(&_sample);
        return true;
    }
    return false;
}

Paso 4: Implementación del Suscriptor

El suscriptor realiza pasos simétricos:

  1. Crear un DomainParticipant.
  2. Registrar el mismo tipo de dato.
  3. Crear el mismo Topic.
  4. Instanciar un Subscriber y un DataReader con un listener.
  5. Procesar datos entrantes mediante el callback on_data_available.

El listener maneja eventos clave:

void SubscriberListener::on_data_available(DataReader* reader) {
    SampleInfo info;
    if (reader->take_next_sample(&msg, &info) == RETCODE_OK && info.valid_data) {
        std::cout << "Recibido: " << msg.message() 
                  << " [índice: " << msg.index() << "]" << std::endl;
    }
}

Paso 5: Compilación y Ejecución

Desde el directorio build:

cmake ..
make
./PublisherApp
./SubscriberApp

Al ejecutar ambos procesos, el suscriptor detectará al publicador, se establecerá la conexión y comenzará el flujo de mensajes.

Consideraciones Finales

Fast DDS proporciona una implementación de alto rendimiento del estándar DDS, ideal para entornos de tiempo real y críticos. La separación clara entre tipos de datos, QoS y lógica de negocio permite diseñar sistemas flexibles y escalables. Este ejemplo básico puede extenderse fácilmente con políticas avanzadas de QoS, seguridad, monitoreo o integración con ROS 2.

Etiquetas: Fast-DDS DDS RTPS C++ Middleware

Publicado el 8-3 01:12