1.1。什么是 DDS?

数据分发服务 (DDS)是一种以 数据为中心的通信协议,用于分布式软件应用程序通信。它描述了支持数据提供者和数据消费者之间通信的通信应用程序编程接口 (API) 和通信语义。

由于它是一个以数据为中心的发布订阅 (DCPS) 模型,因此在其实现中定义了三个关键应用实体:发布实体,定义信息生成对象及其属性;订阅实体,它定义了信息消费对象及其属性;和配置实体,定义作为主题传输的信息类型,并使用其服务质量 (QoS) 属性创建发布者和订阅者,确保上述实体的正确性能。

DDS 使用 QoS 来定义 DDS 实体的行为特征。QoS 由单独的 QoS 策略(源自 QoSPolicy 的类型的对象)组成。这些在Policy中描述。

1.1.1。DCPS 概念模型

在 DCPS 模型中,为开发通信应用系统定义了四个基本要素。

出版商。它是负责创建和配置其实现的DataWriters的 DCPS 实体。DataWriter是负责实际发布消息的实体。每个人都有一个分配的主题,在该主题下发布消息。有关详细信息,请参阅发布者。

订户。它是 DCPS 实体,负责接收在其订阅的主题下发布的数据。它为一个或多个DataReader对象提供服务,这些对象负责将新数据的可用性传达给应用程序。有关详细信息,请参阅订阅者。

主题。它是绑定发布和订阅的实体。它在 DDS 域中是唯一的。通过TopicDescription,它允许发布和订阅的数据类型的统一。有关详细信息,请参阅主题。

域。这是用于链接所有发布者和订阅者的概念,属于一个或多个应用程序,它们在不同主题下交换数据。这些参与域的单个应用程序称为DomainParticipant。DDS 域由域 ID 标识。DomainParticipant 定义域 ID 以指定它所属的 DDS 域。具有不同 ID 的两个 DomainParticipants 不知道彼此在网络中的存在。因此,可以创建多个通信通道。这适用于涉及多个DDS应用程序的场景,它们各自的DomainParticipants相互通信,但这些应用程序不得干扰。域参与者充当其他 DCPS 实体的容器,充当 发布者、订阅者和主题实体的工厂,并在域中提供管理服务。有关详细信息,请参阅域。

1.2. 什么是 RTPS

为支持 DDS 应用程序而开发的实时发布订阅 (RTPS)协议是一种发布订阅通信中间件,它通过 UDP/IP 等尽力传输传输。此外,Fast DDS 还支持 TCP 和共享内存 (SHM) 传输。

它旨在支持单播和多播通信。

在继承自 DDS 的 RTPS 顶部,可以找到域,它定义了一个单独的通信平面。几个域可以同时独立地共存。一个域包含任意数量的RTPSParticipants,即能够发送和接收数据的元素。为此,RTPSParticipants 使用他们的Endpoints:

RTPSWriter:能够发送数据的端点。

RTPSReader:能够接收数据的端点。

RTPSParticipant 可以有任意数量的写入器和读取器端点。

通信围绕主题进行,主题定义和标记正在交换的数据。主题不属于特定参与者。参与者通过 RTPSWriters 对主题下发布的数据进行更改,并通过 RTPSReaders 接收与其订阅的主题相关的数据。通信单元称为Change,它表示在 Topic 下写入的数据的更新。 RTPSReaders/RTPSWriters在其History上注册这些更改,这是一种用作最近更改缓存的数据结构。

在eProsima Fast DDS的默认配置中,当您通过 RTPSWriter 端点发布更改时,会在后台执行以下步骤:

更改将添加到 RTPSWriter 的历史缓存中。

RTPSWriter 将更改发送到它知道的任何 RTPSReaders。

接收到数据后,RTPSReaders 用新的变化更新他们的历史缓存。

但是,Fast DDS 支持多种配置,允许您更改 RTPSWriters/RTPSReaders 的行为。修改 RTPS 实体的默认配置意味着 RTPSWriters 和 RTPSReaders 之间的数据交换流发生变化。此外,通过选择服务质量 (QoS) 策略,您可以通过多种方式影响这些历史缓存的管理方式,但通信循环保持不变。您可以继续阅读RTPS 层部分,了解更多关于快速 DDS 中 RTPS 协议的实现。

