【ROS】源码分析-消息订阅与发布

说明

本文通过NodeHandle::subscribePublication::publish()源码作为入口,来分析PubNode、SubNode之间是网络连接是如何建立的,消息是如何发布的,topic队列到底是如何实现的。

源码目录

ros_comm/clients/roscpp: ROS Node和底层的RPC通讯协议实现,都是c++代码。
ros_comm/tools/rosmaster: ROS Master的功能代码,都是python代码。
ros_tutorials/turtlsim:小海龟的PubNode和SubNode。

源码分析

各个组件 init & start

各个组件是如何启动的

int main(int argc, char** argv)
{
  ros::init(argc, argv, "teleop_turtle");
}

开始前先看下ros::init中的主要初始化代码:这里初始化了网络工具、master服务工具、本Node管理工具、Log工具、参数服务工具

(源码文件:ros_comm/clients/roscpp/src/libros/init.cpp)
void ros::init(const M_string& remappings, const std::string& name, uint32_t options) {
    network::init(remappings);
    master::init(remappings);
    // names:: namespace is initialized by this_node
    this_node::init(name, remappings, options);
    file_log::init(remappings);
    param::init(remappings);
}

然后是NodeHanle创建(构造函数),并调用ros::start(),从而启动各个组件

(源码文件:ros_comm/clients/roscpp/src/libros/node_handle.cpp)
NodeHandle::NodeHandle(const NodeHandle& parent, const std::string& ns, const M_string& remappings)
: collection_(0)
{
  namespace_ = parent.getNamespace();
  construct(ns, false);
}
void NodeHandle::construct(const std::string& ns, bool validate_name) {
   ros::start();
}

void start() {
  param::param("/tcp_keepalive", TransportTCP::s_use_keepalive_, TransportTCP::s_use_keepalive_);
  PollManager::instance()->addPollThreadListener(checkForShutdown);
  XMLRPCManager::instance()->bind("shutdown", shutdownCallback);
  TopicManager::instance()->start();
  ServiceManager::instance()->start();
  ConnectionManager::instance()->start();
  PollManager::instance()->start();
  XMLRPCManager::instance()->start();
}

XMLRPCManager 是ROS中RPC通讯的基础工具,下面看一下主要的功能:这里注册了几个主要的XMLHTTP方法(如publisherUpdate)的处理函数(如pubUpdateCallback), XMLHTTP请求本Node的request会先进入这些设定的callback方法,这些callback进过初步解析参数然后调用本Node具体的实现方法。

(源码文件:ros_comm/clients/roscpp/src/libros/topic_manager.cpp)
void TopicManager::start()
{
  boost::mutex::scoped_lock shutdown_lock(shutting_down_mutex_);
  shutting_down_ = false;

  poll_manager_ = PollManager::instance();
  connection_manager_ = ConnectionManager::instance();
  xmlrpc_manager_ = XMLRPCManager::instance();

  xmlrpc_manager_->bind("publisherUpdate", boost::bind(&TopicManager::pubUpdateCallback, this, boost::placeholders::_1, boost::placeholders::_2));
  xmlrpc_manager_->bind("requestTopic", boost::bind(&TopicManager::requestTopicCallback, this, boost::placeholders::_1, boost::placeholders::_2));
  xmlrpc_manager_->bind("getBusStats", boost::bind(&TopicManager::getBusStatsCallback, this, boost::placeholders::_1, boost::placeholders::_2));
  xmlrpc_manager_->bind("getBusInfo", boost::bind(&TopicManager::getBusInfoCallback, this, boost::placeholders::_1, boost::placeholders::_2));
  xmlrpc_manager_->bind("getSubscriptions", boost::bind(&TopicManager::getSubscriptionsCallback, this, boost::placeholders::_1, boost::placeholders::_2));
  xmlrpc_manager_->bind("getPublications", boost::bind(&TopicManager::getPublicationsCallback, this, boost::placeholders::_1, boost::placeholders::_2));

  poll_manager_->addPollThreadListener(boost::bind(&TopicManager::processPublishQueues, this));
}

