Fast DDS之domain和participant

Domain

DDS中的Domain是由一个整数定义,通过在创建DomainParticipant时指定。这是一种逻辑概念而非物理概念。
Domain域代表一个独立的通信网络。它在共享相同通信基础设施的实体之间创建了逻辑上的分离。在概念上,它可以被视为链接运行在同一域上的所有应用程序,并将它们与运行在不同域的应用程序隔离的虚拟网络。这样,一些独立的分布式应用程序可以在同一物理网络中共存,而不会相互干扰,甚至不需要互相感知。

每个domain有一个唯一的uint32的domainId,由应用程序在创建DomainParticipant时指定。当一个应用程序需要加入domain时就需要使用这个domainId创建一个DomainParticipant。

每个DomainParticipant都会把它所在的DomainId作为其属性。当两个DomainParticipant尝试进行通信时,他们的通讯层会首先检查这两个DomainParticipant是否处于同一Domain(即比较他们的DomainId是否相同)。只有当他们位于相同的Domain时,他们才能够进行通信。如果他们的DomainId不同,那么他们就无法直接通信,这就实现了不同Domain之间的隔离。

这种隔离机制可以确保同一物理网络内的不同DDS应用能够独立运行,彼此不受干扰。无论是数据的传输,还是资源的分配,都是在各自的Domain内进行的,不会影响到其他Domain。

在这里插入图片描述

DomainParticipant

每个DomainParticipant只归属于一个Domain,包含所有和domain相关的实体。同时它也是Publisher,Subscriber和Topic的生产工厂。
DomainParticipant的创建使用DomainParticipantFactory这个单例。
使用DomainParticipant的GuidPrefix_t来区分是否在同一主机运行。同一主机中两个participants的GuidPrefix_t的前四个字节是相同的。可以通过API:is_on_same_host_as() 来检查这一条件。

用途

  1. 作为所有Entity的容器的对象
  2. Publisher, Subscriber, Topic, and MultiTopic Entity对象的工厂
  3. Participant(即DomainParticipant)代表应用程序在一个通信平面上的参与,这个通信平面能够将运行在相同物理计算机集上的应用程序彼此隔离。一个域(Domain)建立了一个“虚拟网络”,链接所有共享相同DomainId的应用程序,并将其与运行在不同域的应用程序隔离开来。通过这种方式,几个独立的分布式应用程序可以在同一个物理网络中并存,而不会相互干扰,甚至无需知道彼此的存在。所以,Participant在DDS中的作用不仅局限于数据的发布和订阅,还包括在分布式环境中提供应用程序隔离。
  4. DomainParticipant 提供了对Domain的管理服务。它提供了一些操作,允许应用程序在本地“忽略”有关特定参与者(ignore_participant)、发布(ignore_publication)、订阅(ignore_subscription)或主题(ignore_topic)的任何信息。
    “忽略”操作的功能是,可以让应用程序选择性地忽略某些特定的数据,即使这些数据在其所在的域内。这样,应用程序就可以筛选出它真正关心的数据,不必处理所有的数据,从而提高了处理的效率。

对第四点的补充:
这些操作都是通过DomainParticipant的接口提供的,应用程序可以通过调用这些接口来执行相应的操作。例如,如果一个应用程序不关心某个特定的主题,那么它就可以调用ignore_topic接口,以后就不会再接收到有关这个主题的任何数据。

这种机制不仅可以提高数据处理的效率,还可以提高系统的灵活性,因为它允许应用程序根据其实际需求来选择处理哪些数据。

GUID_t的创建

GUID是RTPS协议的一部分,GUID_t结构体的定义也位于RTPS层,由两部分组成:前缀guidPrefixentityId。所有由同一个participant创建的实体(如发布者、订阅者、数据写者、数据读者等)都共享相同的guidPrefixentityId则是用来在同一个参与者下唯一地标识一个实体。GUID_t定义如下:

struct RTPS_DllAPI GUID_t
{
    //!Guid prefix
    GuidPrefix_t guidPrefix;
    //!Entity id
    EntityId_t entityId;
    // ...
};

