c++运动学正反解 ros_ROS与C++入门教程-Advanced: Internals(高级:内部)

ROS与C++入门教程-Advanced: Internals(高级:内部)

说明:

此网页包含的roscpp的内部组织的整体的一个高层次的概述,从底部开始了。

一般的理念

对于roscpp的理念是只有特定的API集是外部可见的。

公共的头文件不能包含私有的,除非是私有的需要模板。

这意味着类型删除经常在早期处理

一般情况下,我们尽量减少包含头文件,例如没有Boost.Thread头文件增加到公共的API。

将来也会删除Boost.Bind ,因为它显著增加了编译时间。

xmlrpc

我们使用修改过的第三方库xmlrpcpp,不幸的是,它不允许我们为它的套接字使用自己的轮询代码,所以我们利用单独的线程来完成这个工作。

所有的使用都通过独立XMLRPCManager进行,所有与主机的通讯都使用 master::execute函数。

PollSet/PollManager(轮询和管理)

在底层,所有non-xmlrpc网络都通过poll(),通过PollSet 类进行管理。目前有一个PollSet被使用,它单独由PollManager管理。PollSet让你注册文件描述和增加要监听的事件(读/写)

Connections and Transports(连接和传输)

在系统最低层,transport系统尝试去做传输类型:如TCP, UDP。有一个基本类Transport,被扩展到TransportTCP和TransportUDP ,它们实现 read() 和write()。

在系统最高层,Connection 类提供基于回调的方法读写数据到transport。Connection 负责跟踪,如多少数据被发送和收到以及当操作完成调用回调函数等。

Connections通过ConnectionManager实现追踪。它主要管理连入的TCP/UDP连接。也一样保持指向Connection对象的指针,直到它们允许被删除才取消指向。

当ConnectionManager收到一个新连入的TCP or UDP连接,它会创建TransportSubscriberLink 或ServiceClientLink,依赖在于是话题或服务连接。

Initialization初始化

roscpp初始化有两个部分 init()和start()方法。

init()完成的工作不多:

主要是简单解析环境变量和命令行相关,如master URI, local hostname/ip address, remappings等

init()不允许执行任何的通讯。因此用户可以手工检查master是否启动,是否有自定义的行为等。

init()同样不开始任何线程。

start()完成大部分初始化工作:

start() 也可以手工调用,需要调用shutdown()关闭。

或第一个NodeHandle创建时调用start() ,然后最后一个NodeHandle销毁后再调用shutdown()完成关闭

start()完成的工作包括:

初始化各种独立线程

安装SIGINT型号处理器

创建rosout log4cxx 附加

创建~get_loggers和~set_logger_level服务

检查/use_sim_time参数,如果设置,订阅到/clock话题

Topics话题

话题通过TopicManager管理,它维护一个Subscriptions 和Publications的列表。

当任何的信息到达TopicManager,任何编译时间信息已被删除。

这意味着,TopicManager::subscribe() 只能访问到SubscribeOptions类的内容

(1)Subscriptions订阅

订阅话题通过Subscription 类管理,每个话题只允许对应一个Subscription类 。

多个订阅到相同的话题,会共享单独一个连接,避免带宽浪费

Subscription对象管理 N个PublisherLink对象,每个连接对应不同的publisher在相应的话题上。

有两种类型的PublisherLink 对象, TransportPublisherLink使用传输层连接发布器,IntraprocessPublisherLink连接到本地,进程内的发布器。允许那些完全跳过传输层(并尽可能不提供拷贝消息传递)

(2)publisherUpdate

当一个新发布器注册到master,节点会收到publisherUpdate的xmlrpc调用,它在正确的话题上会转发到Subscription::pubUpdate。对非进程内连接,它会开始一个异步xmlrpc请求到新的发布器。当xmlrpc请求通过,Subscription::pendingConnectionDone() 就会调用。一旦这个请求通过,就会为这个发布器创建TransportPublisherLink。

(3)Retry重试

TCP TransportPublisherLink的连接丢失会尝试重连,它的第一个重连都安排在失连的100ms后。之后的每次都会时间加倍,直到建议的20秒。这是上限。

(4)订阅回调和反序列化