Subscriber注册过程分析(消息订阅)

假设:publisher先启动,然后再启动 subscriber

subscriber启动后,向Master注册自己,并订阅topic

velocity_sub_ = nh_.subscribe("cmd_vel", 1, &Turtle::velocityCallback, this);

然后进入Master的registerSubscriber方法,注册完成后Master返回了Publisher的服务地址(pub_uris )

def registerSubscriber(self, caller_id, topic, topic_type, caller_api):

@apivalidate([], ( is_topic('topic'), valid_type_name('topic_type'), is_api('caller_api')))
    def registerSubscriber(self, caller_id, topic, topic_type, caller_api):
        try:
            self.ps_lock.acquire()
            # 1.记录subNode信息:id、topic、subServerURL
            self.reg_manager.register_subscriber(topic, caller_id, caller_api)
            if not topic in self.topics_types and topic_type != rosgraph.names.ANYTYPE:
                self.topics_types[topic] = topic_type
            mloginfo("+SUB [%s] %s %s",topic, caller_id, caller_api)
            # 2.返回发布者(publisher)的信息给订阅者(subscriber)
            pub_uris = self.publishers.get_apis(topic)
        finally:
            self.ps_lock.release()
        return 1, "Subscribed to [%s]"%topic, pub_uris

拿到pub_urils后进入Subscription.pubUpdate开始更新publisher的服务地址(与下图中的Subscriber Node中的pubUpdate 完全一致)

(源码文件:ros_comm/clients/roscpp/src/libros/topic_manager.cpp)

bool TopicManager::subscribe(const SubscribeOptions& ops){
    SubscriptionPtr s(boost::make_shared<Subscription>(ops.topic, md5sum, datatype, ops.transport_hints));
    s->addCallback(ops.helper, ops.md5sum, ops.callback_queue, ops.queue_size, ops.tracked_object, ops.allow_concurrent_callbacks);

    registerSubscriber(s, ops.datatype)
}

bool TopicManager::registerSubscriber(const SubscriptionPtr& s, const string &datatype) {

	XmlRpcValue args, result, payload;
	args[0] = this_node::getName();
	args[1] = s->getName(); //ops.topic
	args[2] = datatype;
	args[3] = xmlrpc_manager_->getServerURI();

	master::execute("registerSubscriber", args, result, payload, true);

	vector<string> pub_uris;
	for (int i = 0; i < payload.size(); i++)
	{
		if (payload[i] != xmlrpc_manager_->getServerURI())
		{
		  pub_uris.push_back(string(payload[i]));
		}
	}
	s->pubUpdate(pub_uris);//更新pubUrl: Subscription.pubUpdate
}

下面看一下SubNode如何更新pubUrls

(源码文件:ros_comm/clients/roscpp/src/libros/subscription.cpp)

bool Subscription::pubUpdate(const V_string& new_pubs) {

	//1. 打印 new_pubs (最新的pubUrls) 和 publisher_links_(本地存量记录的pubUrls)
	ROSCPP_LOG_DEBUG("Publisher update for [%s]: %s", name_.c_str(), ss.str().c_str());

	//2. 遍历 publisher_links_ 找出已经下线的 pubUrl (即:不在new_pubs中的url), 稍后进行释放 drop
	subtractions.push_back(*spc);

	//3. 对比本地存量记录的pubUrls找出本次新增的urls, 并尝试建立连接
	additions.push_back(*up_i);
	for (V_string::iterator i = additions.begin();
            i != additions.end(); ++i)
    {	
    	 retval &= negotiateConnection(*i);
    }
}

