CyberRT 调度中间件
基本概念
Apollo在3.5版本中推出了Cyber RT替代了原先的ROS。
和ROS & ROS2中相似,Cyber RT中支持两种数据交换模式:
- 一种是Publish-Subscribe模式,经常使用于数据流处理中节点间通讯。即发布者(Publisher)在channel(ROS中对应地称为topic)上发布消息,订阅该channel的订阅者(Subscriber)便会收到消息数据;
- 一种就是常见的Service-Client模式,经常使用于客户端与服务端的请求与响应。本质上它是能够基于前者实现的。
Node是整个数据拓扑网络中的基本单元。一个Node中能够建立多个读者/写者,服务端/客户端。
读者和写者分别对应Reader和Writer,用于Publish-Subscribe模式。
服务端和客户端分别对应Service和Client,用于Service-Client模式。
使用CyberRT需要了解CyberRT 本身的API,同时需要了解Bazel 编译工具,protobuf 通信协议,另外因为CyberRT本质上是基于FastDDS的所以也需要了解FastDDS以及Fast RTPS。
使用方法
Bazel
概念
Bazel 是一个类似于 Make 的工具,是 Google 为其内部软件开发的特点量身定制的工具,如今 Google 使用它来构建内部大多数的软件。它的功能有诸多亮点:
- 多语言支持:目前 Bazel 默认支持 Java、Objective-C 和 C++,但可以被扩展到其他任何变成语言。
高级构建描述语言:项目是使用一种叫 BUILD 的语言来描述的,它是一种简洁的文本语言,它把一个项目视为一个集合,这个集合由一些互相关联的库、二进制文件和测试用例组成。相反,像 Make 这样的工具,需要去描述每个文件如何调用编译器。
- 多平台支持:同一套工具和相同的 BUILD 文件可以用来为不同的体系结构构建软件,甚至是不同的平台。在 Google,Bazel 被同时用在数据中心系统中的服务器应用和手机端的移动应用上。
- 可重复性:在 BUILD 文件中,每个库、测试用例和二进制文件都需要明确指定它们的依赖关系。当一个源码文件被修改时,Bazel 凭这些依赖来判断哪些部分需要重新构建,以及哪些任务可以并行进行。这意味着所有构建都是增量的,并且相同构建总是产生一样的结果。
- 可伸缩性:Bazel 可以处理大型项目;在 Google,一个服务器软件有十万行代码是很常见的,在什么都不改的前提下重新构建这样一个项目,大概只需要 200 毫秒。
对于为什么要重新发明一个构建工具而不直接使用 Make,Google 认为 Make 控制得太细,最终的结果完全依靠开发人员能正确编写规则。很久以前,Google 使用自动生成的臃肿的 Makefile 来构建他们的软件,速度太慢,结果不可靠,最终影响了研发人员的效率和公司的敏捷性。所以他们做了 Bazel。Bazel 的规则层次更高,比如,对于“Java 测试”、“C++ 二进制文件”,它都有定义好的内建规则,而这些规则都已经被无数的测试证明是正确和稳定的。
安装
sudo apt install curl curl https://bazel.build/bazel-release.pub.gpg | sudo apt-key add - echo "deb [arch=amd64] https://storage.googleapis.com/bazel-apt stable jdk1.8" | sudo tee /etc/apt/sources.list.d/bazel.list sudo apt-get update sudo apt-get install bazel # 安装bazel sudo apt-get install --only-upgrade bazel # 升级bazel到最新版本
查看bazel 的版本
bazel version
文件组成
bazel中对于文件架构的概念有两个:workspace和package。
workspace是表示整个项目的,也叫repo,必须在项目的根目录下建一个WORKSPACE文件来定义项目的根目录,bazel会忽略所有项目子目录下的WORKSPACE文件。
package是项目中的模块,也就是一个一个包,包在组织上比较随意,可以根据项目需求来定,你想哪个文件夹中的东西成为一个包,就在那个文件夹的目录里创建一个BUILD文件即可,包的管理范围包括子目录里的东西,但不包括子包所包括的内容。比如:
.../project/ WORKSPACE lib/ BUILD ... src/ BUILD ... other1/ BUILD other2/ ...
lib,src和other1分别为一个包,但是other1中含有一个BUILD,因此other1中有一个子包(上文提到:子包不属于父包),所以other1里面的东西不属于src包,但是other2里面的东西属于src包。
WORKSPACE(工作区)
WORKSPACE是Bazel一个概念,它实质上是一个目录。这个目录是bazel工作时的一个基准目录。
Bazel规定,项目源文件和Bazel构建出的目标文件,均放在此目录。要把一个目录设置为WORKSPACE, 则必须在此目录创建一个新的空文件,名为WORKSPACE. Bazel构建项目时,所有的输入项和依赖项,必须位于同一个工作区内。每个工作目录内,可以有多个项目(目录)。bazel 编译或者执行其它命令时,是在WORKSPACE中。
BUILD文件(package)
BUILD文件的作用类似 Makefile之于Make, xml文件之于Maven, gradle文件之于Gradle. BUILD文件中包含bazel的各种不同类型的指令,其中包含构建规则,它指出Bazel如何利用给定的输入,构建出指定的输出。如利用什么.cpp文件,构建出库或者可执行程序。BUILD文件中的每一条编译指令被称为一个target,它指向一系列的源文件和依赖,一个target也可以指向别的target。
举个例子,下面这个hello-world的target利用了Bazel内置的cc_binary编译指令,来从hello-world.cc源文件(没有其他依赖项)构建一个可执行二进制文件。指令里面有些属性是强制的,比如name,有些属性则是可选的,srcs表示的是源文件。
cc_binary( name = "hello-world", srcs = ["hello-world.cc"],
cc_binary即声明了一个构建规则,用于编译生成一个可执行文件。可执行文件名(目标名)由name属性指定,name属性的值的类型可以看出是string类型。srcs属性指定了源文件,srcs属性的值的类型可以看出是list of strings。
*_binary 规则:指定生成相应语言的可执行程序。cc_binary表示c++可执行程序,jave_binary表示java可执行程序。
*_library 规则:指定生成相应语言的库。
*_test 规则:是一个特殊的bianry规则,通常用于自动化测试。
构建的三个阶段
- loading phase 加载阶段
bazel遍历当前工程下,所有子文件夹,找到其中的BUILD文件,加载外部依赖,生成一个个package和target。 - analysis phase 分析阶段
根据各个BUILD中定义的目标和输入输出信息,建立一个内存中的build graph。 - executing phase 执行阶段
根据build graph逐个运行各个规则定义的动作,产生最终的目标。
BUILD文件规则
cc_binary
目标文件编译规则,为一个二进制可执行文件。name必须唯一,srcs指定了源文件,linkopts指定了链接规则,deps指定了依赖文件
cc_library
库文件编译规则,name指定了编译为库文件后的文件名,srcs和hdrs指定源文件和头文件,deps指定需要依赖的其他文件
cc_test
测试文件规则
package
通用方法,定义的值会作用到下面的每个子rule中。default_visibility指定了这个包的默认可见规则。可见的情况下才能被其他package调用。
licenses
通用方法,默认的license
load
通用方法,加载.bzl文件
filegroup
通用方法,为多个编译目标target指定一个名字,glob是一个帮助函数,指定了目录中哪些文件会include,哪些会exclude。visibility指定了target的可见性,也就是可以被哪些package调用
name
属性来命名规则
deps
属性来描述规则之间的依赖关系。使用冒号来分隔包名和规则名。如果某条规则所依赖的规则在其他目录下,就用"//"开头,如果在同一目录下,可以忽略包名而用冒号开头。
linkopts
指定了链接规则
样例
load("@rules_cc//cc:defs.bzl", "cc_binary", "cc_library", "cc_test")
load("//tools:cpplint.bzl", "cpplint")
package(default_visibility = ["//visibility:public"])
cc_library(
name = "io",
deps = [
":poll_data",
":poll_handler",
":poller",
":session",
],
)
cc_library(
name = "poll_data",
hdrs = ["poll_data.h"],
)
cc_library(
name = "poll_handler",
srcs = ["poll_handler.cc"],
hdrs = ["poll_handler.h"],
deps = [
":poll_data",
":poller",
"//cyber/common:log",
"//cyber/croutine",
],
)
cc_library(
name = "poller",
srcs = ["poller.cc"],
hdrs = ["poller.h"],
deps = [
":poll_data",
"//cyber/base:atomic_rw_lock",
"//cyber/common:log",
"//cyber/common:macros",
"//cyber/scheduler:scheduler_factory",
"//cyber/time",
],
)
cc_test(
name = "poller_test",
size = "small",
srcs = ["poller_test.cc"],
deps = [
":poller",
"//cyber:cyber_core",
"@com_google_googletest//:gtest",
],
)
cc_library(
name = "session",
srcs = ["session.cc"],
hdrs = ["session.h"],
deps = [
":poll_handler",
"//cyber/common:log",
],
)
cc_binary(
name = "tcp_echo_client",
srcs = ["example/tcp_echo_client.cc"],
deps = [
"//cyber:cyber_core",
],
)
cc_binary(
name = "tcp_echo_server",
srcs = ["example/tcp_echo_server.cc"],
deps = [
"//cyber:cyber_core",
],
)
cc_binary(
name = "udp_echo_client",
srcs = ["example/udp_echo_client.cc"],
deps = [
"//cyber:cyber_core",
],
)
cc_binary(
name = "udp_echo_server",
srcs = ["example/udp_echo_server.cc"],
deps = [
"//cyber:cyber_core",
],
)
cpplint()