ros的init机制续篇

2023-05-16

这篇博客主要探讨init的实现过程

ros::init(argc, argv, "add_two_ints_client")

实现代码在init.cpp文件,这里直接给出来

/*
 * Software License Agreement (BSD License)
 *
 *  Copyright (c) 2009, Willow Garage, Inc.
 *  All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions
 *  are met:
 *
 *   * Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *   * Redistributions in binary form must reproduce the above
 *     copyright notice, this list of conditions and the following
 *     disclaimer in the documentation and/or other materials provided
 *     with the distribution.
 *   * Neither the name of Willow Garage, Inc. nor the names of its
 *     contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 *  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 *  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 *  FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 *  COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 *  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 *  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 *  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 *  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 *  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
 *  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 *  POSSIBILITY OF SUCH DAMAGE.
 */

#include "ros/init.h"
#include "ros/names.h"
#include "ros/xmlrpc_manager.h"
#include "ros/poll_manager.h"
#include "ros/connection_manager.h"
#include "ros/topic_manager.h"
#include "ros/service_manager.h"
#include "ros/this_node.h"
#include "ros/network.h"
#include "ros/file_log.h"
#include "ros/callback_queue.h"
#include "ros/param.h"
#include "ros/rosout_appender.h"
#include "ros/subscribe_options.h"
#include "ros/transport/transport_tcp.h"
#include "ros/internal_timer_manager.h"
#include "xmlrpcpp/XmlRpcSocket.h"

#include "roscpp/GetLoggers.h"
#include "roscpp/SetLoggerLevel.h"
#include "roscpp/Empty.h"

#include <ros/console.h>
#include <ros/time.h>
#include <rosgraph_msgs/Clock.h>

#include <algorithm>

#include <signal.h>

#include <cstdlib>