bool Subscription::negotiateConnection(const std::string& xmlrpc_uri){
	//与publisher通信,协商改topic的通信协议TCP/UDP
	c->executeNonBlock("requestTopic", params)
	
	//创建连接(实际是在 XMLRPCManager::serverThreadFunc 方法中创建连接,连接成功后通过回调pendingConnectionDone,最后publisher_links_.push_back(link))
	PendingConnectionPtr conn(boost::make_shared<PendingConnection>(c, udp_transport, shared_from_this(), xmlrpc_uri));
	XMLRPCManager::instance()->addASyncConnection(conn);
	// Put this connection on the list that we'll look at later.
	{
		boost::mutex::scoped_lock pending_connections_lock(pending_connections_mutex_);
		pending_connections_.insert(conn);
	}
}

最后Subscriber就主动建立了与Publisher的连接。

Publisher注册过程分析(Topic声明/发布)

假设:subscriber先启动,然后再启动 publisher

初始化完成后,然后Publisher向master发布topic信息(即注册自己的id&topic&serverUrl信息)

ros::NodeHandle nh_;
twist_pub_ = nh_.advertise<geometry_msgs::Twist>("turtle1/cmd_vel", 1);

然后进入Master的registerPublisher方法,在注册过程中Master会通知(_notify_topic_subscribers)已订阅该topic的Subscriber(这里假设Subscriber先启动)Publisher的服务地址(pub_uris )。