1.3. 编写一个简单的 C++ 发布者和订阅者应用程序

1.3.1。背景

DDS 是实现 DCPS 模型的以数据为中心的通信中间件。该模型基于发布者的开发,这是一个数据生成元素;和一个订阅者,一个数据消费元素。这些实体通过主题进行通信,主题是绑定两个 DDS 实体的元素。发布者在主题下生成信息,订阅者订阅同一主题以接收信息

1.3.2. 先决条件

首先,您需要按照安装手册中列出的步骤安装 eProsima Fast DDS及其所有依赖项。您还需要完成安装手册中列出的安装 eProsima Fast DDS-Gen工具的步骤。此外,本教程中提供的所有命令都针对 Linux 环境进行了概述。

1.3.4。导入链接库及其依赖项

DDS 应用程序需要 Fast DDS 和 Fast CDR 库。根据安装过程,使这些库可用于我们的 DDS 应用程序的过程将略有不同。在 Linux 上,可以在目录/usr/include/fastrtps/和 /usr/include/fastcdr/中分别找到 Fast DDS 和 Fast CDR 的头文件。两者的编译库都可以在目录/usr/lib/中找到

1.3.5。配置 CMake 项目

cmake_minimum_required(VERSION 3.12.4)if(NOT CMAKE_VERSION VERSION_LESS 3.0)cmake_policy(SET CMP0048 NEW)
endif()project(DDSHelloWorld)# Find requirements
if(NOT fastcdr_FOUND)find_package(fastcdr REQUIRED)
endif()if(NOT fastrtps_FOUND)find_package(fastrtps REQUIRED)
endif()# Set C++11
include(CheckCXXCompilerFlag)
if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_COMPILER_IS_CLANG ORCMAKE_CXX_COMPILER_ID MATCHES "Clang")check_cxx_compiler_flag(-std=c++11 SUPPORTS_CXX11)if(SUPPORTS_CXX11)add_compile_options(-std=c++11)else()message(FATAL_ERROR "Compiler doesn't support C++11")endif()
endif()

IDL通过生成的数据文件为:

这必须生成以下文件:HelloWorld.cxx:HelloWorld 类型定义。HelloWorld.h:HelloWorld.cxx 的头文件。HelloWorldPubSubTypes.cxx:HelloWorld 类型的序列化和反序列化代码。HelloWorldPubSubTypes.h:HelloWorldPubSubTypes.cxx 的头文件。

pub端的代码