namespace ros
{

namespace master
{
void init(const M_string& remappings);
}

namespace this_node
{
void init(const std::string& names, const M_string& remappings, uint32_t options);
}

namespace network
{
void init(const M_string& remappings);
}

namespace param
{
void init(const M_string& remappings);
}

namespace file_log
{
void init(const M_string& remappings);
}

CallbackQueuePtr g_global_queue;
ROSOutAppender* g_rosout_appender;
static CallbackQueuePtr g_internal_callback_queue;

static bool g_initialized = false;
static bool g_started = false;
static bool g_atexit_registered = false;
static boost::mutex g_start_mutex;
static bool g_ok = false;
static uint32_t g_init_options = 0;
static bool g_shutdown_requested = false;
static volatile bool g_shutting_down = false;
static boost::recursive_mutex g_shutting_down_mutex;
static boost::thread g_internal_queue_thread;

bool isInitialized()
{
  return g_initialized;
}

bool isShuttingDown()
{
  return g_shutting_down;
}

void checkForShutdown()
{
  if (g_shutdown_requested)
  {
    // Since this gets run from within a mutex inside PollManager, we need to prevent ourselves from deadlocking with
    // another thread that's already in the middle of shutdown()
    boost::recursive_mutex::scoped_try_lock lock(g_shutting_down_mutex, boost::defer_lock);
    while (!lock.try_lock() && !g_shutting_down)
    {
      ros::WallDuration(0.001).sleep();
    }

    if (!g_shutting_down)
    {
      shutdown();
    }

    g_shutdown_requested = false;
  }
}

void requestShutdown()
{
  g_shutdown_requested = true;
}

void atexitCallback()
{
  if (ok() && !isShuttingDown())
  {
    ROSCPP_LOG_DEBUG("shutting down due to exit() or end of main() without cleanup of all NodeHandles");
    g_started = false; // don't shutdown singletons, because they are already destroyed
    shutdown();
  }
}

void shutdownCallback(XmlRpc::XmlRpcValue& params, XmlRpc::XmlRpcValue& result)
{
  int num_params = 0;
  if (params.getType() == XmlRpc::XmlRpcValue::TypeArray)
    num_params = params.size();
  if (num_params > 1)
  {
    std::string reason = params[1];
    ROS_WARN("Shutdown request received.");
    ROS_WARN("Reason given for shutdown: [%s]", reason.c_str());
    requestShutdown();
  }

  result = xmlrpc::responseInt(1, "", 0);
}

bool getLoggers(roscpp::GetLoggers::Request&, roscpp::GetLoggers::Response& resp)
{
  std::map<std::string, ros::console::levels::Level> loggers;
  bool success = ::ros::console::get_loggers(loggers);
  if (success)
  {
    for (std::map<std::string, ros::console::levels::Level>::const_iterator it = loggers.begin(); it != loggers.end(); it++)
    {
      roscpp::Logger logger;
      logger.name = it->first;
      ros::console::levels::Level level = it->second;
      if (level == ros::console::levels::Debug)
      {
        logger.level = "debug";
      }
      else if (level == ros::console::levels::Info)
      {
        logger.level = "info";
      }
      else if (level == ros::console::levels::Warn)
      {
        logger.level = "warn";
      }
      else if (level == ros::console::levels::Error)
      {
        logger.level = "error";
      }
      else if (level == ros::console::levels::Fatal)
      {
        logger.level = "fatal";
      }
      resp.loggers.push_back(logger);
    }
  }
  return success;
}

bool setLoggerLevel(roscpp::SetLoggerLevel::Request& req, roscpp::SetLoggerLevel::Response&)
{
  std::transform(req.level.begin(), req.level.end(), req.level.begin(), (int(*)(int))std::toupper);

  ros::console::levels::Level level;
  if (req.level == "DEBUG")
  {
    level = ros::console::levels::Debug;
  }
  else if (req.level == "INFO")
  {
    level = ros::console::levels::Info;
  }
  else if (req.level == "WARN")
  {
    level = ros::console::levels::Warn;
  }
  else if (req.level == "ERROR")
  {
    level = ros::console::levels::Error;
  }
  else if (req.level == "FATAL")
  {
    level = ros::console::levels::Fatal;
  }
  else
  {
    return false;
  }

  bool success = ::ros::console::set_logger_level(req.logger, level);
  if (success)
  {
    console::notifyLoggerLevelsChanged();
  }

  return success;
}

bool closeAllConnections(roscpp::Empty::Request&, roscpp::Empty::Response&)
{
  ROSCPP_LOG_DEBUG("close_all_connections service called, closing connections");
  ConnectionManager::instance()->clear(Connection::TransportDisconnect);
  return true;
}

void clockCallback(const rosgraph_msgs::Clock::ConstPtr& msg)
{
  Time::setNow(msg->clock);
}

CallbackQueuePtr getInternalCallbackQueue()
{
  if (!g_internal_callback_queue)
  {
    g_internal_callback_queue.reset(new CallbackQueue);
  }

  return g_internal_callback_queue;
}

void basicSigintHandler(int sig)
{
  (void)sig;
  ros::requestShutdown();
}

void internalCallbackQueueThreadFunc()
{
  disableAllSignalsInThisThread();

  CallbackQueuePtr queue = getInternalCallbackQueue();

  while (!g_shutting_down)
  {
    queue->callAvailable(WallDuration(0.1));
  }
}

bool isStarted()
{
  return g_started;
}

void start()
{
  boost::mutex::scoped_lock lock(g_start_mutex);
  if (g_started)
  {
    return;
  }

  g_shutdown_requested = false;
  g_shutting_down = false;
  g_started = true;
  g_ok = true;

  bool enable_debug = false;
  std::string enable_debug_env;
  if ( get_environment_variable(enable_debug_env,"ROSCPP_ENABLE_DEBUG") )
  {
    try
    {
      enable_debug = boost::lexical_cast<bool>(enable_debug_env.c_str());
    }
    catch (boost::bad_lexical_cast&)
    {
    }
  }

#ifdef _MSC_VER
  if (env_ipv6)
  {
    free(env_ipv6);
  }
#endif

  param::param("/tcp_keepalive", TransportTCP::s_use_keepalive_, TransportTCP::s_use_keepalive_);

  PollManager::instance()->addPollThreadListener(checkForShutdown);
  XMLRPCManager::instance()->bind("shutdown", shutdownCallback);

  initInternalTimerManager();

  TopicManager::instance()->start();
  ServiceManager::instance()->start();
  ConnectionManager::instance()->start();
  PollManager::instance()->start();
  XMLRPCManager::instance()->start();

  if (!(g_init_options & init_options::NoSigintHandler))
  {
    signal(SIGINT, basicSigintHandler);
  }

  ros::Time::init();

  if (!(g_init_options & init_options::NoRosout))
  {
    g_rosout_appender = new ROSOutAppender;
    ros::console::register_appender(g_rosout_appender);
  }

  if (g_shutting_down) goto end;

  {
    ros::AdvertiseServiceOptions ops;
    ops.init<roscpp::GetLoggers>(names::resolve("~get_loggers"), getLoggers);
    ops.callback_queue = getInternalCallbackQueue().get();
    ServiceManager::instance()->advertiseService(ops);
  }

  if (g_shutting_down) goto end;

  {
    ros::AdvertiseServiceOptions ops;
    ops.init<roscpp::SetLoggerLevel>(names::resolve("~set_logger_level"), setLoggerLevel);
    ops.callback_queue = getInternalCallbackQueue().get();
    ServiceManager::instance()->advertiseService(ops);
  }

  if (g_shutting_down) goto end;

  if (enable_debug)
  {
    ros::AdvertiseServiceOptions ops;
    ops.init<roscpp::Empty>(names::resolve("~debug/close_all_connections"), closeAllConnections);
    ops.callback_queue = getInternalCallbackQueue().get();
    ServiceManager::instance()->advertiseService(ops);
  }

  if (g_shutting_down) goto end;

  {
    bool use_sim_time = false;
    param::param("/use_sim_time", use_sim_time, use_sim_time);

    if (use_sim_time)
    {
      Time::setNow(ros::Time());
    }

    if (g_shutting_down) goto end;

    if (use_sim_time)
    {
      ros::SubscribeOptions ops;
      ops.init<rosgraph_msgs::Clock>(names::resolve("/clock"), 1, clockCallback);
      ops.callback_queue = getInternalCallbackQueue().get();
      TopicManager::instance()->subscribe(ops);
    }
  }

  if (g_shutting_down) goto end;

  g_internal_queue_thread = boost::thread(internalCallbackQueueThreadFunc);
  getGlobalCallbackQueue()->enable();

  ROSCPP_LOG_DEBUG("Started node [%s], pid [%d], bound on [%s], xmlrpc port [%d], tcpros port [%d], using [%s] time", 
		   this_node::getName().c_str(), getpid(), network::getHost().c_str(), 
		   XMLRPCManager::instance()->getServerPort(), ConnectionManager::instance()->getTCPPort(), 
		   Time::useSystemTime() ? "real" : "sim");

  // Label used to abort if we've started shutting down in the middle of start(), which can happen in
  // threaded code or if Ctrl-C is pressed while we're initializing
end:
  // If we received a shutdown request while initializing, wait until we've shutdown to continue
  if (g_shutting_down)
  {
    boost::recursive_mutex::scoped_lock lock(g_shutting_down_mutex);
  }
}

void check_ipv6_environment() {
  char* env_ipv6 = NULL;
#ifdef _MSC_VER
  _dupenv_s(&env_ipv6, NULL, "ROS_IPV6");
#else
  env_ipv6 = getenv("ROS_IPV6");
#endif

  bool use_ipv6 = (env_ipv6 && strcmp(env_ipv6,"on") == 0);
  TransportTCP::s_use_ipv6_ = use_ipv6;
  XmlRpc::XmlRpcSocket::s_use_ipv6_ = use_ipv6;
}

void init(const M_string& remappings, const std::string& name, uint32_t options)
{
  if (!g_atexit_registered)
  {
    g_atexit_registered = true;
    atexit(atexitCallback);
  }

  if (!g_global_queue)
  {
    g_global_queue.reset(new CallbackQueue);
  }

  if (!g_initialized)
  {
    g_init_options = options;
    g_ok = true;

    ROSCONSOLE_AUTOINIT;
    // Disable SIGPIPE
#ifndef WIN32
    signal(SIGPIPE, SIG_IGN);
#endif
    check_ipv6_environment();
    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);

    g_initialized = true;
  }
}

void init(int& argc, char** argv, const std::string& name, uint32_t options)
{
  M_string remappings;

  int full_argc = argc;
  // now, move the remapping argv's to the end, and decrement argc as needed
  for (int i = 0; i < argc; )
  {
    std::string arg = argv[i];
    size_t pos = arg.find(":=");
    if (pos != std::string::npos)
    {
      std::string local_name = arg.substr(0, pos);
      std::string external_name = arg.substr(pos + 2);

      ROSCPP_LOG_DEBUG("remap: %s => %s", local_name.c_str(), external_name.c_str());
      remappings[local_name] = external_name;

      // shuffle everybody down and stuff this guy at the end of argv
      char *tmp = argv[i];
      for (int j = i; j < full_argc - 1; j++)
        argv[j] = argv[j+1];
      argv[argc-1] = tmp;
      argc--;
    }
    else
    {
      i++; // move on, since we didn't shuffle anybody here to replace it
    }
  }

  init(remappings, name, options);
}

