• DDS:domain


    domain域层架构

    域代表一个单独的通信平面。它在共享公共通信基础设施的实体之间创建了逻辑分离。从概念上讲,它可以看作是一个虚拟网络,连接在同一域上运行的所有应用程序,并将它们与运行在不同域上的应用程序隔离开来。这样,多个独立的分布式应用程序可以在同一个物理网络中共存,而不会相互干扰,甚至不会相互感知。

    每个域都有一个唯一的标识符,称为 domainId,它被实现为一个uint32值。共享此 domainId 的应用程序属于同一个域并且能够通信。

    对于要添加到域的应用程序,它必须创建 具有适当 domainId的DomainParticipant实例。DomainParticipant 的实例是通过 DomainParticipantFactory单例创建的。

    分区在域中引入了另一个实体隔离级别。虽然 DomainParticipant 如果它们在同一个域中将能够相互通信,但仍然可以隔离它们的Publishers和 Subscribers并将它们分配给不同的Partitions。
    在这里插入图片描述

    域参与者

    DomainParticipant是应用程序到域的入口点。每个 DomainParticipant 从创建时就链接到单个域,并包含与该域相关的所有实体。它还充当Publisher、Subscriber 和Topic的工厂。

    DomainParticipant 的行为可以使用 DomainParticipantQos 上指定的 QoS 值进行修改。QoS 值可以在创建 DomainParticipant 时设置,或者稍后使用DomainParticipant::set_qos()成员函数进行修改。

    作为一个实体,DomainParticipant 接受一个DomainParticipantListener ,它将在 DomainParticipant 实例上收到状态更改通知。

    域参与者Qos

    DomainParticipantQos控制 DomainParticipant 的行为。它在内部包含以下QosPolicy对象:
    在这里插入图片描述
    可以使用 DomainParticipant::set_qos()成员函数修改先前创建的 DomainParticipant 的 QoS 值。尝试在已启用的 DomainParticipant 上修改不可变的 QosPolicy 将导致错误。在这种情况下,不会应用任何更改,并且 DomainParticipant 将保留其先前的 DomainParticipantQos。

    // Create a DomainParticipant with default DomainParticipantQos
    DomainParticipant* participant =
            DomainParticipantFactory::get_instance()->create_participant(0, PARTICIPANT_QOS_DEFAULT);
    if (nullptr == participant)
    {
        // Error
        return;
    }
    
    // Get the current QoS or create a new one from scratch
    DomainParticipantQos qos = participant->get_qos();
    
    // Modify QoS attributes
    qos.entity_factory().autoenable_created_entities = false;
    
    // Assign the new Qos to the object
    participant->set_qos(qos);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    默认 DomainParticipantQos

    默认的 DomainParticipantQos 是指DomainParticipantFactoryget_default_participant_qos()单例上的成员函数 返回的值 。该特殊值可用作 QoS 参数 或成员函数,以指示应使用当前默认的 DomainParticipantQos。PARTICIPANT_QOS_DEFAULTcreate_participant()DomainParticipant::set_qos()

    系统启动时,默认的 DomainParticipantQos 等价于默认的构造值DomainParticipantQos()。可以随时使用 set_default_participant_qos() DomainParticipantFactory 单例上的成员函数修改默认 DomainParticipantQos。修改默认 DomainParticipantQos 不会影响已经存在的 DomainParticipant 实例。

    // Get the current QoS or create a new one from scratch
    DomainParticipantQos qos_type1 = DomainParticipantFactory::get_instance()->get_default_participant_qos();
    
    // Modify QoS attributes
    // (...)
    
    // Set as the new default TopicQos
    if (DomainParticipantFactory::get_instance()->set_default_participant_qos(qos_type1) !=
            ReturnCode_t::RETCODE_OK)
    {
        // Error
        return;
    }
    
    // Create a DomainParticipant with the new default DomainParticipantQos.
    DomainParticipant* participant_with_qos_type1 =
            DomainParticipantFactory::get_instance()->create_participant(0, PARTICIPANT_QOS_DEFAULT);
    if (nullptr == participant_with_qos_type1)
    {
        // Error
        return;
    }
    
    // Get the current QoS or create a new one from scratch
    DomainParticipantQos qos_type2;
    
    // Modify QoS attributes
    // (...)
    
    // Set as the new default TopicQos
    if (DomainParticipantFactory::get_instance()->set_default_participant_qos(qos_type2) !=
            ReturnCode_t::RETCODE_OK)
    {
        // Error
        return;
    }
    
    // Create a Topic with the new default TopicQos.
    DomainParticipant* participant_with_qos_type2 =
            DomainParticipantFactory::get_instance()->create_participant(0, PARTICIPANT_QOS_DEFAULT);
    if (nullptr == participant_with_qos_type2)
    {
        // Error
        return;
    }
    
    // Resetting the default DomainParticipantQos to the original default constructed values
    if (DomainParticipantFactory::get_instance()->set_default_participant_qos(PARTICIPANT_QOS_DEFAULT)
            != ReturnCode_t::RETCODE_OK)
    {
        // Error
        return;
    }
    
    // The previous instruction is equivalent to the following
    if (DomainParticipantFactory::get_instance()->set_default_participant_qos(DomainParticipantQos())
            != ReturnCode_t::RETCODE_OK)
    {
        // Error
        return;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61

    set_default_participant_qos() 成员函数也接受该值PARTICIPANT_QOS_DEFAULT 作为输入参数。这会将当前默认 DomainParticipantQos 重置为默认构造值 DomainParticipantQos()

    // Create a custom DomainParticipantQos
    DomainParticipantQos custom_qos;
    
    // Modify QoS attributes
    // (...)
    
    // Create a DomainParticipant with a custom DomainParticipantQos
    
    DomainParticipant* participant = DomainParticipantFactory::get_instance()->create_participant(0, custom_qos);
    if (nullptr == participant)
    {
        // Error
        return;
    }
    
    // Set the QoS on the participant to the default
    if (participant->set_qos(PARTICIPANT_QOS_DEFAULT) != ReturnCode_t::RETCODE_OK)
    {
        // Error
        return;
    }
    
    // The previous instruction is equivalent to the following:
    if (participant->set_qos(DomainParticipantFactory::get_instance()->get_default_participant_qos())
            != ReturnCode_t::RETCODE_OK)
    {
        // Error
        return;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    该值PARTICIPANT_QOS_DEFAULT具有不同的含义,具体取决于它的使用位置:

    create_participant(),DomainParticipant::set_qos()它指的是由返回的默认 DomainParticipantQos get_default_participant_qos()。

    set_default_participant_qos()它指的是默认构造 DomainParticipantQos()的.

    域参与者监听器

    DomainParticipantListener是一个抽象类,定义了将响应DomainParticipant上的状态更改而触发的回调。默认情况下,所有这些回调都是空的并且什么都不做。用户应实现此类的特化,覆盖应用程序所需的回调。未被覆盖的回调将保持其空实现。

    DomainParticipantListener 继承自TopicListener、 PublisherListener和SubscriberListener。因此,它有能力对报告给它的任何附属实体的每一种事件做出反应。由于事件总是被通知给可以处理事件的最具体的实体侦听器,因此只有在没有其他实体能够处理事件时才会调用 DomainParticipantListener 从其他侦听器继承的回调,因为它没有附加侦听器,或者因为StatusMask实体上的禁用回调。

    此外,DomainParticipantListener 添加了以下回调:

    on_participant_discovery(): 在同一个域中发现了一个新的 DomainParticipant,以前已知的 DomainParticipant 已被删除,或者某些 DomainParticipant 已更改其 QoS。

    on_subscriber_discovery():在同一个域中发现了一个新的订阅者,一个先前已知的订阅者已被删除,或者某个订阅者已经改变了它的 QoS。

    on_publisher_discovery():在同一个域中发现了一个新的Publisher ,以前已知的 Publisher 已被删除,或者某些 Publisher 已更改其 QoS。

    on_type_discovery(): 在同一个域中发现了一个新的数据类型。

    on_type_dependencies_reply():类型查找客户端收到了对getTypeDependencies()请求的重播。此回调可用于使用getTypes()请求检索新类型,并使用检索到的类型对象创建新的动态类型。

    on_type_information_received():TypeInformation已从新发现的 DomainParticipant 收到新消息。

    onParticipantAuthentication():通知远程域参与者的身份验证过程的结果(失败或成功)。

    class CustomDomainParticipantListener : public DomainParticipantListener
    {
    
    public:
    
        CustomDomainParticipantListener()
            : DomainParticipantListener()
        {
        }
    
        virtual ~CustomDomainParticipantListener()
        {
        }
    
        virtual void on_participant_discovery(
                DomainParticipant* /*participant*/,
                eprosima::fastrtps::rtps::ParticipantDiscoveryInfo&& info)
        {
            if (info.status == eprosima::fastrtps::rtps::ParticipantDiscoveryInfo::DISCOVERED_PARTICIPANT)
            {
                std::cout << "New participant discovered" << std::endl;
            }
            else if (info.status == eprosima::fastrtps::rtps::ParticipantDiscoveryInfo::REMOVED_PARTICIPANT ||
                    info.status == eprosima::fastrtps::rtps::ParticipantDiscoveryInfo::DROPPED_PARTICIPANT)
            {
                std::cout << "New participant lost" << std::endl;
            }
        }
    
    #if HAVE_SECURITY
        virtual void onParticipantAuthentication(
                DomainParticipant* /*participant*/,
                eprosima::fastrtps::rtps::ParticipantAuthenticationInfo&& info)
        {
            if (info.status == eprosima::fastrtps::rtps::ParticipantAuthenticationInfo::AUTHORIZED_PARTICIPANT)
            {
                std::cout << "A participant was authorized" << std::endl;
            }
            else if (info.status == eprosima::fastrtps::rtps::ParticipantAuthenticationInfo::UNAUTHORIZED_PARTICIPANT)
            {
                std::cout << "A participant failed authorization" << std::endl;
            }
        }
    
    #endif // if HAVE_SECURITY
    
        virtual void on_subscriber_discovery(
                DomainParticipant* /*participant*/,
                eprosima::fastrtps::rtps::ReaderDiscoveryInfo&& info)
        {
            if (info.status == eprosima::fastrtps::rtps::ReaderDiscoveryInfo::DISCOVERED_READER)
            {
                std::cout << "New subscriber discovered" << std::endl;
            }
            else if (info.status == eprosima::fastrtps::rtps::ReaderDiscoveryInfo::REMOVED_READER)
            {
                std::cout << "New subscriber lost" << std::endl;
            }
        }
    
        virtual void on_publisher_discovery(
                DomainParticipant* /*participant*/,
                eprosima::fastrtps::rtps::WriterDiscoveryInfo&& info)
        {
            if (info.status == eprosima::fastrtps::rtps::WriterDiscoveryInfo::DISCOVERED_WRITER)
            {
                std::cout << "New publisher discovered" << std::endl;
            }
            else if (info.status == eprosima::fastrtps::rtps::WriterDiscoveryInfo::REMOVED_WRITER)
            {
                std::cout << "New publisher lost" << std::endl;
            }
        }
    
        virtual void on_type_discovery(
                DomainParticipant* participant,
                const eprosima::fastrtps::rtps::SampleIdentity& request_sample_id,
                const eprosima::fastrtps::string_255& topic,
                const eprosima::fastrtps::types::TypeIdentifier* identifier,
                const eprosima::fastrtps::types::TypeObject* object,
                eprosima::fastrtps::types::DynamicType_ptr dyn_type)
        {
            (void)participant, (void)request_sample_id, (void)topic, (void)identifier, (void)object, (void)dyn_type;
            std::cout << "New data type discovered" << std::endl;
    
        }
    
        virtual void on_type_dependencies_reply(
                DomainParticipant* participant,
                const eprosima::fastrtps::rtps::SampleIdentity& request_sample_id,
                const eprosima::fastrtps::types::TypeIdentifierWithSizeSeq& dependencies)
        {
            (void)participant, (void)request_sample_id, (void)dependencies;
            std::cout << "Answer to a request for type dependencies was received" << std::endl;
        }
    
        virtual void on_type_information_received(
                DomainParticipant* participant,
                const eprosima::fastrtps::string_255 topic_name,
                const eprosima::fastrtps::string_255 type_name,
                const eprosima::fastrtps::types::TypeInformation& type_information)
        {
            (void)participant, (void)topic_name, (void)type_name, (void)type_information;
            std::cout << "New data type information received" << std::endl;
        }
    
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107

    域参与者工厂

    此类的唯一目的是允许创建和销毁DomainParticipant对象。 DomainParticipantFactory本身没有工厂,是一个单例对象,可以通过类get_instance()上的静态成员函数访问DomainParticipantFactory 。

    DomainParticipantFactory 的行为可以使用DomainParticipantFactoryQos上指定的 QoS 值进行修改。由于 DomainParticipantFactory 是一个单例,它的 QoS 只能通过 DomainParticipantFactory::set_qos()成员函数来修改。

    DomainParticipantFactory 不接受任何侦听器,因为它不是实体。

    DomainParticipantFactoryQos

    DomainParticipantFactoryQos 控制 DomainParticipantFactory 的行为。它在内部包含以下QosPolicy对象:
    在这里插入图片描述

    由于 DomainParticipantFactory 是一个单例,它的 QoS 只能通过 DomainParticipantFactory::set_qos()成员函数来修改。

    DomainParticipantFactoryQos qos;
    
    // Setting autoenable_created_entities to true makes the created DomainParticipants
    // to be enabled upon creation
    qos.entity_factory().autoenable_created_entities = true;
    if (DomainParticipantFactory::get_instance()->set_qos(qos) != ReturnCode_t::RETCODE_OK)
    {
        // Error
        return;
    }
    
    // Create a DomainParticipant with the new DomainParticipantFactoryQos.
    // The returned DomainParticipant is already enabled
    DomainParticipant* enabled_participant =
            DomainParticipantFactory::get_instance()->create_participant(0, PARTICIPANT_QOS_DEFAULT);
    if (nullptr == enabled_participant)
    {
        // Error
        return;
    }
    
    // Setting autoenable_created_entities to false makes the created DomainParticipants
    // to be disabled upon creation
    qos.entity_factory().autoenable_created_entities = false;
    if (DomainParticipantFactory::get_instance()->set_qos(qos) != ReturnCode_t::RETCODE_OK)
    {
        // Error
        return;
    }
    
    // Create a DomainParticipant with the new DomainParticipantFactoryQos.
    // The returned DomainParticipant is disabled and will need to be enabled explicitly
    DomainParticipant* disabled_participant =
            DomainParticipantFactory::get_instance()->create_participant(0, PARTICIPANT_QOS_DEFAULT);
    if (nullptr == disabled_participant)
    {
        // Error
        return;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    从 XML 文件加载配置文件

    要基于 XML 配置文件创建实体,必须首先加载包含此类配置文件的文件。

    如果配置文件在默认加载的文件之一中进行了描述,它将在初始化时自动可用。否则,load_XML_profiles_file()成员函数可用于加载 XML 中的配置文件。有关 XML 配置文件格式和自动加载的更多信息,请参阅 XML 配置文件部分。

    加载后,配置文件的名称可用于根据配置文件规范创建具有 QoS 设置的实体

    // Load the XML with the profiles
    DomainParticipantFactory::get_instance()->load_XML_profiles_file("profiles.xml");
    
    // Profiles can now be used to create Entities
    DomainParticipant* participant_with_profile =
            DomainParticipantFactory::get_instance()->create_participant_with_profile(0, "participant_profile");
    if (nullptr == participant_with_profile)
    {
        // Error
        return;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    创建域参与者

    DomainParticipant的创建是通过DomainParticipantFactorycreate_participant()单例 上的成员函数 完成的,该函数充当 DomainParticipant 的工厂。

    强制性论点是:

    标识将DomainId在其中创建 DomainParticipant 的域。

    描述DomainParticipant行为的DomainParticipantQos。如果提供TOPIC_QOS_DEFAULT的值为 ,则使用 DomainParticipantQos 的值。

    可选参数是:

    从DomainParticipantListener派生的侦听器,实现将触发的回调以响应 DomainParticipant 上的事件和状态更改。默认情况下使用空回调。

    StatusMask激活或停用触发 DomainParticipantListener 上的单个 回调的A。默认情况下,所有事件都已启用。

    create_participant() 如果在操作过程中出现错误,将返回一个空指针,例如,如果提供的 QoS 不兼容或不受支持。建议检查返回值是否为有效指针。

    // Create a DomainParticipant with default DomainParticipantQos and no Listener
    // The value PARTICIPANT_QOS_DEFAULT is used to denote the default QoS.
    DomainParticipant* participant_with_default_attributes =
            DomainParticipantFactory::get_instance()->create_participant(0, PARTICIPANT_QOS_DEFAULT);
    if (nullptr == participant_with_default_attributes)
    {
        // Error
        return;
    }
    
    // A custom DomainParticipantQos can be provided to the creation method
    DomainParticipantQos custom_qos;
    
    // Modify QoS attributes
    // (...)
    
    DomainParticipant* participant_with_custom_qos =
            DomainParticipantFactory::get_instance()->create_participant(0, custom_qos);
    if (nullptr == participant_with_custom_qos)
    {
        // Error
        return;
    }
    
    // Create a DomainParticipant with default QoS and a custom Listener.
    // CustomDomainParticipantListener inherits from DomainParticipantListener.
    // The value PARTICIPANT_QOS_DEFAULT is used to denote the default QoS.
    CustomDomainParticipantListener custom_listener;
    DomainParticipant* participant_with_default_qos_and_custom_listener =
            DomainParticipantFactory::get_instance()->create_participant(0, PARTICIPANT_QOS_DEFAULT,
                    &custom_listener);
    if (nullptr == participant_with_default_qos_and_custom_listener)
    {
        // Error
        return;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    基于配置文件的 DomainParticipant 创建

    除了使用 DomainParticipantQos 之外,配置文件的名称可用于创建具有 DomainParticipantFactory 单例上的create_participant_with_profile() 成员函数的DomainParticipant。

    强制性论点是:

    标识将DomainId在其中创建 DomainParticipant 的域。不要使用DomainId高于 200 的值(请参阅创建 DomainParticipant)。

    要应用于 DomainParticipant 的配置文件的名称。

    可选参数是:

    从DomainParticipantListener派生的侦听器,实现将触发的回调以响应 DomainParticipant 上的事件和状态更改。默认情况下使用空回调。

    StatusMask激活或停用触发 DomainParticipantListener 上的单个 回调的A。默认情况下,所有事件都已启用。

    create_participant_with_profile()如果在操作过程中出现错误,将返回一个空指针,例如,如果提供的 QoS 不兼容或不受支持。建议检查返回值是否为有效指针。

    XML 配置文件必须先前已加载。请参阅从 XML 文件加载配置文件。

    // 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)
    {
        // Error
        return;
    }
    
    // Create a DomainParticipant using a profile and a custom Listener.
    // CustomDomainParticipantListener inherits from DomainParticipantListener.
    CustomDomainParticipantListener custom_listener;
    DomainParticipant* participant_with_profile_and_custom_listener =
            DomainParticipantFactory::get_instance()->create_participant_with_profile(0, "participant_profile",
                    &custom_listener);
    if (nullptr == participant_with_profile_and_custom_listener)
    {
        // Error
        return;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    删除域参与者

    可以使用DomainParticipantFactory 单例delete_participant()上的成员函数 删除DomainParticipant。
    只有当属于参与者(发布者、订阅者或主题)的所有实体都已被删除时,才能删除 DomainParticipant。否则,该函数将发出错误,并且不会删除 DomainParticipant。这可以通过使用DomainParticipantdelete_contained_entities()的成员函数来 执行。

    // Create a DomainParticipant
    DomainParticipant* participant =
            DomainParticipantFactory::get_instance()->create_participant(0, PARTICIPANT_QOS_DEFAULT);
    if (nullptr == participant)
    {
        // Error
        return;
    }
    
    // Use the DomainParticipant to communicate
    // (...)
    
    // Delete entities created by the DomainParticipant
    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
  • 相关阅读:
    STC89C52单片机 启动!!!(三)
    力扣(LeetCode)20. 有效的括号(C++)
    OCR技术:解决图片转excel表格的方案与技巧
    自动化运维CICD
    三、Node.js模块化
    哪里有写毕业论文需要的外文文献?
    linux yum安装mysq8
    前端面试的话术集锦第 23 篇博文——高频考点(常考算法题解析)
    【Jenkins高级操作,欢迎阅读】
    WPS(WSC)中M1 到M8 图解
  • 原文地址:https://blog.csdn.net/neuzhangno/article/details/127775212