Writing a Simple Publisher and Subscriber

c/c++

浏览数:93

2019-8-19

用c++实现一个publisher/subscriber

publisher

#include "ros/ros.h"
#include "std_msgs/String.h"

#include <sstream>

/**
 * This tutorial demonstrates simple sending of messages over the ROS system.
 */
int main(int argc, char **argv)
{
  /**
   * The ros::init() function needs to see argc and argv so that it can perform
   * any ROS arguments and name remapping that were provided at the command line.
   * For programmatic remappings you can use a different version of init() which takes
   * remappings directly, but for most command-line programs, passing argc and argv is
   * the easiest way to do it.  The third argument to init() is the name of the node.
   *
   * You must call one of the versions of ros::init() before using any other
   * part of the ROS system.
   */
  ros::init(argc, argv, "talker");

  /**
   * NodeHandle is the main access point to communications with the ROS system.
   * The first NodeHandle constructed will fully initialize this node, and the last
   * NodeHandle destructed will close down the node.
   */
  ros::NodeHandle n;

  /**
   * The advertise() function is how you tell ROS that you want to
   * publish on a given topic name. This invokes a call to the ROS
   * master node, which keeps a registry of who is publishing and who
   * is subscribing. After this advertise() call is made, the master
   * node will notify anyone who is trying to subscribe to this topic name,
   * and they will in turn negotiate a peer-to-peer connection with this
   * node.  advertise() returns a Publisher object which allows you to
   * publish messages on that topic through a call to publish().  Once
   * all copies of the returned Publisher object are destroyed, the topic
   * will be automatically unadvertised.
   *
   * The second parameter to advertise() is the size of the message queue
   * used for publishing messages.  If messages are published more quickly
   * than we can send them, the number here specifies how many messages to
   * buffer up before throwing some away.
   */
    
  //  
  ros::Publisher chatter_pub = n.advertise<std_msgs::String>("chatter", 1000);

  ros::Rate loop_rate(10);

  /**
   * A count of how many messages we have sent. This is used to create
   * a unique string for each message.
   */
  int count = 0;
  while (ros::ok())
  {
    /**
     * This is a message object. You stuff it with data, and then publish it.
     */
    std_msgs::String msg;

    std::stringstream ss;
    ss << "hello world " << count;
    msg.data = ss.str();

    ROS_INFO("%s", msg.data.c_str());

    /**
     * The publish() function is how you send messages. The parameter
     * is the message object. The type of this object must agree with the type
     * given as a template parameter to the advertise<>() call, as was done
     * in the constructor above.
     */
    chatter_pub.publish(msg);

    ros::spinOnce();

    loop_rate.sleep();
    ++count;
  }


  return 0;
}
  • 初始化ros system
    ros::init(argc, argv, “talker”); //使得可以通过命令行进行name remapping
  • 告知master程序想向什么topic publish何种type的msg
    ros::Publisher chatter_pub = n.advertise (“chatter”, 1000);
  • publish
    chatter_pub.publish(msg);

几个知识点:

  • ros::init(argc, argv, “talker”);中的”talker”是node name。不可以含/,并且该name要全局唯一.

    Node names must be unique in a running system The name used here must be a base name, ie. it cannot have a / in it.

  • ros::NodeHandle n;
    第一个handle会对node初始化,最后一个destruct的handle会clean掉该node正在使用的相关资源.

    Create a handle to this process’ node. The first NodeHandle created will actually do the initialization of the node, and the last one destructed will cleanup any resources the node was using.

  • n.advertise (“chatter”, 1000)
    第二个参数含义为队列大小,当消费方消费过慢,比如发了1001个msg,还无人sub,那么第一条会被丢弃.

    his lets the master tell any nodes listening on chatter that we are going to publish data on that topic. The second argument is the size of our publishing queue. In this case if we are publishing too quickly it will buffer up a maximum of 1000 messages before beginning to throw away old ones.

  • ros::Publisher chatter_pub = n.advertise (“chatter”, 1000);
    注意,出了作用域,chatter_pub将不再可以向相应topic发送消息

    NodeHandle::advertise() returns a ros::Publisher object, which serves two purposes: 1) it contains a publish() method that lets you publish messages onto the topic it was created with, and 2) when it goes out of scope, it will automatically unadvertise.

  • ros::Rate loop_rate(10);
    10代表10HZ,即每秒10次,这样loop_rate.sleep()的时候自己计算该sleep多久;
  • ros::ok() will return false if:
    • 收到SIGINT信号(ctrl + c)
    • 被同名node踢下网络
    • 程序里调用了ros::shutdown()
    • 所有的ros::NodeHandles都destroy了
  • ros::spinOnce();
    如果不加spinOnce,回调函数将不会被调用.

    Calling ros::spinOnce() here is not necessary for this simple program, because we are not receiving any callbacks. However, if you were to add a subscription into this application, and did not have ros::spinOnce() here, your callbacks would never get called. So, add it for good measure.