void init(const VP_string& remappings, const std::string& name, uint32_t options)
{
  M_string remappings_map;
  VP_string::const_iterator it = remappings.begin();
  VP_string::const_iterator end = remappings.end();
  for (; it != end; ++it)
  {
    remappings_map[it->first] = it->second;
  }

  init(remappings_map, name, options);
}

std::string getROSArg(int argc, const char* const* argv, const std::string& arg)
{
  for (int i = 0; i < argc; ++i)
  {
    std::string str_arg = argv[i];
    size_t pos = str_arg.find(":=");
    if (str_arg.substr(0,pos) == arg)
    {
      return str_arg.substr(pos+2);
    }
  }
  return "";
}

void removeROSArgs(int argc, const char* const* argv, V_string& args_out)
{
  for (int i = 0; i < argc; ++i)
  {
    std::string arg = argv[i];
    size_t pos = arg.find(":=");
    if (pos == std::string::npos)
    {
      args_out.push_back(arg);
    }
  }
}

void spin()
{
  SingleThreadedSpinner s;
  spin(s);
}

void spin(Spinner& s)
{
  s.spin();
}

void spinOnce()
{
  g_global_queue->callAvailable(ros::WallDuration());
}

void waitForShutdown()
{
  while (ok())
  {
    WallDuration(0.05).sleep();
  }
}

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

bool ok()
{
  return g_ok;
}

void shutdown()
{
  boost::recursive_mutex::scoped_lock lock(g_shutting_down_mutex);
  if (g_shutting_down)
    return;
  else
    g_shutting_down = true;

  ros::console::shutdown();

  g_global_queue->disable();
  g_global_queue->clear();

  if (g_internal_queue_thread.get_id() != boost::this_thread::get_id())
  {
    g_internal_queue_thread.join();
  }
  //ros::console::deregister_appender(g_rosout_appender);
  delete g_rosout_appender;
  g_rosout_appender = 0;

  if (g_started)
  {
    TopicManager::instance()->shutdown();
    ServiceManager::instance()->shutdown();
    PollManager::instance()->shutdown();
    ConnectionManager::instance()->shutdown();
    XMLRPCManager::instance()->shutdown();
  }

  g_started = false;
  g_ok = false;
  Time::shutdown();
}

}

需要探讨的函数init是:

void init(int& argc, char** argv, const std::string& name, uint32_t options)
{
  M_string remappings;

  int full_argc = argc;
  // now, move the remapping argv's to the end, and decrement argc as needed
  for (int i = 0; i < argc; )
  {
    std::string arg = argv[i];
    size_t pos = arg.find(":=");
    if (pos != std::string::npos)
    {
      std::string local_name = arg.substr(0, pos);
      std::string external_name = arg.substr(pos + 2);

      ROSCPP_LOG_DEBUG("remap: %s => %s", local_name.c_str(), external_name.c_str());
      remappings[local_name] = external_name;

      // shuffle everybody down and stuff this guy at the end of argv
      char *tmp = argv[i];
      for (int j = i; j < full_argc - 1; j++)
        argv[j] = argv[j+1];
      argv[argc-1] = tmp;
      argc--;
    }
    else
    {
      i++; // move on, since we didn't shuffle anybody here to replace it
    }
  }

  init(remappings, name, options);
}
  • 这个函数有四个输入参数,返回值为空。
    参数1:int& argc: 引用类型,由类型的实际值引用(类似于指针)表示的数据类型。如果为某个变量分配一个引用类型,则该变量将引用(或“指向”)原始值。不创建任何副本。引用类型包括类、接口、委托和装箱值类型。这个参数用来表示输入字符串的个数,字符串是在第二个参数中给出。
    参数2:char** argv:指向指针的指针类型。定义一个指向指针的指针时,第一个指针包含了第二个指针的地址,第二个指针指向包含实际值的位置。(详细见博客)一个字符类型的指针是能够用来表示一个字符串的,所以利用字符类型的指向指针的指针是能够用来表示多个字符串。
    参数3:const std::string& name: 节点的名字。const:const是一个C语言(ANSI C)的关键字,具有着举足轻重的地位。它限定一个变量不允许被改变,产生静态作用。使用const在一定程度上可以提高程序的安全性和可靠性。这里的节点名字在函数的实现中是不能改变的,因为这里传入的参数是引用类型,一旦修改后,其他文件中的相应值也被修改,这是不可取的。那这里为什么不是传递单纯的值类型呢?
    参数4:uint32_t options: 枚举类型的节点创建的类型,其值定义如下:
    NoSigintHandler:通过ctrl-c或者ctrl-z关闭节点失效
    AnonymousName:在节点名字后面添加随机数保证节点名字的唯一性(节点的名称有大作用吗?)
    NoRosout:不广播节点的一些运行消息到/rosout话题
enum InitOption
{
  /**
 - Don't install a SIGINT handler.  You should install your own SIGINT handler in this
 - case, to ensure that the node gets shutdown correctly when it exits.
   */
  NoSigintHandler = 1 << 0,
  /** \brief Anonymize the node name.  Adds a random number to the end of your node's name, to make it unique.
   */
  AnonymousName = 1 << 1,
  /**
 - \brief Don't broadcast rosconsole output to the /rosout topic
   */
  NoRosout = 1 << 2,
};

  • 定义一个map容器
M_string remappings;

其中M_string的定义是:

typedef std::map<std::string, std::string> M_string;

map是C++ STL 的一个关联容器,它提供一对一(其中第一个可以称为关键字,每个关键字只能在map中出现一次,第二个可能称为该关键字的值)的数据处理能力,由于这个特性,它完成有可能在我们处理一对一数据的时候,在编程上提供快速通道。也就是通过第一个关键字,我们就能找到第二个关键字的数据。举个例子,在使用launch文件启动节点是需要指定一些参数,这时所定义的参数名(name)和参数值(value)就是存在这样的一个容器中,之后在代码中通过参数名去获取参数值。

<arg name="fcu_url" value="$(arg fcu_url)" />

  • 提取从字符串数组(字符类型的指向指针的指针)中提取参数。find、substr时string的常见使用类型。