// Copyright 2016 Proyectos y Sistemas de Mantenimiento SL (eProsima).2//3// Licensed under the Apache License, Version 2.0 (the "License");4// you may not use this file except in compliance with the License.5// You may obtain a copy of the License at6//7//     http://www.apache.org/licenses/LICENSE-2.08//9// Unless required by applicable law or agreed to in writing, software10// distributed under the License is distributed on an "AS IS" BASIS,11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.12// See the License for the specific language governing permissions and13// limitations under the License.1415/**16 * @file HelloWorldPublisher.cpp17 *18 */1920#include "HelloWorldPubSubTypes.h"2122#include <fastdds/dds/domain/DomainParticipantFactory.hpp>23#include <fastdds/dds/domain/DomainParticipant.hpp>24#include <fastdds/dds/topic/TypeSupport.hpp>25#include <fastdds/dds/publisher/Publisher.hpp>26#include <fastdds/dds/publisher/DataWriter.hpp>27#include <fastdds/dds/publisher/DataWriterListener.hpp>2829using namespace eprosima::fastdds::dds;3031class HelloWorldPublisher32{33private:3435    HelloWorld hello_;3637    DomainParticipant* participant_;3839    Publisher* publisher_;4041    Topic* topic_;4243    DataWriter* writer_;4445    TypeSupport type_;4647    class PubListener : public DataWriterListener48    {49    public:5051        PubListener()52            : matched_(0)53        {54        }5556        ~PubListener() override57        {58        }5960        void on_publication_matched(61                DataWriter*,62                const PublicationMatchedStatus& info) override63        {64            if (info.current_count_change == 1)65            {66                matched_ = info.total_count;67                std::cout << "Publisher matched." << std::endl;68            }69            else if (info.current_count_change == -1)70            {71                matched_ = info.total_count;72                std::cout << "Publisher unmatched." << std::endl;73            }74            else75            {76                std::cout << info.current_count_change77                        << " is not a valid value for PublicationMatchedStatus current count change." << std::endl;78            }79        }8081        std::atomic_int matched_;8283    } listener_;8485public:8687    HelloWorldPublisher()88        : participant_(nullptr)89        , publisher_(nullptr)90        , topic_(nullptr)91        , writer_(nullptr)92        , type_(new HelloWorldPubSubType())93    {94    }9596    virtual ~HelloWorldPublisher()97    {98        if (writer_ != nullptr)99        {
100            publisher_->delete_datawriter(writer_);
101        }
102        if (publisher_ != nullptr)
103        {
104            participant_->delete_publisher(publisher_);
105        }
106        if (topic_ != nullptr)
107        {
108            participant_->delete_topic(topic_);
109        }
110        DomainParticipantFactory::get_instance()->delete_participant(participant_);
111    }
112
113    //!Initialize the publisher
114    bool init()
115    {
116        hello_.index(0);
117        hello_.message("HelloWorld");
118
119        DomainParticipantQos participantQos;
120        participantQos.name("Participant_publisher");
121        participant_ = DomainParticipantFactory::get_instance()->create_participant(0, participantQos);
122
123        if (participant_ == nullptr)
124        {
125            return false;
126        }
127
128        // Register the Type
129        type_.register_type(participant_);
130
131        // Create the publications Topic
132        topic_ = participant_->create_topic("HelloWorldTopic", "HelloWorld", TOPIC_QOS_DEFAULT);
133
134        if (topic_ == nullptr)
135        {
136            return false;
137        }
138
139        // Create the Publisher
140        publisher_ = participant_->create_publisher(PUBLISHER_QOS_DEFAULT, nullptr);
141
142        if (publisher_ == nullptr)
143        {
144            return false;
145        }
146
147        // Create the DataWriter
148        writer_ = publisher_->create_datawriter(topic_, DATAWRITER_QOS_DEFAULT, &listener_);
149
150        if (writer_ == nullptr)
151        {
152            return false;
153        }
154        return true;
155    }
156
157    //!Send a publication
158    bool publish()
159    {
160        if (listener_.matched_ > 0)
161        {
162            hello_.index(hello_.index() + 1);
163            writer_->write(&hello_);
164            return true;
165        }
166        return false;
167    }
168
169    //!Run the Publisher
170    void run(
171            uint32_t samples)
172    {
173        uint32_t samples_sent = 0;
174        while (samples_sent < samples)
175        {
176            if (publish())
177            {
178                samples_sent++;
179                std::cout << "Message: " << hello_.message() << " with index: " << hello_.index()
180                            << " SENT" << std::endl;
181            }
182            std::this_thread::sleep_for(std::chrono::milliseconds(1000));
183        }
184    }
185};
186
187int main(
188        int argc,
189        char** argv)
190{
191    std::cout << "Starting publisher." << std::endl;
192    int samples = 10;
193
194    HelloWorldPublisher* mypub = new HelloWorldPublisher();
195    if(mypub->init())
196    {
197        mypub->run(static_cast<uint32_t>(samples));
198    }
199
200    delete mypub;
201    return 0;
202}

每一部分的功能:

DomainParticipantFactory. 允许创建和销毁 DomainParticipant 对象。DomainParticipant. 充当所有其他实体对象的容器以及发布者、订阅者和主题对象的工厂。TypeSupport. 为参与者提供序列化、反序列化和获取特定数据类型的密钥的功能。Publisher. 它是负责创建 DataWriters 的对象。DataWriter. 允许应用程序设置要在给定主题下发布的数据的值。DataWriterListener. 允许重新定义 DataWriterListener 的功能。

派生类的私有成员

类的私有数据成员,hello_数据成员被定义为 HelloWorld类的一个对象,它定义了我们用 IDL 文件创建的数据类型。接下来定义参与者、发布者、主题、DataWriter和数据类型对应的私有数据成员。类的type_对象TypeSupport是将用于在 DomainParticipant 中注册主题数据类型的对象