其中,guidPrefix占12个字节,entityId占4个字节;guidPrefix在抓包中显示分为三个部分:hostId, appId, instancdId, 下面代码说明
《抓包.png》

创建流程:
DomainParticiapnt构造时创建GUID_t

participant_id_ = qos_.wire_protocol().participant_id;
if (!eprosima::fastrtps::rtps::RTPSDomainImpl::create_participant_guid(participant_id_, guid_))
{
    EPROSIMA_LOG_ERROR(DOMAIN_PARTICIPANT, "Error generating GUID for participant");
}

分析这段代码:

  1. 首先创建participant_id_participant_id_是从qos配置中读取,qos中默认为-1;进入RTPSDomainImpl::create_participant_guid函数后,会判断participant_id_如果为-1,就自动创建新的id,默认从0开始,依次累加,如果qos中指定但和其他id冲突,就会直接报错导致无法生成guid:
uint32_t RTPSDomainImpl::getNewId()
{
    uint32_t i = 0;
    while (m_RTPSParticipantIDs[i].reserved || m_RTPSParticipantIDs[i].used)
    {
        ++i;
    }
    m_RTPSParticipantIDs[i].reserved = true;
    return i;
}
  1. participant_id创建完成之后开始创建GuidPrefix_tGuidPrefix_t的构造分两部分进行:
    第一部分,前8个字节,在GuidUtils这个单例构造时创建
GuidUtils() {
        prefix_.value[0] = c_VendorId_eProsima[0];
        prefix_.value[1] = c_VendorId_eProsima[1];

        uint16_t host_id = SystemInfo::instance().host_id();
        prefix_.value[2] = static_cast<octet>(host_id & 0xFF);
        prefix_.value[3] = static_cast<octet>((host_id >> 8) & 0xFF);

        int pid = SystemInfo::instance().process_id();
        prefix_.value[4] = static_cast<octet>(pid & 0xFF);
        prefix_.value[5] = static_cast<octet>((pid >> 8) & 0xFF);

        std::random_device generator;
        std::uniform_int_distribution<uint16_t> distribution(0, std::numeric_limits<uint16_t>::max());
        uint16_t rand_value = distribution(generator);
        prefix_.value[6] = static_cast<octet>(rand_value & 0xFF);
        prefix_.value[7] = static_cast<octet>((rand_value >> 8) & 0xFF);
    }

判断是否为同一个进程可以通过guid的前8个字节(在服务发现中均通过这种方式判断):

bool GuidPrefix_t::is_on_same_process_as(
        const GuidPrefix_t& other_guid_prefix) const
{
    return memcmp(value, other_guid_prefix.value, 8) == 0;
}

GuidPrefix_t的后四个字节由participant_id组成

   void guid_prefix_create(
            uint32_t participant_id,
            GuidPrefix_t& guid_prefix) const
    {
        // Use precalculated vendor-host-process part of the prefix
        std::copy(prefix_.value, prefix_.value + 8, guid_prefix.value);

        // Add little endian serialization of participant_id
        guid_prefix.value[8] = static_cast<octet>(participant_id & 0xFF);
        guid_prefix.value[9] = static_cast<octet>((participant_id >> 8) & 0xFF);
        guid_prefix.value[10] = static_cast<octet>((participant_id >> 16) & 0xFF);
        guid_prefix.value[11] = static_cast<octet>((participant_id >> 24) & 0xFF);
    }
  1. GuidPrefix_t创建完成之后开始创建entityID,对participant来说entityID是固定值:c_EntityId_RTPSParticipant,Fast DDS中定义为:#define ENTITYID_RTPSParticipant 0x000001c1

GuidPrefix_t中提供方法用于判断是否是同一进程,这在服务发现时用到:

bool GuidPrefix_t::is_on_same_process_as(
        const GuidPrefix_t& other_guid_prefix) const
{
    return memcmp(value, other_guid_prefix.value, 8) == 0;
}

即通过GuidPrefix_t的前8个字节判断是否是同一进程内的通信。