#find()返回值是字母在母串中的位置(下标记录),如果没有找到,那么会返回一个特别的标记npos。(见博客)

substr()是C++语言函数,主要功能是复制子字符串,要求从指定位置开始,并具有指定的长度。如果没有指定长度_Count或_Count+_Off超出了源字符串的长度,则子字符串将延续到源字符串的结尾。
函数原型:
substr(size_type _Off = 0,size_type _Count = npos)
参数:
_Off——所需的子字符串的起始位置。字符串中第一个字符的索引为 0,默认值为0。
_Count——复制的字符数目
返回值——一个子字符串,从其指定的位置开始
具体见博客


ROSCPP_LOG_DEBUG是一个宏定义在文件file_log.h中,打印信息到终端中。

#define ROSCPP_LOG_DEBUG(...) ROS_DEBUG_NAMED("roscpp_internal", __VA_ARGS__)
ROSCPP_LOG_DEBUG
  • 将参数和参数值压入map容器中
remappings[local_name] = external_name;

调用另一个初始化函数init

void init(const M_string& remappings, const std::string& name, uint32_t options)
init(remappings, name, options);

完整的函数实现:

void init(const M_string& remappings, const std::string& name, uint32_t options)
{
  if (!g_atexit_registered)
  {
    g_atexit_registered = true;
    atexit(atexitCallback);
  }

  if (!g_global_queue)
  {
    g_global_queue.reset(new CallbackQueue);
  }

  if (!g_initialized)
  {
    g_init_options = options;
    g_ok = true;

    ROSCONSOLE_AUTOINIT;
    // Disable SIGPIPE
#ifndef WIN32
    signal(SIGPIPE, SIG_IGN);
#endif
    check_ipv6_environment();
    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);

    g_initialized = true;
  }
}

一开始g_atexit_registered是false,所以会进入if语句里面,进入之后就赋值为true。

static bool g_atexit_registered = false;

组成程序退出的回调函数:
使用atexit函数注册的退出函数是在进程正常退出时,才会被调用。如果是因为收到信号signal而导致程序退出,如kill pid,那么atexit所注册的函数将不会被调用。这个函数是定义关联在signal.h中。signal.h是C标准函数库中的信号处理部分, 定义了程序执行时如何处理不同的信号。信号用作进程间通信, 报告异常行为(如除零)、用户的一些按键组合(如同时按下Ctrl与C键,产生信号SIGINT)。

atexit(atexitCallback);

atexitCallback回调函数打印一些消息到终端和调用shutdown结束节点

void atexitCallback()
{
  if (ok() && !isShuttingDown())
  {
    ROSCPP_LOG_DEBUG("shutting down due to exit() or end of main() without cleanup of all NodeHandles");
    g_started = false; // don't shutdown singletons, because they are already destroyed
    shutdown();
  }
}

shutdown()函数的定义,解释在文件中注明
boost::recursive_mutex::scoped_lock lock(g_shutting_down_mutex);是一个递归锁。
递归锁:同一个线程在不解锁的情况下,可以多次获取锁定同一个递归锁,而且不会产生死锁。非递归锁:在不解锁的情况下,当同一个线程多次获取同一个递归锁时,会产生死锁。具体见博客。

void shutdown()
{
  boost::recursive_mutex::scoped_lock lock(g_shutting_down_mutex);//线程锁---重入锁或者称为递归锁
  if (g_shutting_down)//如果已经关闭则退出
    return;
  else
    g_shutting_down = true;//置关闭节点的标记为true

  ros::console::shutdown();//关闭日记的输出

  g_global_queue->disable();//关闭队列
  g_global_queue->clear();//清空队列

  if (g_internal_queue_thread.get_id() != boost::this_thread::get_id())
  {
    g_internal_queue_thread.join();//关闭线程
  }
  //ros::console::deregister_appender(g_rosout_appender);
  delete g_rosout_appender;//回收内存
  g_rosout_appender = 0;

  if (g_started)
  {
    TopicManager::instance()->shutdown();//关闭话题管理
    ServiceManager::instance()->shutdown();//关闭服务管理
    PollManager::instance()->shutdown();//关闭线程池
    ConnectionManager::instance()->shutdown();//关闭通信连接
    XMLRPCManager::instance()->shutdown();//关闭话题之间通信
  }
  g_started = false;
  g_ok = false;
  Time::shutdown();//关闭时间服务
}

接着init的函数继续分析

if (!g_global_queue)//这个指针是否为空
  {
    g_global_queue.reset(new CallbackQueue);//如果为空则创建一个回调队列的指针
  }

g_global_queue的定义为

CallbackQueuePtr g_global_queue;

CallbackQueuePtr的定义为一个回调队列的共享指针

typedef boost::shared_ptr<CallbackQueue> CallbackQueuePtr;

reset()是boost::shared_ptr的一个函数,其中reset() 相当于释放当前所控制的对象
reset(T* p) 相当于释放当前所控制的对象,然后接管p所指的对象。


ROSCONSOLE_AUTOINIT是定义在console.h文件中。这个的主要作用是初始化日志输出的库,常并不需要直接调用,具体的内容如下:

#define ROSCONSOLE_AUTOINIT \
  do \
  { \
    if (ROS_UNLIKELY(!::ros::console::g_initialized)) \
    { \
      ::ros::console::initialize(); \
    } \
  } while(false)

ROSCONSOLE_AUTOINIT

由于各个节点之间采用TCP通信,为了保证一个节点退出时,其他节点不受影响,所以采用signal函数。这个函数会在Linux系统中运行。这时产生SIGPIPE信号时就不会中止程序,直接把这个信号忽略掉。

#ifndef WIN32
    signal(SIGPIPE, SIG_IGN);
#endif
  • SIGPIPE信号产生的规则:当一个进程向某个已收到RST的套接字执行写操作时,内核向该进程发送SIGPIPE信号。
  • SIG_IGN是忽略信号。定义如下:
#define SIG_IGN (void (*)())1

chek_ipv6_environment的定义如下:

check_ipv6_environment();
void check_ipv6_environment() {
  char* env_ipv6 = NULL;
#ifdef _MSC_VER
  _dupenv_s(&env_ipv6, NULL, "ROS_IPV6");
#else
  env_ipv6 = getenv("ROS_IPV6");
#endif

  bool use_ipv6 = (env_ipv6 && strcmp(env_ipv6,"on") == 0);
  TransportTCP::s_use_ipv6_ = use_ipv6;
  XmlRpc::XmlRpcSocket::s_use_ipv6_ = use_ipv6;
}

