2021SC@SDUSC
目录
一、写在前面
二、ROS中节点的名称类型
三、GraphName
四、总结
一、写在前面
上一节我们分析了ROS节点间进行通信的数据格式以及解析方式,但正如在源码分析开始时我们所说,ROS是一个分布式的系统,因此节点之间通讯不仅需要恰当的数据格式,而且需要一个命名系统使消息能够路由至正确的位置.在本节我们将分析namespace包下有关节点命名与解析的代码.
二、ROS中节点的名称类型
ROS中有三种类型的类型的节点名称:全局名称、相对名称和私有名称.下面我们将类比Linux下的文件系统来解释ROS的命名策略与命名格式.
全局名称类似Linux中的绝对路径,它指明了从根节点开始的路径从而提供了一个不依赖上下文的路径搜索方式.如使用cd /usr/local/opt切换目录,系统解析路径后会从根节点开始逐层搜索直到进入指定目录.在ROS中,全局名称同样是以/开头的.
相对名称类似Linux中的相对路径,它指明了从某个路径点开始的路径,因此搜索结果与当前路径有关.如在Linux中使用cd bin进入目录,如果在/usr/local/tomcat下执行这个命令,则会进入/usr/local/opt/tomcat/bin目录,但如果在/users/admin/mysql目录下执行这个命令,则会进入/users/admin/mysql/bin目录.可见这种方式究竟进入哪个目录取决于当前的目录.在ROS中,相对名称以下级路径开头,没有前缀.
私有名称可以类比相对名称,它指明了从某个路径点开始的路径,但系统自动进行一层转换而从当前路径下的私有路径中进行搜索.假设在/users/admin目录下访问一个私有路径files,则实际进入的路径是/users/admin/.private_$(whoami)/files,这样就实现了私有路径的访问.在ROS中私有名称是以~开头的.
另外ROS中还有一类匿名节点名称,匿名路径与其它路径并无不同,只是通过这种方式表示一类孤儿节点或想要隐藏的节点,这种做法与Linux下以 . 开头的隐藏文件或隐藏路径是相似的.
三、GraphName
public class GraphName {
@VisibleForTesting
static final String ANONYMOUS_PREFIX = "anonymous_";
private static final String ROOT = "/";
private static final String SEPARATOR = "/";
private static AtomicInteger anonymousCounter = new AtomicInteger();
private final String name;
private GraphName(String name) {
Preconditions.checkNotNull(name);
this.name = name;
}
private static final Pattern VALID_GRAPH_NAME_PATTERN = Pattern
.compile("^([\~\/A-Za-z][\w_\/]*)?$");
上图为GraphName类的数据成员与构造函数.ANONYMOUS_PREFIX为匿名节点的前缀名称,ROOT代表根节点的前缀,SEPARATOR代表不同路径层级之间的分隔符,anonymousCounter是一个原子整型数,用于追加至匿名名称之后避免命名重复.name表示该节点的名称,一经确定就不能够修改.
GraphName的构造函数被声明为私有,GraphName接收一个整型参数表示节点的路径并以此设置静态变量name.VALID_GRAPH_NAME_PATTERN是一个正则表达式,用于检测节点路径名称是否有效.有效的路径前缀必须为波浪线、斜线或者字母的一种,路径中不能包含除了斜线或者字母以外的字符.
public static GraphName newAnonymous() {
return new GraphName(ANONYMOUS_PREFIX + anonymousCounter.incrementAndGet());
}
public static GraphName root() {
return new GraphName(ROOT);
}
public static GraphName empty() {
return new GraphName("");
}
private static String canonicalize(String name) {
if (!VALID_GRAPH_NAME_PATTERN.matcher(name).matches()) {
throw new RosRuntimeException("Invalid graph name: " + name);
}
// Trim trailing slashes for canonical representation.
while (!name.equals(GraphName.ROOT) && name.endsWith(SEPARATOR)) {
name = name.substring(0, name.length() - 1);
}
if (name.startsWith("~/")) {
name = "~" + name.substring(2);
}
return name;
}
public static GraphName of(String name) {
return new GraphName(canonicalize(name));
}
上面的代码为间接调用构造方法获得GraphName对象的静态方法.
newAnonymous返回一个匿名节点,匿名节点的名称为匿名前缀与匿名计数的拼接.因为newAnonymous方法是静态非同步的,因此需要将匿名计数声明为AtomicInteger以避免多线程重复读取同一个数字从而使匿名节点的名称重复.
root方法返回一个name为ROOT值的GraphName对象,empty方法返回一个name为空字符串的GraphName对象.of方法接收一个字符串作为参数,返回一个name为经过canonicalize处理后字符串的GraphName对象.
canonicalize方法接收一个字符串name,首先判断name是否为标准的ROS名称格式,然后将name尾部的斜线去除直到没有斜线或name已经为根节点.如果name的头部为~/,则将~后的斜线也去除.经过这样的过程后返回正式的路径名称.
public boolean isGlobal() {
return !isEmpty() && name.charAt(0) == '/';
}
public boolean isRoot() {
return name.equals(GraphName.ROOT);
}
public boolean isEmpty() {
return name.isEmpty();
}
public boolean isPrivate() {
return !isEmpty() && name.charAt(0) == '~';
}
public boolean isRelative() {
return !isPrivate() && !isGlobal();
}
public GraphName getParent() {
if (name.length() == 0) {
return GraphName.empty();
}
if (name.equals(GraphName.ROOT)) {
return GraphName.root();
}
int slashIdx = name.lastIndexOf('/');
if (slashIdx > 1) {
return new GraphName(name.substring(0, slashIdx));
}
if (isGlobal()) {
return GraphName.root();
}
return GraphName.empty();
}
public GraphName getbasename() {
int slashIdx = name.lastIndexOf('/');
if (slashIdx > -1) {
if (slashIdx + 1 < name.length()) {
return new GraphName(name.substring(slashIdx + 1));
}
return GraphName.empty();
}
return this;
}
isGlobal方法判断本GraphName对象是否为全局名称,只要名称非空且开头为斜线则返回true.isRoot判断本对象是否为根节点名称,当且仅当名称为斜线时返回true.isEmpty判断本对象的名称是否为空.
isPrivate方法判断本对象是否为私有名称,如果名称不为空且开头为波浪线则返回true. isRelative方法判断本对象是否为相对名称,因为名称在赋值时已经判断合法性,因此只需要排除名称为全局名称和私有名称即可确定名称为相对名称.
getbasename返回本级名称而忽略所有上级名称,getParent正好相反,会返回该对象的父路径名称.
public GraphName toGlobal() {
if (isGlobal()) {
return this;
}
if (isPrivate()) {
return new GraphName(GraphName.ROOT + name.substring(1));
}
return new GraphName(GraphName.ROOT + name);
}
public GraphName join(GraphName other) {
if (other.isGlobal() || isEmpty()) {
return other;
}
if (isRoot()) {
return other.toGlobal();
}
if (other.isEmpty()) {
return this;
}
return new GraphName(toString() + SEPARATOR + other.toString());
}
public GraphName join(String other) {
return join(GraphName.of(other));
}
@Override
public String toString() {
return name;
}
toGlobal方法首先判断本对象是否为全局名称,若是则直接返回,判断是否为私有名称,如果是则使用斜线替代波浪线,以此作为一个新的GraphName对象的名称并将其返回.否则使用斜线拼接本对象的名称以此返回一个新的GraphName对象.join方法接收另外一个GraphName对象other作为参数,接着进行如下判断:如果other是空名称或者全局名称或本对象为空名称则将other原样返回;如果本对象是根名称则将other转为全局名称后返回;如果other是空对象则将本对象返回;否则将两个对象的名称进行拼接后返回一个GraphName对象.
四、总结
本节我们分析了Node的命名空间,辨析了ROS中三种不同的名称类型与构建方式,此外,我们还分析了GraphName对象中关于名称的各种操作.需要注意的是,因为GraphName在建立完成后名称就是不可变的,因此关于名称的各种操作都是返回一个新建的GraphName对象.



