wiki链接:http://wiki.ros.org/roscpp/Internals
这部分包括一个对roscpp内部架构组织高度概括的审视,从最底层开始。
1.通用哲学
roscpp的哲学是仅仅使一部分API对外可见。这意味着公共的头文件不允许包含私有的头文件。这意味着在最初时发生很多类型擦除。(???)
通常我们会尽量减小包含的外部头文件数目。例如,普通公共头文件不允许包含Boost.Thread头文件(CallbackQueue例外)。未来将去除花费很多编译时间的Boost.Bind。
2.xmlrpc(xml远程过程调用)
xml rpc是使用http协议做传输协议的rpc机制,使用xml文本的方式传输命令和数据.
ros使用一个名叫xmlrpcpp的修改的第三方库件。但它不支持在socket里实现用户轮询代码,用户要轮转一个额外的线程去做这部分工作。
使用xmlrpcpp时,所有使用都通过XMLRPSManager组件,所有与master的通信都通过master::execute函数。3.PollSet/PollManager
在最底层,所有非xmlrpc网络通过poll()实现,被PollSet类管理。4.连接和传输
传输系统试图使底层的传输原型(TCP、UDP)变得通用。有一个基本抽象类Transport(http://docs.ros.org/api/roscpp/html/classros_1_1Transport.html)它被TransportTCP类和TransportUDP类所继承,它们实现了read()方法和write()方法。
在更高层,Connection类(http://docs.ros.org/api/roscpp/html/classros_1_1Connection.html)提供了一个基于回调的方式像传输层读写数据。这个类管理有多少数据被发送和接收,当操作完成时,调用回调函数。
Connection类被ConnectionManager类管理,这个类主要负责管理TCP/UDP连接,并且保持Connection对象的指针,直到它们被delete掉。当ConnectionManager接到一个新来到的TCP或UDP连接时,它将以话题连接还是服务连接创建一个TransportSubscriberLink或是ServiceClientLink
5.初始化
这个主题在节点初始化部分已经讲过,这里再深入一下。roscpp有两部分的初始化,init()方法和start()方法。init()方法做很少工作,主要就是解析一下环境和命令行参数,init()不允许参与实际的连接,因而,用户可以手动检测像是master启动没有,有没有按自定义的行为启动这样的事。init()方法也不会启动任何线程。start()方法参与大部分实际的初始化工作。start()可以手动调用,这样之后要调用shutdown()。或者在第一个节点句柄被创建时自动被调用,当最后一个节点句柄被销毁时,shutdown()被自动调用。
start()将1.初始化所有的组件
2.安装SIGINT信号句柄,如果需要的话
3.创建rosout log4cxx添加器
4.创建logger服务
5.检查/use_sim_time参数,如果被设置,订阅/clock话题
6.话题
话题被TopicManager管理,它维护Subscription和Pulication列表。
1)话题订阅
Subscription类管理话题的订阅。一个话题只能有一个Subscription类,如果很多消息订阅者共同订阅一个话题,则它们共享一个连接,以节省带宽资源。Subscription类对象管理N个PublisherLink对象,每一个PubllisherLink对象连接着一个该话题的不同的Publisher.
有两种不同的PublisherLink对象,TransportPublisherLink通过传输层连接着一个publisher,而IntraprocessPublisherLink连接一个本地、内部线程的publisher,传输消息数据时可以跳过传输层(可以的话提供免复制消息传递)。
publisherUpdate:当一个话题发布者在master注册之后,这个节点将接受一个“publisherUpdate” xmlrpc调用,它使Subscription::pubUpdate指向正确的话题。对于非内部线程连接,这将启动一个对新publisher的异步的xmlrpc请求,当请求通过时,Subscription::pendingConnectionDone()方法被调用。一旦这个请求完成,一个新的TransportPublisherLink对象为这个publisher建立。
Retry:重试。如果一个TCP TransportPublisherLink对象与它的publisher连接中断,它将尝试重新与之连接。它的第一次重试被设置为连接断开之后的100ms,以后重试时间间隔将加倍,直到20s后它被盖掉。
消息订阅回调和去序列化:当一个消息接收时,它被压入一个消息订阅队列SubscriptionQueue,这个队列将在队列满时抛弃旧消息。值的注意的是,这时消息还没有被去序列化。一个MessageDeserializer对象将被压入队列,而非消息本身,这个对象被所有消息回调所共有,这意味着:
因为队列满而被抛弃的消息将不会被去序列化。
直到第一次与订阅相关的回调函数被调用时消息才会被去序列化。
2)话题发布
对一个话题的发布,由Publication类来管理。对于每一个话题,仅能有一个Publication对象。对于相同话题的许多个话题发布者,它们共享连接。
Publication对象管理N个SubscriberLink对象,每一个对象连接这个话题的一个订阅者。与消息发布相似,消息订阅SubscriberLink对象也有两种类型,使用传输层的TransportSubscriberLink和不经过传输层的IntraprocessSubscriberLink.
3)内部线程无复制支持
roscpp使用boost::shared_ptr和rtti(Runtime Type Information,运行时类型信息)的组合提供基本安全的无复制内部线程间的消息传递。基本安全是指消息发布者对于自己已经发布的消息没有修改的能力。注意,这仅仅适用于消息被作为boost::shared_ptr类型传递的情况。否则,它将还会经历序列化/去序列化的过程,即使它已经不经过传输层。内部线程消息所经历的过程:
Publisher::publish() 被发布
TopicManager::publish()
调用Publication::getPublishTypes()判断消息订阅者(发布消息针对的对象)的类型
如果存在‘需要序列化’的消息订阅者,序列化消息
如果不存在任何‘免复制’的消息订阅者,立即清除消息指针和rtti信息
Publication::publish()
寻找这个Publication中是否有InteraprocessSubscriberLink
如果有,直接封装消息给它.
IntraprocessSubscriberLink::enqueueMessage()
IntraprocessPublisherLink::handleMessage()
Subscription::handleMessage()
对于每一个subscriber,检查rtti信息是否一致,如果是,则将消息添加进其订阅队列,直到ros::spin()调用时,调用回调函数处理消息。
这些中的有些部分是不必须的。