当消息到达,它会推送到SubscriptionQueue队列,当队列填满,它会丢弃旧的消息。值得注意的是,消息还没有被反序列化。一个MessageDeserializer对象放入到队列,而不是消息本身。如果可能,这个对象会被多个回调函数共享。(如果它们的rtti 信息匹配)。

这意味着:

因为队列满而丢弃的消息不会被发序列化。

消息不会反序列,直到第一个关联到订阅的回调函数被调用才进行。

发布器

发布话题通过Publication类管理,一个发布类对应一个话题。多个发布器在相同的话题,共享连接给它们的订阅器。

Publication对象管理N个SubscriberLink对象,每一个连接到不同的订阅器,在其对应的话题上。

有两个类型的SubscriberLink对象。 TransportSubscriberLink 连接到订阅器通过传输层(ConnectionManager创建)

IntraprocessSubscriberLink 连接到进程内的订阅器,忽略所有的传输层。

No-Copy Intraprocess Support非复制进程的支持

roscpp组合使用 boost::shared_ptr 和rtti提供基本安全的没有复制的进程内消息传递。

它仅仅是基本安全的,因为它依赖于发布者从不修改刚刚发布的消息对象。

注意:这只是应用,如果消息作为boost::shared_ptr传递,否则还是要求序列化和反序列化,通过它,将会忽略传输层。

一个进程内消息的路径如下:

Publisher::publish()

TopicManager::publish()

调用Publication::getPublishTypes()确认我们订阅器的类型。

如果有"serialization required"的订阅器, 序列化这个消息。

如果没有任何"no-copy"的订阅,立即清除消息指针和RTTI信息

Publication::publish()

如果有,找到Publication中的IntraprocessSubscriberLink

直接入队的消息,只允许"no-copy"的发布(这可能不是必须的)

IntraprocessSubscriberLink::enqueueMessage()

IntraprocessPublisherLink::handleMessage()

Subscription::handleMessage()

对于每个订阅器,检查rtti消息匹配,如果是,则将消息添加到其订阅队列。

有一些部分,这不是真正必要的。出于性能原因,它可能是一个好主意来摆脱intraprocesspublisherlink / intraprocesssubscriberlink直接连接的发布和订阅。

Services服务

服务单独由ServiceManager管理

(1)服务端

服务端有ServicePublication类管理,它会跟踪许多ServiceClientLink 对象。

在任何时给定的服务只由一个服务端提供服务。在roscpp节点也一样。

ServiceClientLink对象(由ConnectionManager创建)处理单独服务客户端。

(2)客户端

连接到服务端由ServiceServerLink管理,每个新的服务端创建一个新的ServiceServerLink

User API用户API

用户API基本都包含在ros.h头文件,除了callback_queue.h,它需要移除一些额外的包含,才能使用到。

(1)多种回调形式

示例:

void (const MsgConstPtr& msg);

void (const MsgPtr& msg);

void (const Msg& msg);

void (Msg msg);

void (const ros::MessageEvent& evt);

etc.

允许这样调用的机制叫ParameterAdapter,它是指定不同参数类型的模板类。

例如:标准形式(const MsgConstPtr&) :

template

struct ParameterAdapter& >

{

typedef typename boost::remove_reference::type>::type Message;

typedef ros::MessageEvent Event;

typedef const boost::shared_ptr Parameter;

static const bool is_const = true;

static Parameter getParameter(const Event& event)

{

return event.getMessage();

}

};

它提供一个消息的typedef,它保证是non-const和non-reference。

它提供MessageEvent类型作为Event的typedef

它提供Parameter的typedef,它返回传递到回调函数的实际值和is_const静态变量,告诉我们如果消息是常量不允许更改。

ParameterAdapter是模板的函数形式(不仅仅是message类型),并提取该消息类型和其他信息。

ParameterAdapter 与SubscriptionCallbackHelperT(也是模板的函数形式) 一起使用,调用回调类似如下:

callback_(ParameterAdapter

::getParameter(event));

添加新的回调形式,只要他们仍然是单参数,创建一个新的ParameterAdapter指定的(如果必要的话,也可以在ROS外实现)。

回调队列

