前言
因为网上有不少关于cyber基础介绍的文章了,本文便不加赘述。本文主要关注cyber基础以及通信相关例子的实现.
课程地址: https://apollo.baidu.com/community/course/outline/329?activeId=10200
更多还请参考:
[1] Apollo星火计划学习笔记——第三讲(Apollo Cyber RT 模块详解与实战)https://blog.csdn.net/sinat_52032317/article/details/126924375
[2] 第一章:Cyber RT基础入门与实践https://apollo.baidu.com/community/article/1093 [TEST1 & TEST2]
[3] 第二章:Cyber RT通信机制解析与实践https://apollo.baidu.com/community/article/1094 [TEST3 & TEST4 & TEST5 & TEST6]
这部分更多内容请见 第一章:Cyber RT基础入门与实践https://apollo.baidu.com/community/article/1093
Apollo Cyber是首个专为自动驾驶定制的高性能且开源的实时通信框架,它主要解决了自动驾驶系统的高并发、低延迟、高吞吐、任务调度等问题,同时还提供了多种通信机制和用户级的协程,在资源有限的情况下会根据任务的优先级来进行调度处理。
Node是Cyber的基础构建,每一个模块都会包含一个Node节点,模块之间通过Node节点来进行通信。Node之间的通信可以设定不同的模式,有Reader/Writer
和Service/Client
。
Cyber采用的是分布式系统,Node是通过Topology来管理的,每个Node都是这个拓扑图的顶点,其中每个Node顶点是通过Channel或者Service来连接的。Node节点是去中心化的,可以动态监控节点的增加和删除。Channel可以理解为一块共享内存,采用共享内存的通信方式,可以大大提高通信效率。
Apollo采用Bazel进行编译.Bazel是Google研发的一款开源构建和测试工具,也是一种简单、易读的构建工具。其优点如下:
Bazel项目结构如下所示:
project
|-- pkg
| |-- BUILD
| |-- src.cc
|-- WORKSPACE
WORKSPACE
文件将目录及其内容标识为 Bazel 工作区并位于项目目录结构的根部,BUILD文件中常见的两种规则:
cc_binary:表示要构建对应文件变成二进制文件。
cc_library:表示要构建对应文件变成相关依赖库。
流程
<1> 创建本节实验工程目录; |
.
├── BUILD
├── cyberfile.xml
├── learning_cyber.BUILD //内容为空
└── test01
└── demo01
├── BUILD
└── demo01.cc
load("//tools/install:install.bzl", "install", "install_src_files")
install(
name = "install",
data = [
"learning_cyber.BUILD",
"cyberfile.xml",
],
deps = [
"//learning_cyber/test01/demo01:install",
],
)
install_src_files(
name = "install_src",
src_dir = ["."],
dest = "learning_cyber/src",
filter = "*",
deps = [
"//learning_cyber/test01/demo01:install_src",
]
)
PS: |
cyberfile.xml
<package>
<name>learning_cybername>
<version>1.0.0version>
<description>
learning_cyber
description>
<maintainer email="AD-platform">[email protected]maintainer>
<type>moduletype>
<src_path>//learning_cybersrc_path>
<license>BSDlicense>
<author>Apolloauthor>
<depend type="binary" repo_name="cyber">cyber-devdepend>
<builder>bazelbuilder>
package>
#include
int main(int argc, char const *argv[])
{
apollo::cyber::Init(argv[0]);
AINFO << "hello Apollo";
AWARN << "hello Apollo";
AERROR << "hello Apollo";
AFATAL << "hello Apollo";
return 0;
}
BUILD
load("@rules_cc//cc:defs.bzl", "cc_binary", "cc_library")
load("//tools/install:install.bzl", "install", "install_src_files")
load("//tools:cpplint.bzl", "cpplint")
package(default_visibility = ["//visibility:public"])
cc_binary(
name = "demo01",
srcs = ["demo01.cc"],
deps = ["//cyber"],
)
install(
name = "install",
runtime_dest = "learning_cyber/bin",
targets = [
":demo01"
],
)
install_src_files(
name = "install_src",
src_dir = ["."],
dest = "learning_cyber/src/cyberatest",
filter = "*",
)
cc_binary的编译规则可见https://docs.bazel.build/versions/5.1.0/be/c-cpp.html#cc_binary
buildtool build -p learning_cyber/
为了能够看到结果,通过以下命令将输出结果打印到窗口,命令如下:
export GLOG_alsologtostderr=1
cd /opt/apollo/neo/bin
在/opt/apollo/neo/bin
目录下,执行以下命令:
./demo01
执行完成后,可以看到命令行窗口打印出了"hello Apollo"的内容。
单个源码的工程可以满足小型项目的需要,但在实际使用中,我们常希望将较大的项目拆分为多个包,以允许快速的增量构建(即仅重建更改的内容)接下来,我们介绍使用多个包来管理你的工程。
.
├── BUILD
├── cyberfile.xml
├── test_bazel
│ ├── demo_lib
│ │ ├── BUILD
│ │ ├── getName.cc
│ │ └── getName.h
│ └── demo_main
│ ├── BUILD
│ └── main.cc
└── test.BUILD
load("//tools/install:install.bzl", "install", "install_src_files")
install(
name = "install",
data = [
"test.BUILD",
"cyberfile.xml",
],
deps = [
"//test/test_bazel/demo_main:install",
],
)
install_src_files(
name = "install_src",
src_dir = ["."],
dest = "test/src",
filter = "*",
deps = [
"//test/test_bazel/demo_main:install_src",
]
)
cyberfile.xml
<package>
<name>testname>
<version>1.0.0version>
<description>
test component
description>
<maintainer email="AD-platform">[email protected]maintainer>
<type>moduletype>
<src_path>//testsrc_path>
<license>BSDlicense>
<author>Apolloauthor>
<depend type="binary" repo_name="cyber">cyber-devdepend>
<builder>bazelbuilder>
package>
getName.h
#pragma once
#include
using namespace std;
string get_name(const string& name);
getName.cc
#include "getName.h"
string get_name(const string& name){
return "Hello" + name;
}
源码编写完成后,通过cc_library规则将getName.cc源码构建为库文件getName_lib,hdrs表示的是源文件对应的头文件路径,demo_lib的BUILD文件内容如下:
load("@rules_cc//cc:defs.bzl", "cc_binary", "cc_library")
package(default_visibility = ["//visibility:public"])
cc_library(
name = "getName_lib",
srcs = ["getName.cc"],
hdrs = ["getName.h"]
)
main.cc
#include "test/test_bazel/demo_lib/getName.h"
#include
int main()
{
for (int i = 0; i< 5; ++i)
{
std::cout << get_name(" Apollo ") << std::endl;
}
return 0;
}
demo_main的BUILD文件
通过cc_binary配置将main.cc编译构建可执行文件main,deps表示构建该文件所依赖相关的库。
load("@rules_cc//cc:defs.bzl", "cc_binary", "cc_library")
load("//tools/install:install.bzl", "install", "install_src_files")
load("//tools:cpplint.bzl", "cpplint")
package(default_visibility = ["//visibility:public"])
cc_binary(
name = "main",
srcs = ["main.cc"],
deps = ["//test/test_bazel/demo_lib:getName_lib"],
)
install(
name = "install",
runtime_dest = "test/bin",
targets = [
":main"
],
)
install_src_files(
name = "install_src",
src_dir = ["."],
dest = "test/src/cyberatest",
filter = "*",
)
buildtool build -p cyber_demo
cd /opt/apollo/neo/bin
export GLOG_alsologtostderr=1
./main
也可以cd到bazel-bin去查看
这部分更多内容请见 第二章:Cyber RT通信机制解析与实践https://apollo.baidu.com/community/article/1094
话题通信方式适合于持续性通信的应用场景,比如雷达信号,摄像头图像信息这类数据的传输。
Server-Client通信可以在客户端发出消息请求时,服务端才进⾏请求回应,并将客户端所需的数据返回给客户端。该通信模式适合临时的消息传输,适⽤于不需要持续性发送数据的场景。
以共享的方式实现不同节点之间数据交互的通信模式。参数服务器是基于服务实现的,包含客户端和服务器端,服务端节点可以存储数据,客户端节点可以访问服务端节点操作数据,这个过程虽然基于请求响应的,但是无需自己实现请求与响应,此过程已经被封装,调用者只需要通过比较简单友好的API就可以实现参数操作。类似于“全局变量”的方式来存储这些参数,并定义一些自定义参数来进行使用。
Protobuf 是 Google 公司开发的一种跨语言和平台的序列化数据结构的方式,是一个灵活的、高效的用于序列化数据的协议,与 XML 和 JSON 格式相比,Protobuf 更小、更快、更便捷。其优点如下:
Protobuf有几个部分构成:
Protobuf的编译要分为两个步骤:
使用Protobuf来定义数据格式,在main程序中设置数据值并输出。
<1> 创建本节实验工程目录 |
<1> 创建本节实验工程目录:
cyber_demo
|-- cyber_03
|-- proto
|-- BUILD
|-- car_msg.proto
|-- test_proto
|-- BUILD
|-- car.cc
|--BUILD
|--cyberfile.xml
|--cyber_demo.BUILD
PS: 也可以直接在之前创建过的文件夹中进行手动创建cyber03文件夹以及其下文件并对cyberfile.xml和BUILD文件进行修改.
<2> 编写Apollo包管理相关的BUILD和cyberfile.xml文件
BUILD文件内容:
load("//tools/install:install.bzl", "install", "install_src_files")
install(
name = "install",
data = [
"cyber_demo.BUILD",
"cyberfile.xml",
],
deps = [
"//cyber_demo/cyber_03/test_proto:install",
],
)
install_src_files(
name = "install_src",
src_dir = ["."],
dest = "cyber_demo/src",
filter = "*",
deps = [
"//cyber_demo/cyber_03/test_proto:install_src",
]
)
若在之前创建的文件夹进行添加,则只需对install和install_src_files中的deps进行修改与添加.
编写cyberfile文件:
cyber_demo
1.0.0
cyber_demo
[email protected]
module
//cyber_demo
BSD
Apollo
cyber-dev
3rd-protobuf-dev
bazel
与之前的实验相比多了一句
,添加protobuf相关依赖.
<3> 编写proto源文件及BUILD文件;
编写proto文件,文件中定义了车辆的信息:
syntax = "proto2";
package apollo.cyber.test.proto;
message CarMsg {
required string owner = 1;
optional string license_plate = 2;
optional uint64 max_passenger = 3;
repeated string car_info = 4;
}
编写proto的BUILD文件:
load("@rules_proto//proto:defs.bzl", "proto_library")
load("@rules_cc//cc:defs.bzl", "cc_proto_library")
load("//tools:python_rules.bzl", "py_proto_library")
package(default_visibility = ["//visibility:public"])
proto_library(
name = "car_msg_proto",
srcs = ["car_msg.proto"],
)
cc_proto_library(
name = "car_msg_cc_proto",
deps = [":car_msg_proto"],
)
代码解析:
<4> 编写主代码及BUILD文件
通过car.cc输出车辆基本信息:
#include "test/cyber_03/proto/car_msg.pb.h" // 填写自己的路径
using namespace std;
int main()
{
apollo::cyber::test::proto::CarMsg car;
car.set_owner("apollo");
car.set_license_plate("京A88888");
car.set_max_passenger(6);
car.add_car_info("SUV"); //车型
car.add_car_info("Red"); //车身颜色
car.add_car_info("electric"); //电动
string owner = car.owner();
string license_plate = car.license_plate();
uint64_t max_passenger = car.max_passenger();
cout << "owner:" << owner << endl;
cout << "license_plate:" << license_plate << endl;
cout << "max_passenger:" << max_passenger << endl;
for (int i = 0; i < car.car_info_size(); ++i){
string info = car.car_info(i);
cout << info << " ";
}
cout << endl;
return 0;
}
编辑car.cc的BUILD文件:
load("@rules_cc//cc:defs.bzl", "cc_binary", "cc_library")
load("//tools/install:install.bzl", "install", "install_src_files")
load("//tools:cpplint.bzl", "cpplint")
package(default_visibility = ["//visibility:public"])
cc_binary(
name = "car",
srcs = ["car.cc"],
deps = ["//test/cyber_03/proto:car_msg_cc_proto"],
)
install(
name = "install",
runtime_dest = "test/bin",
targets = [
":car"
],
)
install_src_files(
name = "install_src",
src_dir = ["."],
dest = "test/src/cyberatest",
filter = "*",
)
修改cc_binary中的deps路径
<5> 编译代码
cd /apollo_workspace
buildtool build -p test
<6> 运行可执行文件
cd /opt/apollo/neo/bin/
./car
结果
owner:apollo
license_plate:京A88888
max_passenger:6
SUV Red electric
文件结构参照文档第二章:Cyber RT通信机制解析与实践https://apollo.baidu.com/community/article/1094
/apollo_workspace/
|--test
| |--communication
| | |--BUILD //cyber_test编译文件
| |--talker.cc //talker-listener通信实现
| |--listener.cc
| |--server.cc //server-client通信实现
| |--client.cc
| |--param_server.cc //parameter server-client通信实现
| |--param_client.cc
|--proto
|--BUILD //car.proto 编译文件
|--car.proto //小车数据定义的文件ß
<1> proto文件编写
// 定义proto使用的版本
syntax = "proto2";
//定义包名,在cc文件中调用(重名需更改)
package apollo.cyber.example.proto;
//定义一个车的消息,车的型号,车主,车的车牌号,已跑公里数,车速
message Car{
optional string plate = 1;
optional string type = 2;
optional string owner = 3;
optional uint64 kilometers = 4;
optional uint64 speed = 5;
};
BUILD文件
load("@rules_proto//proto:defs.bzl", "proto_library")
load("@rules_cc//cc:defs.bzl", "cc_proto_library")
load("//tools:python_rules.bzl", "py_proto_library")
package(default_visibility = ["//visibility:public"])
proto_library(
name = "car_proto",
srcs = ["car.proto"],
)
cc_proto_library(
name = "car_cc_proto",
deps = [":car_proto"],
)
<2> 源文件以及BUILD文件编写
talker.cc
//头文件引用
#include "test/communication/proto/car.pb.h" //注意路径
#include "cyber/cyber.h"
#include "cyber/time/rate.h"
//car数据定义的引用,可以看出其定义来源于一个proto
using apollo::cyber::example::proto::Car;
int main(int argc, char *argv[]) {
// 初始化一个cyber框架
apollo::cyber::Init(argv[0]);
// 创建talker节点
auto talker_node = apollo::cyber::CreateNode("talker");
// 从节点创建一个Topic,来实现对车速的查看
auto talker = talker_node->CreateWriter<Car>("car_speed");
AINFO << "I'll start telling you the current speed of the car.";
//设置初始速度为0,然后速度每秒增加5km/h
uint64_t speed = 0;
while (apollo::cyber::OK()) {
auto msg = std::make_shared<Car>();
msg->set_speed(speed);
//假设车速持续增加
speed += 5;
talker->Write(msg);
sleep(1);
}
return 0;
}
listener.cc
#include "test/communication/proto/car.pb.h" //注意路径
#include "cyber/cyber.h"
//car数据定义的引用,可以看出其定义来源于一个proto
using apollo::cyber::example::proto::Car;
//接收到消息后的响应函数
void message_callback(
const std::shared_ptr<Car>& msg) {
AINFO << "now speed is: " << msg->speed();
}
int main(int argc, char* argv[]) {
//初始化cyber框架
apollo::cyber::Init(argv[0]);
//创建监听节点
auto listener_node = apollo::cyber::CreateNode("listener");
//创建监听响应进行消息读取
auto listener = listener_node->CreateReader<Car>(
"car_speed", message_callback);
apollo::cyber::WaitForShutdown();
return 0;
}
BUILD
注意路径的更改
load("@rules_cc//cc:defs.bzl", "cc_binary", "cc_library")
load("//tools/install:install.bzl", "install", "install_src_files")
load("//tools:cpplint.bzl", "cpplint")
package(default_visibility = ["//visibility:public"])
cc_binary(
name = "talker",
srcs = ["talker.cc"],
deps = [
"//cyber",
"//test/communication/proto:car_cc_proto",
],
linkstatic = True,
)
cc_binary(
name = "listener",
srcs = ["listener.cc"],
deps = [
"//cyber",
"//test/communication/proto:car_cc_proto",
],
linkstatic = True,
)
install(
name = "install",
runtime_dest = "test/bin",
targets = [
":talker",
":listener"
],
)
install_src_files(
name = "install_src",
src_dir = ["."],
dest = "test/src/cyberatest",
filter = "*",
)
<3> 包管理的BUILD文件deps修改,和之前的步骤一致.
<4> 编译
buildtool build -p test/
<5>运行
首先,先将输出方法改为控制台输出。
export GLOG_alsologtostderr=1
打开两个终端,都进入Apollo的docker环境,一个终端运行talker,另一个运行listener,会发现listener运行后开始接收talker发送的小车速度的消息。
# 运行talker
./bazel-bin/test/communication/cyber_test/talker
# 运行listener
./bazel-bin/test/communication/cyber_test/listener
<1> proto文件编写
TEST4中已经写过了,不用再写.
<2> 源文件以及BUILD文件编写
server.cc
#include "test/communication/proto/car.pb.h" //注意路径
#include "cyber/cyber.h"
//car数据定义的引用,可以看出其定义来源于一个proto
using apollo::cyber::example::proto::Car;
int main(int argc, char* argv[]) {
apollo::cyber::Init(argv[0]);
std::shared_ptr<apollo::cyber::Node> node(
apollo::cyber::CreateNode("server"));
//创建server node并设置topic名称,定义响应函数
auto server = node->CreateService<Car, Car>("service_client",
[](const std::shared_ptr<Car>& request,std::shared_ptr<Car>& response) {
AINFO << "Hi, I am your car's server.";
response->set_type("apollo");
response->set_plate("京A8888888");
response->set_owner("xxx");
response->set_kilometers(5);
});
apollo::cyber::WaitForShutdown();
return 0;
}
client.cc
#include "test/communication/proto/car.pb.h" //注意路径
#include "cyber/cyber.h"
//car数据定义的引用,可以看出其定义来源于一个proto
using apollo::cyber::example::proto::Car;
int main(int argc, char* argv[]){
//初始化cyber框架
apollo::cyber::Init(argv[0]);
//创建client node
std::shared_ptr<apollo::cyber::Node> node(
apollo::cyber::CreateNode("client"));
auto client = node->CreateClient<Car, Car>("service_client");
AINFO << "Hi server, I want to know car's information";
//创建一个请求
auto request = std::make_shared<Car>();
//发送请求并获得回应数据
auto response = client->SendRequest(request);
if(apollo::cyber::OK){
if (response != nullptr) {
AINFO << "this car owner is : " << response->owner();
AINFO << "this car plate is : " << response->plate();
AINFO << "this car type is : " << response->type();
AINFO << "this car has run : " << response->kilometers()<<"kilometers";
} else {
AINFO << "service may not ready.";
}
}
apollo::cyber::WaitForShutdown();
return 0;
}
BUILD文件
// 加上server 和 client
cc_binary(
name = "server",
srcs = ["server.cc"],
deps = [
"//cyber",
"//test/communication/proto:car_cc_proto",
],
linkstatic = True,
)
cc_binary(
name = "client",
srcs = ["client.cc"],
deps = [
"//cyber",
"//test/communication/proto:car_cc_proto",
],
linkstatic = True,
)
// 加上 server和client
install(
name = "install",
runtime_dest = "test/bin",
targets = [
":talker",
":listener",
":client",
":server",
],
)
<3> 包管理的BUILD文件deps修改,和之前的步骤一致(此处若已经进行过TEST4则无需更改).
<4> 编译
buildtool build -p test/
<5>运行
首先,先将输出方法改为控制台输出。
export GLOG_alsologtostderr=1
分别打开两个终端,运行server和client
# 运行server
./bazel-bin/test/communication/cyber_test/server
# 运行client
./bazel-bin/test/communication/cyber_test/client
<1>编写源文件和BUILD文件
param_client.cc
#include "cyber/cyber.h"
#include "cyber/parameter/parameter_client.h"
using apollo::cyber::Parameter;
using apollo::cyber::ParameterClient;
using apollo::cyber::Parameter;
using apollo::cyber::ParameterClient;
int main(int argc, char** argv){
apollo::cyber::Init(*argv);
std::shared_ptr<apollo::cyber::Node> client_node =
apollo::cyber::CreateNode("parameters_client");
auto param_client = std::make_shared<ParameterClient>(client_node,"parameters_server");
AINFO<<"Hi, I'm car's parameters client!";
//获取所有参数,用vector获取所有参数
std::vector<Parameter> car_parameters;
param_client->ListParameters(&car_parameters);
//遍历获取的参数,显示参数的名字以及参数的类型
for(auto &¶meter : car_parameters){
AINFO << parameter.Name() <<" is " << parameter.TypeName();
}
//客户端选择一个参数进行修改,比如修改max_speed
param_client->SetParameter(Parameter("max_speed",180));
//获取修改后的max_speed
Parameter car_parameter;
param_client->GetParameter("max_speed", &car_parameter);
AINFO << "now max_speed " << car_parameter.AsInt64();
apollo::cyber::WaitForShutdown();
return 0;
}
param_server.cc
#include "cyber/cyber.h"
#include "cyber/parameter/parameter_client.h"
#include "cyber/parameter/parameter_server.h"
using apollo::cyber::Parameter;
using apollo::cyber::ParameterServer;
int main(int argc, char** argv) {
//初始化cyber框架
//创建一个node,该node的名字和Topic名字同名
apollo::cyber::Init(*argv);
std::shared_ptr<apollo::cyber::Node> server_node =
apollo::cyber::CreateNode("parameters_server");
AINFO << "Hi,I'am your car's parameter server.";
//从该node创建参数服务器
auto param_server = std::make_shared<ParameterServer>(server_node);
//服务端进行参数设置,对小车的最高速度、最大人数、是否自动驾驶等参数进行了设置
param_server->SetParameter(Parameter("max_speed", 120));
param_server->SetParameter(Parameter("max_people", 5));
param_server->SetParameter(Parameter("if_auto", true));
//尝试客户端修改一个参数,如max_speed,查看服务端目前的值
Parameter car_parameter;
param_server->GetParameter("max_speed", &car_parameter);
AINFO << "origin max_speed " << car_parameter.AsInt64();
apollo::cyber::WaitForShutdown();
return 0;
}
BUILD
在原有基础上,添加如下:
cc_binary(
name = "param_server",
srcs = ["param_server.cc"],
deps = [
"//cyber",
"//cyber/parameter",
],
linkstatic = True,
)
cc_binary(
name = "param_client",
srcs = ["param_client.cc"],
deps = [
"//cyber",
"//cyber/parameter",
],
linkstatic = True,
)
install(
name = "install",
runtime_dest = "test/bin",
targets = [
":talker",
":listener",
":client",
":server",
":param_client",
":param_server",
],
)
<2> 包管理BUILD(无需更改)
<3> 编译
buildtool build -p test/
<4>运行
首先,先将输出方法改为控制台输出。
export GLOG_alsologtostderr=1
分别在两个终端运行param_server和param_client
./bazel-bin/test/communication/cer_test/param_server
./bazel-bin/test/communication/ber_test/param_client
[1] Apollo星火计划学习笔记——第三讲(Apollo Cyber RT 模块详解与实战)https://blog.csdn.net/sinat_52032317/article/details/126924375
[2] Cyber RT基础入门与实践https://apollo.baidu.com/community/article/1093
[3] 第二章:Cyber RT通信机制解析与实践https://apollo.baidu.com/community/article/1094