PubListener通过从该类继承来定义DataWriterListener该类。此类覆盖默认的 DataWriter 侦听器回调,允许在发生事件时执行例程。当检测到新的 DataReader 正在侦听 DataWriter 正在发布的主题时,重写的回调on_publication_matched() 允许定义一系列操作。检测与 DataWriter 匹配的 DataReader的info.current_count_change()这些更改。这是MatchedStatus允许跟踪订阅状态更改的结构中的成员。最后,listener_类的对象被定义为 的实例PubListener

类的公共构造函数和析构函数HelloWorldPublisher定义如下。构造函数将类的私有数据成员初始化nullptr为 ,TypeSupport 对象除外,它被初始化为HelloWorldPubSubType类的实例。类析构函数删除这些数据成员,从而清理系统内存

初始化函数

初始化 HelloWorld 类型hello_结构成员的内容。通过 DomainParticipant 的 QoS 为参与者分配名称。使用DomainParticipantFactory创建参与者。注册 IDL 中定义的数据类型。为出版物创建主题。创建发布者。使用先前创建的侦听器创建 DataWriter。

为了发布,实现了公共成员功能publish()。在 DataWriter 的侦听器回调中,表明 DataWriter 已与侦听发布主题的 DataReader 匹配,数据成员matched_被更新。它包含发现的 DataReader 的数量。因此,当发现第一个 DataReader 时,应用程序开始发布。这只是由 DataWriter 对象写入更改

sub订阅者

// Copyright 2016 Proyectos y Sistemas de Mantenimiento SL (eProsima).2//3// Licensed under the Apache License, Version 2.0 (the "License");4// you may not use this file except in compliance with the License.5// You may obtain a copy of the License at6//7//     http://www.apache.org/licenses/LICENSE-2.08//9// Unless required by applicable law or agreed to in writing, software10// distributed under the License is distributed on an "AS IS" BASIS,11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.12// See the License for the specific language governing permissions and13// limitations under the License.1415/**16 * @file HelloWorldSubscriber.cpp17 *18 */1920#include "HelloWorldPubSubTypes.h"2122#include <fastdds/dds/domain/DomainParticipantFactory.hpp>23#include <fastdds/dds/domain/DomainParticipant.hpp>24#include <fastdds/dds/topic/TypeSupport.hpp>25#include <fastdds/dds/subscriber/Subscriber.hpp>26#include <fastdds/dds/subscriber/DataReader.hpp>27#include <fastdds/dds/subscriber/DataReaderListener.hpp>28#include <fastdds/dds/subscriber/qos/DataReaderQos.hpp>29#include <fastdds/dds/subscriber/SampleInfo.hpp>3031using namespace eprosima::fastdds::dds;3233class HelloWorldSubscriber34{35private:3637    DomainParticipant* participant_;3839    Subscriber* subscriber_;4041    DataReader* reader_;4243    Topic* topic_;4445    TypeSupport type_;4647    class SubListener : public DataReaderListener48    {49    public:5051        SubListener()52            : samples_(0)53        {54        }5556        ~SubListener() override57        {58        }5960        void on_subscription_matched(61                DataReader*,62                const SubscriptionMatchedStatus& info) override63        {64            if (info.current_count_change == 1)65            {66                std::cout << "Subscriber matched." << std::endl;67            }68            else if (info.current_count_change == -1)69            {70                std::cout << "Subscriber unmatched." << std::endl;71            }72            else73            {74                std::cout << info.current_count_change75                        << " is not a valid value for SubscriptionMatchedStatus current count change" << std::endl;76            }77        }7879        void on_data_available(80                DataReader* reader) override81        {82            SampleInfo info;83            if (reader->take_next_sample(&hello_, &info) == ReturnCode_t::RETCODE_OK)84            {85                if (info.valid_data)86                {87                    samples_++;88                    std::cout << "Message: " << hello_.message() << " with index: " << hello_.index()89                                << " RECEIVED." << std::endl;90                }91            }92        }9394        HelloWorld hello_;9596        std::atomic_int samples_;9798    } listener_;99
100public:
101
102    HelloWorldSubscriber()
103        : participant_(nullptr)
104        , subscriber_(nullptr)
105        , topic_(nullptr)
106        , reader_(nullptr)
107        , type_(new HelloWorldPubSubType())
108    {
109    }
110
111    virtual ~HelloWorldSubscriber()
112    {
113        if (reader_ != nullptr)
114        {
115            subscriber_->delete_datareader(reader_);
116        }
117        if (topic_ != nullptr)
118        {
119            participant_->delete_topic(topic_);
120        }
121        if (subscriber_ != nullptr)
122        {
123            participant_->delete_subscriber(subscriber_);
124        }
125        DomainParticipantFactory::get_instance()->delete_participant(participant_);
126    }
127
128    //!Initialize the subscriber
129    bool init()
130    {
131        DomainParticipantQos participantQos;
132        participantQos.name("Participant_subscriber");
133        participant_ = DomainParticipantFactory::get_instance()->create_participant(0, participantQos);
134
135        if (participant_ == nullptr)
136        {
137            return false;
138        }
139
140        // Register the Type
141        type_.register_type(participant_);
142
143        // Create the subscriptions Topic
144        topic_ = participant_->create_topic("HelloWorldTopic", "HelloWorld", TOPIC_QOS_DEFAULT);
145
146        if (topic_ == nullptr)
147        {
148            return false;
149        }
150
151        // Create the Subscriber
152        subscriber_ = participant_->create_subscriber(SUBSCRIBER_QOS_DEFAULT, nullptr);
153
154        if (subscriber_ == nullptr)
155        {
156            return false;
157        }
158
159        // Create the DataReader
160        reader_ = subscriber_->create_datareader(topic_, DATAREADER_QOS_DEFAULT, &listener_);
161
162        if (reader_ == nullptr)
163        {
164            return false;
165        }
166
167        return true;
168    }
169
170    //!Run the Subscriber
171    void run(
172        uint32_t samples)
173    {
174        while(listener_.samples_ < samples)
175        {
176            std::this_thread::sleep_for(std::chrono::milliseconds(100));
177        }
178    }
179};
180
181int main(
182        int argc,
183        char** argv)
184{
185    std::cout << "Starting subscriber." << std::endl;
186    int samples = 10;
187
188    HelloWorldSubscriber* mysub = new HelloWorldSubscriber();
189    if(mysub->init())
190    {
191        mysub->run(static_cast<uint32_t>(samples));
192    }
193
194    delete mysub;
195    return 0;
196}