_MSC_VER定义编译器的版本
MS VC++ 16.0 _MSC_VER = 1928 (Visual Studio 2019)
MS VC++ 15.0 _MSC_VER = 1910 (Visual Studio 2017)
MS VC++ 14.0 _MSC_VER = 1900 (Visual Studio 2015)
MS VC++ 12.0 _MSC_VER = 1800 (VisualStudio 2013)
MS VC++ 11.0 _MSC_VER = 1700 (VisualStudio 2012)
MS VC++ 10.0 _MSC_VER = 1600(VisualStudio 2010)
MS VC++ 9.0 _MSC_VER = 1500(VisualStudio 2008)
MS VC++ 8.0 _MSC_VER = 1400(VisualStudio 2005)
MS VC++ 7.1 _MSC_VER = 1310(VisualStudio 2003)
MS VC++ 7.0 _MSC_VER = 1300(VisualStudio .NET)
MS VC++ 6.0 _MSC_VER = 1200(VisualStudio 98)
MS VC++ 5.0 _MSC_VER = 1100(VisualStudio 97)


_dupenv_s和getenv的功能是一样的,返回一给定的环境变量值,环境变量名可大写或小写。如果指定的变量在环境中未定义,则返回一空串。只不过在不同编译环境中调用。getenv在vs编译环境会出现警告。


是否使用ipv6网络环境。

bool use_ipv6 = (env_ipv6 && strcmp(env_ipv6,"on") == 0);

设置通信网络环境

TransportTCP::s_use_ipv6_ = use_ipv6;
  XmlRpc::XmlRpcSocket::s_use_ipv6_ = use_ipv6;

小结:check_ipv6_environment()函数的作用是获取系统中ipv6网络的环境变量并且根据配置进行修改通信网络的配置。


五个初始化函数,通信网络初始、主节点初始化、当前节点初始化、日志文件保存初始化、参数器初始化。

network::init(remappings);
master::init(remappings);
this_node::init(name, remappings, options);
file_log::init(remappings);
param::init(remappings);

定义如下:

namespace master
{
void init(const M_string& remappings);
}

namespace this_node
{
void init(const std::string& names, const M_string& remappings, uint32_t options);
}

namespace network
{
void init(const M_string& remappings);
}

namespace param
{
void init(const M_string& remappings);
}

namespace file_log
{
void init(const M_string& remappings);
}

网络初始化:

void init(const M_string& remappings)
{
  M_string::const_iterator it = remappings.find("__hostname");//从map容器中找到网络的ip,可以将ip定义为__hostname
  if (it != remappings.end())//判断是否找到
  {
    g_host = it->second;//如果找到就取ip保存到参数变量中
  }
  else
  {
    it = remappings.find("__ip");//如果找__hostname,找不到,则直接找字段“__ip”
    if (it != remappings.end())//是否找到
    {
      g_host = it->second;//取值
    }
  }

  it = remappings.find("__tcpros_server_port");//服务器的端口
  if (it != remappings.end())
  {
    try
    {
      g_tcpros_server_port = boost::lexical_cast<uint16_t>(it->second);//检查端口是否有效
    }
    catch (boost::bad_lexical_cast&)
    {
      throw ros::InvalidPortException("__tcpros_server_port [" + it->second + "] was not specified as a number within the 0-65535 range");
    }
  }

  if (g_host.empty())//如果找不到相应的网络参数,使用确定的参数。
  {
    g_host = determineHost();
  }
}

determineHost()的定义如下:

std::string determineHost()
{
  std::string ip_env;
  // First, did the user set ROS_HOSTNAME?
  if ( get_environment_variable(ip_env, "ROS_HOSTNAME")) {
    ROSCPP_LOG_DEBUG( "determineIP: using value of ROS_HOSTNAME:%s:", ip_env.c_str());
    if (ip_env.size() == 0)
    {
      ROS_WARN("invalid ROS_HOSTNAME (an empty string)");
    }
    return ip_env;
  }

  // Second, did the user set ROS_IP?
  if ( get_environment_variable(ip_env, "ROS_IP")) {
    ROSCPP_LOG_DEBUG( "determineIP: using value of ROS_IP:%s:", ip_env.c_str());
    if (ip_env.size() == 0)
    {
      ROS_WARN("invalid ROS_IP (an empty string)");
    }
    return ip_env;
  }

  // Third, try the hostname
  char host[1024];
  memset(host,0,sizeof(host));
  if(gethostname(host,sizeof(host)-1) != 0)
  {
    ROS_ERROR("determineIP: gethostname failed");
  }
  // We don't want localhost to be our ip
  else if(strlen(host) && strcmp("localhost", host))
  {
    return std::string(host);
  }

  // Fourth, fall back on interface search, which will yield an IP address

#ifdef HAVE_IFADDRS_H
  struct ifaddrs *ifa = NULL, *ifp = NULL;
  int rc;
  if ((rc = getifaddrs(&ifp)) < 0)
  {
    ROS_FATAL("error in getifaddrs: [%s]", strerror(rc));
    ROS_BREAK();
  }
  char preferred_ip[200] = {0};
  for (ifa = ifp; ifa; ifa = ifa->ifa_next)
  {
    char ip_[200];
    socklen_t salen;
    if (!ifa->ifa_addr)
      continue; // evidently this interface has no ip address
    if (ifa->ifa_addr->sa_family == AF_INET)
      salen = sizeof(struct sockaddr_in);
    else if (ifa->ifa_addr->sa_family == AF_INET6)
      salen = sizeof(struct sockaddr_in6);
    else
      continue;
    if (getnameinfo(ifa->ifa_addr, salen, ip_, sizeof(ip_), NULL, 0,
                    NI_NUMERICHOST) < 0)
    {
      ROSCPP_LOG_DEBUG( "getnameinfo couldn't get the ip of interface [%s]", ifa->ifa_name);
      continue;
    }
    //ROS_INFO( "ip of interface [%s] is [%s]", ifa->ifa_name, ip);
    // prefer non-private IPs over private IPs
    if (!strcmp("127.0.0.1", ip_) || strchr(ip_,':'))
      continue; // ignore loopback unless we have no other choice
    if (ifa->ifa_addr->sa_family == AF_INET6 && !preferred_ip[0])
      strcpy(preferred_ip, ip_);
    else if (isPrivateIP(ip_) && !preferred_ip[0])
      strcpy(preferred_ip, ip_);
    else if (!isPrivateIP(ip_) &&
             (isPrivateIP(preferred_ip) || !preferred_ip[0]))
      strcpy(preferred_ip, ip_);
  }
  freeifaddrs(ifp);
  if (!preferred_ip[0])
  {
    ROS_ERROR( "Couldn't find a preferred IP via the getifaddrs() call; I'm assuming that your IP "
        "address is 127.0.0.1.  This should work for local processes, "
        "but will almost certainly not work if you have remote processes."
        "Report to the ROS development team to seek a fix.");
    return std::string("127.0.0.1");
  }
  ROSCPP_LOG_DEBUG( "preferred IP is guessed to be %s", preferred_ip);
  return std::string(preferred_ip);
#else
  // @todo Fix IP determination in the case where getifaddrs() isn't
  // available.
  ROS_ERROR( "You don't have the getifaddrs() call; I'm assuming that your IP "
             "address is 127.0.0.1.  This should work for local processes, "
             "but will almost certainly not work if you have remote processes."
             "Report to the ROS development team to seek a fix.");
  return std::string("127.0.0.1");
#endif
}