GUID_t 用途总结

  • 第0和第1个字节是vendorID,表示实现DDS协议的供应商的ID,不同的供应商有不同的值;第2和第3个字节是host_id,这个值是通过ip地址得到的,同一台主机这两个字段值相同,所以可以通过这两个字段判断是否在同一主机,这4个字节组成抓包中的hostId(可以判断是否是同一主机)
  • 第4和第5个字节是进程id,同一进程有相同的id;第6和第7个字节是随机生成的,这四个字节组成抓包中的appId
  • 第8到第11字节由participant_id经过位运算组成instanceId(可以判断是否是同一participant)
  • 最后四字节为entityid,唯一标识同一participant中的不同entity

每个位于同一participant中的entity(Publisher, Subscriber, DataWriter, DataReader, Topic)都共享participantGuidPrefix_t

创建

DomainParticipantFactory* factory = DomainParticipantFactory::get_instance();
participant_ = factory->create_participant(0, pqos); // 第一个参数domainid和第二个参数qos必选,listener和statusmask可选
if(nullptr == participant_with_default_attributes) {
    // Error
    return;
}

基于Profile创建, domainId和profile name是必须的,listener和StatusMask是可选的:

// First load the XML with the profiles
DomainParticipantFactory::get_instance()->load_XML_profiles_file("profiles.xml");

// Create a DomainParticipant using a profile and no Listener
DomainParticipant* participant_with_profile =
        DomainParticipantFactory::get_instance()->create_participant_with_profile(0, "participant_profile");
if (nullptr == participant_with_profile) {
    return;
}

删除

只有当所有属于这个participant的entities(Publisher,Subscriber,Topic)都被删除时可以删除DomainParticipant,否则报错。可以通过 delete_contained_entities() 函数删除所有entities。

if (participant->delete_contained_entities() != ReturnCode_t::RETCODE_OK) {
    // DomainParticipant failed to delete the entities it created.
    return;
}

// Delete the DomainParticipant
if (DomainParticipantFactory::get_instance()->delete_participant(participant) != ReturnCode_t::RETCODE_OK) {
    // Error
    return;
}

DomainParticipantFactory

单例类,通过get_instance()获取对象实例,唯一的目的是用于创建和销毁DomainParticipant对象实例;通过DomainParticipantFactoryQos配置Qos,Qos只能通过Domain Participant Factory::set_qos()来设置

DomainParticipantQos

DomainParticipant的行为可以通过DomainParticipantQos的Qos值来修改,Qos值可以在创建时设置,也可以后续使用DomainParticipant::set_qos()函数来设置。默认值为PARTICIPANT_QOS_DEFAULT。也可以使用 set_default_participant_qos() 函数设置qos。

DomainParticipant的Qos有以下内容:
UserDataQosPolicy user_data()
EntityFactoryQosPolicy entity_factory()
ParticipantResourceLimitsQos allocation()
PropertyPolicyQos properties()
WireProtocolConfigQos wire_protocol()
TransportConfigQos transport() and setup_transports()
FlowControllersQos flow_controllers()
ThreadSettings builtin_controllers_sender_thread()
ThreadSettings timed_events_thread()
ThreadSettings discovery_server_thread()
ThreadSettings security_log_thread()

DomainParticipantListener

DomainPariticipant有一个DomainParticipantListener的抽象类,用户可以继承这个抽象类实现响应函数用于通知DomainParticipant实例状态的改变。DomainParticipantListener继承自TopicListener,PublisherListener和SubscriberListener,因此它可以监听任何实体的事件通知。
当有事件发生时,总是先通知处理该事件的具体的监听器,因此DomainParticipantListener从其他监听器继承的回调函数只有在没有其他实体能够处理该事件的情况下会被调用(该实体没有附加监听器u后者StatusMask禁用了回调)

有以下回调函数:

  • on_participant_discovery():服务发现发现participant
  • on_subscriber_discovery():服务发现过程中发现新的Subscriber
  • on_publisher_discovery():服务发现过程中发现新的Publisher
  • on_type_discovery()
  • on_type_dependencies_reply()
  • on_type_information_received()

Partitions

todo

RTPSParticipant

RTPSParticipant的构造大概分为以下几部分:

内置transport的创建

单播和组播初始化

参数默认值:

// 默认值
uint16_t portBase;           // 7400.
uint16_t domainIDGain;       // 250.
uint16_t participantIDGain;  // 2.
uint16_t offsetd0;           // 0.
uint16_t offsetd1;           // 10.
uint16_t offsetd2;           // 1.
uint16_t offsetd3;           // 11.

组播端口值:uint32_t port = portBase + domainIDGain * domainId + offsetd0;
单播端口值:uint32_t port = portBase + domainIDGain * domainId + offsetd1 + participantIDGain * RTPSParticipantID;
组播IP地址:DEFAULT_METATRAFFIC_MULTICAST_ADDRESS = "239.255.0.1",通过函数getDefaultMetatrafficMulticastLocators获取;
单播IP地址,被设置为0:

bool UDPv4Transport::getDefaultMetatrafficUnicastLocators(
        LocatorList& locators,
        uint32_t metatraffic_unicast_port) const
{
    Locator locator;
    locator.kind = LOCATOR_KIND_UDPv4;
    locator.port = static_cast<uint16_t>(metatraffic_unicast_port);
    locator.set_Invalid_Address(); // 这里被初始化为0
    locators.push_back(locator);

    return true;
}

接着开始调用NormalizeLocator

LocatorList UDPv4Transport::NormalizeLocator(
        const Locator& locator)
{
    LocatorList list;

    if (IPLocator::isAny(locator))  // 单播都是0,所以可以继续
    {
        std::vector<IPFinder::info_IP> locNames;
        get_ipv4s(locNames, false, false);
        for (const auto& infoIP : locNames)
        {
            auto ip = asio::ip::address_v4::from_string(infoIP.name);
            if (is_interface_allowed(ip))
            {
                Locator newloc(locator);
                // 这里获取本地ip地址并最终存放到m_att.builtin.metatrafficUnicastLocatorList变量中
                IPLocator::setIPv4(newloc, infoIP.locator); 
                list.push_back(newloc);
            }
        }
        if (list.empty())
        {
            Locator newloc(locator);
            IPLocator::setIPv4(newloc, "127.0.0.1");
            list.push_back(newloc);
        }
    }
    else
    {
        list.push_back(locator);
    }

    return list;
}

通过对NormalizeLocator函数的调用,最终确定组播地址为239.255.0.1,单播地址为获取的本地地址,比如我这里docker环境中的地址是172.17.0.3;同时,将m_att.builtin.initialPeersList值初始化为组播地址值:

// Initial peers
if (m_att.builtin.initialPeersList.empty())
{
    m_att.builtin.initialPeersList = m_att.builtin.metatrafficMulticastLocatorList;
}

通过对函数get_default_unicast_locators()的调用,将默认的单播地址也初始化为本地地址;

然后开始通过createReceiverResources函数创建接收资源:

  1. 创建ReceiverResource对象(每个locator对应一个ReceiverResource
  2. 每个ReceiverResource对象对应创建一个MessageReceiver对象,并作为ReceiverResource对象的一个属性保存
  3. 将创建好的ReceiverResource实例添加到RTPSParticipantImplm_receiverResourcelist字段中。

创建send buffer

创建SendBuffersManager对象

// Create buffer pool
send_buffers_.reset(new SendBuffersManager(num_send_buffers, allow_growing_buffers));
send_buffers_->init(this);

FlowController对象的创建

FlowControllerFactory对象在初始化时,构造了三个或四个FlowControllerImpl相关对象:

  1. FlowControllerImpl<FlowControllerPureSyncPublishMode, FlowControllerFifoSchedule>,一直使用同步方式发送数据;
  2. FlowControllerImpl<FlowControllerSyncPublishMode, FlowControllerFifoSchedule>:尝试以同步方式发送数据,即发送一条消息后会等待确认收到该消息的反馈再发送下一条。如果收到反馈需要花费大量时间,它会自动切换到异步模式,以避免阻塞数据的发送
  3. FlowControllerImpl<FlowControllerAsyncPublishMode, FlowControllerFifoSchedule>:异步模式发送数据
  4. FASTDDS_STATISTICS模式下异步发送数据

这些对象将会在创建DataWriter时使用

内置协议的创建

第一部分,服务发现 请移步至 服务发现
第二部分,心跳 请移步至 Fast DDS中的保活机制

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值