如何进入的Master方法在《【ROS】源码分析-服务发现原理》中有讲解:master::execute("registerPublisher"

(源码文件:ros_comm/tools/rosmaster/src/rosmaster/master_api.py)
    @apivalidate([], ( is_topic('topic'), valid_type_name('topic_type'), is_api('caller_api')))
    def registerPublisher(self, caller_id, topic, topic_type, caller_api):
        try:
            self.ps_lock.acquire()
            # 1.记录pubNode信息:id、topic、pubServerURL
            self.reg_manager.register_publisher(topic, caller_id, caller_api)
            # don't let '*' type squash valid typing
            if topic_type != rosgraph.names.ANYTYPE or not topic in self.topics_types:
                self.topics_types[topic] = topic_type
            pub_uris = self.publishers.get_apis(topic)
            sub_uris = self.subscribers.get_apis(topic)
            # 2.通知订阅者该topic的pubisherServer的信息
            self._notify_topic_subscribers(topic, pub_uris, sub_uris)
            mloginfo("+PUB [%s] %s %s",topic, caller_id, caller_api)
            # 3.返回订阅者(subscriber)的信息给发布者(publisher)
            sub_uris = self.subscribers.get_apis(topic)            
        finally:
            self.ps_lock.release()
        return 1, "Registered [%s] as publisher of [%s]"%(caller_id, topic), sub_uris

Subscriber得知pub_urls后会主动尝试与Publisher建立连接(与Subscriber注册过程中的pubUpdate完全一致),整个过程如下图所示:
Publisher注册和Topic声明过程

Publisher发布消息过程分析

Subscriber从pubUpdate中开始一步建立与Publisher的网络连接通道(conn),主要过程如下图:
在这里插入图片描述

网络连接建立玩后,就是开始发布消息了

geometry_msgs::Twist twist;
twist.angular.z = a_scale_*angular_;
twist.linear.x = l_scale_*linear_;
twist_pub_.publish(twist);    
  • Publisher先发布消息到本地队列,然后poll_manager会轮训经本地队列中的消息发给Subscriber。
  • Subscriber接收到消息后放到Callback本地队列,AsyncSpinner会轮训调度读取本地队列中的数据,并根据消息的type匹配出callback_func,然后进行回调。

在这里插入图片描述

Topic消息队列实现是怎样的

基于topic队列发送消息过程中的本地队列默认实现是

init.cpp

void ros::init(const M_string& remappings, const std::string& name, uint32_t options){
    g_global_queue.reset(new CallbackQueue);
}

CallbackQueue* getGlobalCallbackQueue()
{
  return g_global_queue.get();
}

callback_queue.h

class ROSCPP_DECL CallbackQueue : public CallbackQueueInterface 
{
    struct CallbackInfo
   {
    CallbackInfo()
	    : removal_id(0)
	    , marked_for_removal(false)
	    {}
	    CallbackInterfacePtr callback;
	    uint64_t removal_id;
	    bool marked_for_removal;
	};

    typedef std::deque<CallbackInfo> D_CallbackInfo;
    D_CallbackInfo callbacks_;
}

publication.h

class ROSCPP_DECL Publication
{
  typedef std::vector<SerializedMessage> V_SerializedMessage;
  V_SerializedMessage publish_queue_;
}

所以Publisher/Subscriber本地队列是内存队列/列表(std::dequestd::vector)。

  • 6
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
### 回答1: ros-melodic-can-msgs 是一个 ROS 包,用于在 ROS 中使用和操作 Controller Area Network (CAN) 协议进行通信。其中包含了相关的消息和服务类型,用于在 ROS 系统中传输 CAN 相关的数据。你可以在这里找到 ros-melodic-can-msgs 的源代码:https://github.com/ros-drivers/can_msgs ros-melodic-can-msgs 是针对 ROS Melodic 版本开发的,Melodic 是 ROS 的一个发行版本。如果你正在使用其他版本的 ROS,你需要使用对应版本的 ros-can-msgs 包。 ### 回答2: ros-melodic-can-msgs是ROS(机器人操作系统)的一个软件包,它是用于CAN(控制器局域网络)通信的消息定义和工具。CAN是一种常用于车辆和其他实时控制系统中的网络通信协议。 这个软件包包含了一些重要的消息类型,用于在ROS中进行CAN数据的传输和解析。例如,它定义了CAN消息的ID、数据格式和长度等信息。这些消息类型可以让ROS系统和连接在CAN总线上的设备进行通信。 在ros-melodic-can-msgs源码中,我们可以看到定义了各种用于CAN通信的消息类型,比如CAN_Frame、CAN_FD_Frame和CAN_Error等。这些消息类型可以用于接收和发送CAN数据,并且还包括了一些附加的属性,比如时间戳等。 此外,源码中还提供了一些工具和函数,用于CAN数据的解析和处理。例如,它提供了一个解析CAN帧的函数,可以从CAN消息中提取数据和控制信息。同时,还可以将ROS中的消息转换为CAN消息,并将CAN消息转换为ROS消息,方便在ROS系统中使用CAN数据。 总之,ros-melodic-can-msgs是一个用于CAN通信的ROS软件包,提供了消息定义和工具,用于在ROS系统中处理和交换CAN数据。通过使用这个软件包,开发人员可以方便地使用ROS构建与CAN设备之间的通信,实现车辆控制和实时系统的开发。 ### 回答3: ros-melodic-can-msgs是ROS的一个软件包,用于处理Controller Area Network(CAN)通信协议的消息。这个软件包提供了在ROS系统中处理CAN消息的功能。 CAN是一种广泛应用于汽车、工业控制和机器人等领域的通信协议,它允许不同的设备通过总线共享数据。ros-melodic-can-msgs软件包的目的是为ROS系统添加对CAN通信的支持。 这个软件包包含了一系列的消息类型,用于定义在ROS系统中传输CAN消息。这些消息类型包括CAN帧消息类型、CAN控制器状态消息类型以及一些其他辅助消息类型。通过这些消息类型,ROS系统可以接收和发送CAN消息,实现与CAN总线上其他设备的通信。 除了消息类型,ros-melodic-can-msgs还提供了一些辅助工具和函数,用于处理CAN消息。这些工具和函数可以用于解析CAN帧消息、生成CAN帧消息以及读取和修改CAN控制器的状态。 使用ros-melodic-can-msgs,开发者可以将CAN通信集成到ROS系统中。他们可以通过订阅CAN消息话题来接收其他设备发送的CAN消息,并可以通过发布CAN消息话题来向其他设备发送CAN消息。这样,开发者可以方便地与CAN总线上的其他设备进行数据交换,并能够利用ROS系统提供的各种功能进行数据处理和分析。 总之,ros-melodic-can-msgs是一个在ROS系统中处理CAN通信的软件包,它提供了一系列的消息类型、工具和函数,使得开发者可以方便地与CAN总线上的设备进行数据交换和通信。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值