master的初始化代码如下:

void init(const M_string& remappings)
{
  M_string::const_iterator it = remappings.find("__master");//找master的ip配置参数
  if (it != remappings.end())
  {
    g_uri = it->second;
  }

  if (g_uri.empty())
  {
    char *master_uri_env = NULL;
    #ifdef _MSC_VER
      _dupenv_s(&master_uri_env, NULL, "ROS_MASTER_URI");
    #else
      master_uri_env = getenv("ROS_MASTER_URI");
    #endif
    if (!master_uri_env)
    {
      ROS_FATAL( "ROS_MASTER_URI is not defined in the environment. Either " \
                 "type the following or (preferrably) add this to your " \
                 "~/.bashrc file in order set up your " \
                 "local machine as a ROS master:\n\n" \
                 "export ROS_MASTER_URI=http://localhost:11311\n\n" \
                 "then, type 'roscore' in another shell to actually launch " \
                 "the master program.");
      ROS_BREAK();
    }

    g_uri = master_uri_env;

#ifdef _MSC_VER
    // http://msdn.microsoft.com/en-us/library/ms175774(v=vs.80).aspx
    free(master_uri_env);
#endif
  }

  // Split URI into
  if (!network::splitURI(g_uri, g_host, g_port))
  {
    ROS_FATAL( "Couldn't parse the master URI [%s] into a host:port pair.", g_uri.c_str());
    ROS_BREAK();
  }
}

network::splitURI的定义如下:

bool splitURI(const std::string& uri, std::string& host, uint32_t& port)
{
  // skip over the protocol if it's there
  if (uri.substr(0, 7) == std::string("http://"))
    host = uri.substr(7);
  else if (uri.substr(0, 9) == std::string("rosrpc://"))
    host = uri.substr(9);
  // split out the port
  std::string::size_type colon_pos = host.find_first_of(":");
  if (colon_pos == std::string::npos)
    return false;
  std::string port_str = host.substr(colon_pos+1);
  std::string::size_type slash_pos = port_str.find_first_of("/");
  if (slash_pos != std::string::npos)
    port_str = port_str.erase(slash_pos);
  port = atoi(port_str.c_str());
  host = host.erase(colon_pos);
  return true;
}

这个节点初始化代码:

void ThisNode::init(const std::string& name, const M_string& remappings, uint32_t options)
{
  char *ns_env = NULL;
#ifdef _MSC_VER
  _dupenv_s(&ns_env, NULL, "ROS_NAMESPACE");
#else
  ns_env = getenv("ROS_NAMESPACE");
#endif

  if (ns_env)
  {
    namespace_ = ns_env;
#ifdef _MSC_VER
    free(ns_env);
#endif
  }

  if (name.empty()) {
    throw InvalidNameException("The node name must not be empty");
  }

  name_ = name;

  bool disable_anon = false;
  M_string::const_iterator it = remappings.find("__name");
  if (it != remappings.end())
  {
    name_ = it->second;
    disable_anon = true;
  }

  it = remappings.find("__ns");
  if (it != remappings.end())
  {
    namespace_ = it->second;
  }

  if (namespace_.empty())
  {
    namespace_ = "/";
  }

  namespace_ = (namespace_ == "/")
    ? std::string("/") 
    : ("/" + namespace_)
    ;


  std::string error;
  if (!names::validate(namespace_, error))
  {
    std::stringstream ss;
    ss << "Namespace [" << namespace_ << "] is invalid: " << error;
    throw InvalidNameException(ss.str());
  }

  // names must be initialized here, because it requires the namespace to already be known so that it can properly resolve names.
  // It must be done before we resolve g_name, because otherwise the name will not get remapped.
  names::init(remappings);

  if (name_.find("/") != std::string::npos)
  {
    throw InvalidNodeNameException(name_, "node names cannot contain /");
  }
  if (name_.find("~") != std::string::npos)
  {
    throw InvalidNodeNameException(name_, "node names cannot contain ~");
  }

  name_ = names::resolve(namespace_, name_);

  if (options & init_options::AnonymousName && !disable_anon)
  {
    char buf[200];
    snprintf(buf, sizeof(buf), "_%llu", (unsigned long long)WallTime::now().toNSec());
    name_ += buf;
  }

  ros::console::setFixedFilterToken("node", name_);
}

日记输出的初始化代码:

void init(const M_string& remappings)
{
  std::string log_file_name;
  M_string::const_iterator it = remappings.find("__log");
  if (it != remappings.end())
  {
    log_file_name = it->second;
  }

  {
    // Log filename can be specified on the command line through __log
    // If it's been set, don't create our own name
    if (log_file_name.empty())
    {
      // Setup the logfile appender
      // Can't do this in rosconsole because the node name is not known
      pid_t pid = getpid();
      std::string ros_log_env;
      if ( get_environment_variable(ros_log_env, "ROS_LOG_DIR"))
      {
        log_file_name = ros_log_env + std::string("/");
      }
      else
      {
        if ( get_environment_variable(ros_log_env, "ROS_HOME"))
        {
          log_file_name = ros_log_env + std::string("/log/");
        }
        else
        {
          // Not cross-platform?
          if( get_environment_variable(ros_log_env, "HOME") )
          {
            std::string dotros = ros_log_env + std::string("/.ros/");
            fs::create_directory(dotros);
            log_file_name = dotros + "log/";
            fs::create_directory(log_file_name);
          }
        }
      }

      // sanitize the node name and tack it to the filename
      for (size_t i = 1; i < this_node::getName().length(); i++)
      {
        if (!isalnum(this_node::getName()[i]))
        {
          log_file_name += '_';
        }
        else
        {
          log_file_name += this_node::getName()[i];
        }
      }

      char pid_str[100];
      snprintf(pid_str, sizeof(pid_str), "%d", pid);
      log_file_name += std::string("_") + std::string(pid_str) + std::string(".log");
    }

    log_file_name = fs::system_complete(log_file_name).string();
    g_log_directory = fs::path(log_file_name).parent_path().string();
  }
}