Subscriber. 它是负责创建和配置 DataReader 的对象。

DataReader. 它是负责实际接收数据的对象。它在应用程序中注册标识要读取的数据的主题(TopicDescription)并访问订阅者接收到的数据。

DataReaderListener. 这是分配给数据读取器的侦听器。

DataReaderQoS. 定义 DataReader 的 QoS 的结构。

SampleInfo. 它是伴随每个样本“读取”或“获取”的信息。
从类的私有数据成员开始,值得一提的是数据读取监听器的实现。类的私有数据成员将是参与者、订阅者、主题、数据读取器和数据类型。与数据写入器的情况一样,侦听器实现了要在事件发生时执行的回调。SubListener 的第一个被覆盖的回调是on_subscription_matched(),它类似于on_publication_matched()DataWriter 的回调

void on_subscription_matched(DataReader*,const SubscriptionMatchedStatus& info) override
{if (info.current_count_change == 1){std::cout << "Subscriber matched." << std::endl;}else if (info.current_count_change == -1){std::cout << "Subscriber unmatched." << std::endl;}else{std::cout << info.current_count_change<< " is not a valid value for SubscriptionMatchedStatus current count change" << std::endl;}
}

第二个被覆盖的回调是on_data_available(). 在此,数据读取器可以访问的下一个接收到的样本被获取并处理以显示其内容。在这里SampleInfo定义了类的对象,它决定了一个样本是否已经被读取或获取。每次读取样本时,接收样本的计数器都会增加

void on_data_available(DataReader* reader) override
{SampleInfo info;if (reader->take_next_sample(&hello_, &info) == ReturnCode_t::RETCODE_OK){if (info.valid_data){samples_++;std::cout << "Message: " << hello_.message() << " with index: " << hello_.index()<< " RECEIVED." << std::endl;}}
}