subscriber

#include "ros/ros.h"
#include "std_msgs/String.h"

/**
 * This tutorial demonstrates simple receipt of messages over the ROS system.
 */
void chatterCallback(const std_msgs::String::ConstPtr& msg)
{
  ROS_INFO("I heard: [%s]", msg->data.c_str());
}

int main(int argc, char **argv)
{
  /**
   * The ros::init() function needs to see argc and argv so that it can perform
   * any ROS arguments and name remapping that were provided at the command line.
   * For programmatic remappings you can use a different version of init() which takes
   * remappings directly, but for most command-line programs, passing argc and argv is
   * the easiest way to do it.  The third argument to init() is the name of the node.
   *
   * You must call one of the versions of ros::init() before using any other
   * part of the ROS system.
   */
  ros::init(argc, argv, "listener");

  /**
   * NodeHandle is the main access point to communications with the ROS system.
   * The first NodeHandle constructed will fully initialize this node, and the last
   * NodeHandle destructed will close down the node.
   */
  ros::NodeHandle n;

  /**
   * The subscribe() call is how you tell ROS that you want to receive messages
   * on a given topic.  This invokes a call to the ROS
   * master node, which keeps a registry of who is publishing and who
   * is subscribing.  Messages are passed to a callback function, here
   * called chatterCallback.  subscribe() returns a Subscriber object that you
   * must hold on to until you want to unsubscribe.  When all copies of the Subscriber
   * object go out of scope, this callback will automatically be unsubscribed from
   * this topic.
   *
   * The second parameter to the subscribe() function is the size of the message
   * queue.  If messages are arriving faster than they are being processed, this
   * is the number of messages that will be buffered up before beginning to throw
   * away the oldest ones.
   */
  ros::Subscriber sub = n.subscribe("chatter", 1000, chatterCallback);

  /**
   * ros::spin() will enter a loop, pumping callbacks.  With this version, all
   * callbacks will be called from within this thread (the main one).  ros::spin()
   * will exit when Ctrl-C is pressed, or the node is shutdown by the master.
   */
  ros::spin();

  return 0;
}
  • 初始化ROS
  • subscribe chatter topic
  • spin,等待msg到达
  • msg到达时,回调函数会被调用

和publisher类似

  • ros::Subscriber sub = n.subscribe(“chatter”, 1000, chatterCallback);
    只是需要指定一个回调函数,作为收到消息时的处理函数.
    void chatterCallback(const std_msgs::String::ConstPtr& msg),回调传入的是一个boost:share_ptr。你可以存储这个ptr,不用担心其指向的msg被删除.

    This is the callback function that will get called when a new message has arrived on the chatter topic. The message is passed in a boost shared_ptr, which means you can store it off if you want, without worrying about it getting deleted underneath you, and without copying the underlying data.

  • ros::spin()

    ros::spin() enters a loop, calling message callbacks as fast as possible. Don’t worry though, if there’s nothing for it to do it won’t use much CPU. ros::spin() will exit once ros::ok() returns false
    可能类似于select之类的吧,循环并不会太耗cpu。

编译

修改CMakeLists.txt

add_executable(talker src/talker.cpp)
target_link_libraries(talker ${catkin_LIBRARIES})
add_dependencies(talker beginner_tutorials_generate_messages_cpp)

add_executable(listener src/listener.cpp)
target_link_libraries(listener ${catkin_LIBRARIES})
add_dependencies(listener beginner_tutorials_generate_messages_cpp)


进入顶层目录执行make.

This will create two executables, talker and listener, which by default will go into package directory of your devel space, located by default at ~/catkin_ws/devel/lib/ .

测试publisher和sunscriber



https://askubuntu.com/questions/690631/executables-vs-shared-objects
之前一直用的centos,并且工作项目里bin和lib下的文件是严格区别开来的,一直理解的是可执行程序是executable,而lib下的是动态库shared object.这个理解实际上是有误的.

实际测试,先启动talker,后启动listener,listener只会消费他启动之后收到的监听topic上的消息.
再举个例子,先启动talker,关闭talker,再启动listener,listener不会受到任何消息.
实际上,master并不承担消息中转的作用,他不是消息中间件,各个node只是向他注册消息,告知master自己的身份,想发或想收什么样的消息.node之间还是直接通信的

作者:sdu20112013