参数器初始化代码:

void init(const M_string& remappings)
{
  M_string::const_iterator it = remappings.begin();
  M_string::const_iterator end = remappings.end();
  for (; it != end; ++it)
  {
    const std::string& name = it->first;
    const std::string& param = it->second;

    if (name.size() < 2)
    {
      continue;
    }

    if (name[0] == '_' && name[1] != '_')
    {
      std::string local_name = "~" + name.substr(1);

      bool success = false;

      try
      {
        int32_t i = boost::lexical_cast<int32_t>(param);
        ros::param::set(names::resolve(local_name), i);
        success = true;
      }
      catch (boost::bad_lexical_cast&)
      {

      }

      if (success)
      {
        continue;
      }

      try
      {
        double d = boost::lexical_cast<double>(param);
        ros::param::set(names::resolve(local_name), d);
        success = true;
      }
      catch (boost::bad_lexical_cast&)
      {

      }

      if (success)
      {
        continue;
      }

      if (param == "true" || param == "True" || param == "TRUE")
      {
        ros::param::set(names::resolve(local_name), true);
      }
      else if (param == "false" || param == "False" || param == "FALSE")
      {
        ros::param::set(names::resolve(local_name), false);
      }
      else
      {
        ros::param::set(names::resolve(local_name), param);
      }
    }
  }

  XMLRPCManager::instance()->bind("paramUpdate", paramUpdateCallback);
}

到此一个节点基础的初始化就完成,网络、主节点等模块将下篇博客详细分析。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

ros的init机制续篇 的相关文章

  • ubuntu18.04命令安装ros2

    ROS2官方文档 本教程为apt get命令安装方式 官网教程有点问题 借鉴一下大佬的安装方式 文章目录 1 安装ROS2 1 1 安装秘钥相关指令 1 2 授权秘钥 1 3 添加ROS2软件源 1 4 安装 2 设置环境 可选但是推荐 2
  • 如何将视频或图像序列转换为包文件?

    我是 ROS 新手 我需要转换预先存在的视频文件 或者large可以连接到视频流中的图像数量 bagROS 中的文件 我在网上找到了这段代码 http answers ros org question 11537 creating a ba
  • 在容器中运行服务(upstart/init.d)

    我正在尝试在 docker 中启动一个具有许多 init 和 upstart 服务的系统 但出现此错误 initctl Unable to connect to Upstart Failed to connect to socket com
  • 在 __init__ 中定义成员与在 python 中的类体中定义成员之间的区别?

    做和做有什么区别 class a def init self self val 1 to doing class a val 1 def init self pass class a def init self self val 1 这创建
  • 是否可以从不同的 JVM 调用 Java 应用程序中的方法?

    当我第一次使用 Apache 守护程序为 Windows 开发 Java 服务时 我使用了JVM我非常喜欢的模式 您指定您的类和启动 停止 静态 方法 但对于 Linux Jsvc 看起来并没有相同的选项 我真的很想知道为什么 无论如何 如
  • 从 pcl::PointCloud 中删除点

    我是 PCL 新手 我正在使用 PCL 库 并且正在寻找一种从点云中提取点或将特定点复制到新点的方法 我想验证每个点是否符合条件 并且我想获得仅包含优点的点云 谢谢 使用 ExtractIndices 类 将要删除的点添加到 PointIn
  • Python、__init__ 和自我困惑

    好吧 当我发现这个时 我正在查看一些来源 gt gt gt def parse self filename parse ID3v1 0 tags from MP3 file self clear try fsock open filenam
  • tinymce动态加载js时无法统一

    我在使用tinyMCE时遇到了麻烦 当我把
  • 错误状态:平台不允许不安全的 HTTP:http://0.0.0.0:9090

    我正在尝试从我的 flutter 应用程序连接到 ws local host 9090 使用 rosbridge 运行 的 Ros WebSocket 服务 但我在 Flutter 中收到以下错误 错误状态 平台不允许不安全的 HTTP h
  • __init__ 方法获取一个值,但没有传递任何值[重复]

    这个问题在这里已经有答案了 我有课TextBuffer 它有一个 init 函数可用于传递内容的初始值TextBuffer 如果没有传递任何值 则应使用空列表作为默认值 该类有一个方法add 为了将文本添加到 TextBuffer a Te
  • 在 __init__() python 中打开文件

    嘿我有以下问题 我需要打开一个文件 init 与check函数我需要检查该文件的行中的字符串 数字是否相同 如果不是 它应该返回True如果是的话它应该返回False 如果没有更多的行None 我不知道文件中有多少行 我的代码工作正常 测试
  • 为什么 UILabel 没有初始化?

    代码来自斯坦福大学CS193p 我添加了一个 NSLog 来查看 标签似乎没有被初始化 任何想法 interface AskerViewController
  • 如何访问 Heroku 中的 docker 容器?

    我已按照此处构建图像的说明进行操作 https devcenter heroku com articles container registry and runtime getting started https devcenter her
  • ObjC-为什么分别实现[alloc]和[init]方法时不正确? [复制]

    这个问题在这里已经有答案了 切勿在未重新分配指向该对象的任何指针的情况下初始化该对象 举个例子 不要这样做 NSObject someObject NSObject alloc someObject init 如果对 init 的调用返回某
  • ROS安装错误(Ubuntu 16.04中的ROS Kinetic)

    中列出的步骤顺序http wiki ros org kinetic Installat 已被关注 尝试在Ubuntu 16 04中安装ROSkinetic 输入以下命令时出错 sudo apt get install ros kinetic
  • 无法子类化 WKWebView

    我正在尝试子类化 WKWebView 当我实现自己的初始化程序时 出现以下错误 required initializer init coder must be provided by subclass of WKWebView 好的 众所周
  • 如何将 .sks 文件添加到现有 Swift/Sprite-Kit 项目?

    我开始遵循 Ray Wenderlich 的 太空入侵者 教程 但分歧很大 我现在有 3 个 SKScene 我的标题屏幕 我的主游戏屏幕和我的最终关卡 游戏结束屏幕 我添加了标题屏幕和游戏结束场景 它们都有 sks 文件 主游戏屏幕没有
  • 使用 CMake 链接 .s 文件

    我有一个我想使用的 c 函数 但它是用Intel编译器而不是gnu C编译器 我在用着cmake构建程序 我实际上正在使用ROS因此rosmake但基础是cmake所以我认为这更多是一个 cmake 问题而不是ROS问题 假设使用构建的文件
  • Python 检查 __init__ 参数

    在过去的几个小时里我一直在试图解决这个问题 但我即将放弃 如何确保在 python 中只有匹配的特定条件才会创建对象 例如 假设我想创建一个对象 Hand 并且仅当初始化程序中有足够的 Fingers 时才初始化 Hand 请以此作为类比
  • 在初始化脚本中切换用户?

    这是我的 Ubuntu 工作站上的初始化脚本 我需要以除 root 之外的其他用户身份运行命令 但我就是不知道应该如何完成它 两者都不sudo u or su newuser似乎有效 剧本 respawn console none star