fastdds的快速使用相关推荐

  1. FastDDS的服务器记录-译-

    discourse.ros.org/t/fastdds-without-discovery-server/26117/9 有疑问如下: 我一直在以多种方式与 FastDDS(在 ROS2 Humble ...

  2. 优雅的玩转Fast-DDS

    优雅的玩转Fast-DDS 安装依赖 sudo apt install cmake g++ python3-pip wget gitpip3 install -U colcon-common-exte ...

  3. 快速排查feign.FeignException: status 500 …

    feign.FeignException: status 500 - 总结一下feign报500的时候快速排查问题的方法, 这个bug容易出现的地方分别为: 1. 远程调用的时候feign的注册信息有 ...

  4. python中如何对复杂的json数据快速查找key对应的value值(使用JsonSearch包)

    前言 之前在实际的项目研发中,需要对一些复杂的json数据进行取值操作,由于json数据的层级很深,所以经常取值的代码会变成类似这样: value = data['store']['book'][0] ...

  5. 如何利用python的newspaper包快速爬取网页数据

    文章目录 前言 一个爬取新闻网页数据的神器 小试牛刀 如何快速安装 windows安装 Debian / Ubuntu安装 OSX安装 体验更多的功能 前言 随着越来的进行自然语言处理相关方面的研究, ...

  6. 【快速上手mac必备】常用优质mac软件推荐(音视频、办公、软件开发、辅助工具、系统管理、云存储)

    本文章的主要内容是我作为一名大四学生.准程序员.up主这三种身份来给大家推荐一下 mac 上好用的软件以及工具.本人也是从去年9月份开始从windows阵营转移到了mac阵营,刚开始使用的时候,也曾主 ...

  7. 容器云原生DevOps学习笔记——第二期:如何快速高质量的应用容器化迁移

    暑期实习期间,所在的技术中台-效能研发团队规划设计并结合公司开源协同实现符合DevOps理念的研发工具平台,实现研发过程自动化.标准化: 实习期间对DevOps的理解一直懵懵懂懂,最近观看了阿里专家带 ...

  8. 面试高频——JUC并发工具包快速上手(超详细总结)

    目录 一.什么是JUC 二.基本知识 2.1.进程和线程 2.2.Java默认有两个进程 2.3.Java能够开启线程吗? 2.4.并发和并行 2.5.线程的状态 2.6.wait和sleep的区别 ...

  9. Shiro第一个程序:官方快速入门程序Qucickstart详解教程

    目录 一.下载解压 二.第一个Shiro程序 1. 导入依赖 2. 配置shiro配置文件 3. Quickstart.java 4. 启动测试 三.shiro.ini分析 四.Quickstart. ...

最新文章

  1. js如何获得FCKeditor控件的值
  2. MyBatis1:MyBatis入门
  3. [javaweb] servlet介绍与servlet的继承关系 和 service 方法 (一)
  4. VTK:Shaders之SphereMap
  5. 使用Freemarker来页面静态化,与Spring整合使用
  6. HTTP协议中的Content-Encoding
  7. PHP以xml形式获取POST数据
  8. php和html开发工具,常用的php开发工具有哪些?
  9. : You have an error in your SQL syntax; check the manual that corresponds to your MySQL server versi
  10. E: 无法打开锁文件 /var/lib/dpkg/lock-frontend - open (2: 没有那个文件或目录)
  11. Codeforces 831 A Unimodal Array
  12. deepinV20 显卡驱动 cuda10.2+cudnn配置
  13. jbox弹窗_强大的jquery弹出层插件jBox
  14. 最简单最适合纯小白的postman使用方法(测试接口的不二利器)(从介绍到下载到使用的详细教程)
  15. 电脑开机蓝屏代码C000021a
  16. 数据结构基础之迭代法归并排序
  17. MacOS下iterm,Dracula主题配置
  18. D3D基本矩阵函数和显卡硬件术语
  19. android shpe 三角形_在Android中制作三角形按钮
  20. 2011年30家最能赚钱移动互联公司排行榜

热门文章

  1. 谈谈面试题之什么是面向对象?谈谈你对面向对象的理解?
  2. Sketchup 外壳设计之方盒
  3. 远程桌面连接文件复制不出来
  4. 批量苹果手机拍摄照片格式修改(heic转jpg)
  5. Python 银行信用卡客户流失预测(kaggle)
  6. 数据爬取的概念和分类
  7. 名帖175 颜真卿 行书《祭伯父文稿》
  8. Matlab 非线性有约束规划的粒子群算法
  9. secureCRT的下载与破解
  10. vue往数组中添加元素_vuejs给数组添加元素