在roscpp,CallbackQueue类是比较复杂的一个。支持很多功能。

目前支持功能:

确保特定的人(指定 removal id传递给 addCallback())任意回调,一旦removeByID返回,回调不会被调用。

移除当前正在进行的回调

递归调用(在同一个队列中调用 CallbackQueue::callAvailable() 或callOne()的回调里再调用 CallbackQueue::callAvailable() 或callOne())

线程安全地调用 callAvailable() 和callOne()

这些要求为CallbackQueue增加了许多复杂性,同时降低的执行效率。如果选择一个不同的接口或不同的需求集,则可以编写一个比当前的快得多且不太复杂的队列。

除去(或改变语义的)callAvailable()可以让CallbackQueue 简单得多,并可能消除很多复杂的需要(如线程本地存储的需要)。

Future未来

(1)Transports传输层

传输层是当我们只有一个TCP传输时,想到任何必要的变化将发生在UDP实现时。这没有发生,并且事实证明传输层被设计的方式是一个错误,并且只支持TCP情况。这是我们缺乏对UDP的大消息支持的原因。

传输层现在不能正常工作的主要原因是它不知道消息。 目前,例如,接收消息是“拉”模型。 TransportPublisherLink请求其Connection4字节长度,并且当到达时,解析,分配空间,并要求消息本身。 相反,传输应该将消息推送到PublisherLink,并且Connection类可以完全取消。 基本上,我设计了一个更通用的网络传输系统,当我应该设计一个消息传递传输系统。

(2)Sub/Pub(发布/订阅)

虽然roscpp支持非复制进程内消息传递,但它不是最快的实现。 这主要是因为roscpp开始不支持内部进程消息传递,并且已经成长为当前的形式。 理想情况下,这将交换,与内部进程的情况成为主要的,with the network side hooking into it。 通过对lockfree包中的lock-free结构的一些增强,它甚至可以以lock-free的方式进行,尽管你还需要一个lockfree CallbackQueue实现。这是可能的,但也许不是与当前CallbackQueue做同样的保证 - 这就是为什么CallbackQueue被抽象为CallbackQueueInterface。

(3)NodeHandle

IMHO一个错误是用NodeHandle做的,因为它做的太多了,你可以在roscpp中做的一切都是作为一个方法。 这意味着即使你需要的是它的命名空间/重映射功能,你拖着很多行李。 经过相当多的思考,我宁愿一个Context类只处理命名空间和重新映射(并且有公共和私有命名空间的概念)。 我也希望我减少了每个函数类型的重载,并使用命名参数idiom(如TransportHints)将它们移动到XOptions结构中。 理想情况下,正常订户也会遵循message_filters模式。

订阅一个话题可能看起来像这样:

ros::Subscriber sub(SubscribeOptions(context.name("foo"), 5).allowConcurrent().callbackQueue(my_queue).callback(myCallback));

// Alternatively, you could leave out the callback above, and then do:

// sub.registerCallback(myCallback);

还要注意使用上面的context.name(),它应该返回一个Name对象而不是一个字符串。 这可能是太远,但有好处,不直接使用字符串(如知道一个名字是否已经重新映射已经或没有)。 如果直接传递字符串,将使用全局上下文。

(4)Single Master Assumption单主机假设

目前有一个假设,在所有的roscpp,有一个单一的主人。 这主要是从旧ros::Node保持而来,有相同的假设。需要改变以允许多个主设备(除了在用户API侧)的主要内容是在内部存储主设备以及任何主题/服务。 Name类将在这里帮助,因为它可以包括主人。

在用户API方面,我一直在想:

ros::MasterContext master("http://pri:11311");

ros::Context context(master);

...

如果没有指定MasterContext,它会回退到全局(从环境/命令行创建),就像现在一样。

(5)Global Singletons独立的全局变量

现在有许多独立的全局变量。 我想把它们清理成只有一个单例或全局访问器,用一个类来管理他们的生命周期,例如:

class Globals

{

ConnectionManager* connection_manager_;

XMLRPCManager* xmlrpc_manager_;

...

public:

ConnectionManager* getConnectionManager();

...

};

Globals* getGlobals();

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值