随机推荐

  • CC, TBD, EOD都是什么鬼?拯救一写英文邮件就发慌

    职场新人在工作中经常听到这样的对话 xff1a 给客户的邮件记得CC我 xff0c BCC给财务 xff0c 告诉客户合同签订时间还TBD But CC BCC TBD到底是什么鬼 xff1f 马上来恶补一下职场英文缩写 xff0c 拯救一
  • Apache Openmeetings安装介绍

    翻译自Apache OpenMeetings 更新时间 xff1a 2017 01 11 目录 目录Openmeetings安装端口NAT设置自定义硬件需求Debian链接更新日志VoIP提示和技巧 Openmeetings安装 从过往版本
  • Could not transfer artifact xxx from/to xxx解决方案

    在做Openmeetings二次开发的时候install时出现了如下错误 INFO Parent project loaded span class hljs keyword from span repository org apache
  • MavenInvocationException解决方案

    在编译Openmeetings的时候出现了这样的错误信息 xff1a MavenInvocationException Error configuring command line Reason Maven executable not f
  • 生成生命周期介绍

    翻译自http maven apache org guides introduction introduction to the lifecycle html 目录 目录生成生命周期基础 生成生命周期由阶段组成通用命令行调用一个生成阶段是由
  • Crypto++库在VS 2013中的使用 + 基于操作模式AES加密

    一 下载Crypto 43 43 Library Crypto 43 43 Library的官方网 xff1a http www cryptopp com 二 建立自己使用的Crypto 43 43 Library 由于从官方网下载的Cry
  • MATLAB工具箱路径缓存

    关于MATLAB工具箱路径缓存 出于性能考虑 xff0c MATLAB将跨会话缓存工具箱文件夹信息 缓存特性对您来说是透明的 但是 xff0c 如果MATLAB没有找到您的最新版本的MATLAB代码文件 xff0c 或者如果您收到有关工具箱
  • MySQL语法

    初识MySQL 为什么学习数据库 1 岗位技能需求 2 现在的世界 得数据者得天下 3 存储数据的方法 4 程序 网站中 大量数据如何长久保存 5 数据库是几乎软件体系中最核心的一个存在 什么是数据库 数据库 DataBase 简称DB 概
  • HBase Configuration过程

    HBase客户端API中 xff0c 我们看到对HBase的任何操作都需要首先创建HBaseConfiguration类的实例 为HBaseConfiguration类继承自Configuration类 xff0c 而Configurati
  • 中国版的 Github:gitee.com、coding.net

    https gitee com 码云 社区版 主要功能代码托管 xff1a 对个人开发者提供免费的云端 Git 仓库 xff0c 可创建最多 1000 个项目 xff0c 不限公有或私有 xff0c 支持SSH HTTP SVN xff1b
  • winScp 连接 FilEZillA报(由于目标计算机积极拒绝,无法连接)

    场景 xff1a 服务器一台 xff1b 本地台式机一台 xff0c 为了文件传输方便 xff0c 在服务器上使用FilEZillA搭建了FTP xff0c 在本地使用WinScp进行连接 问题 xff1a 首先FTP搭建没问题 xff0c
  • 关于 Raspberry Pi3 使用 Intel® RealSense™ D400 cameras的简单介绍

    Raspberry Pi Raspberry pi 可以称为个人微型电脑 xff0c 虽然它的性能无法与普通电脑相比 xff0c 但是它在很多方面都给我们带来了惊喜 xff0c 它的特点是便于携带 xff0c 功能基本和普通电脑一样 xff
  • 安装好后 实例启动出现问题

    错误如上正在排错中 File 34 usr lib python2 7 site packages nova conductor manager py 34 line 671 in build instances request spec
  • gazebo仿真之plugin系列一

    官网教程 xff1a http gazebosim org tutorials tut 61 plugins hello world amp cat 61 write plugin 本次内容涉及五个方面 xff1a plugin的基本介绍与
  • gazebo官网教程之快速开始

    英文教程 xff1a http gazebosim org tutorials tut 61 quick start amp cat 61 get started 一 运行gazebo 打开有默认环境的gazebo只需要三步 xff1a 1
  • 基于unity无人机3D仿真《一》

    基于unity无人机3D仿真 一 实现无人机的模型的制作 运动学关系 姿态角等 xff1b 实现无人机各种姿态运动 一 目前的效果 二 无人机模型 制作软件 xff1a maya 模型结构 xff1a 三 开发平台 unity2017 43
  • 比特、字节转换

    1bite xff08 比特 xff09 61 1字节 数字 xff1a 1字符 61 1字节 英文 xff1a 1字符 61 1字节 汉字 xff1a 1字符 61 2字节 在ASCII码中 xff0c 一个英文字母 xff08 不分大小
  • Unity无人机仿真github项目

    本人本科生有幸得到导师的指导 xff0c 对Unity这个平台学习已有一段时间 该平台在搭建自主仿真平台方面确实有很大优势 下面是在学习过程中收集到的一些多旋翼无人机仿真的github项目 xff0c 可供需要的快速学习 xff08 推荐先
  • matlab2020a中使用TrueTime工具

    环境 xff1a matlab版本 xff1a 2020a 参考文章 网络控制系统仿真 xff1a Truetime2 0工具箱安装 xff08 win10 43 matlab R2017b xff09 目标 xff1a 在matlab20
  • ros的init机制续篇

    这篇博客主要探讨init的实现过程 ros span class token double colon punctuation span span class token function init span span class toke