当前位置: 首页 > news >正文

ROS2底层机制源码分析

  • init
    ->init_and_remove_ros_arguments
        ->init
            ->Context::init 保存初始化传入的信号
            ->install_signal_handlers→SignalHandler::install 开线程响应信号
        ->_remove_ros_arguments 移除ros参数
    ->SingleNodeManager::instance().init 
    ->mogo_recorder::MogoRecorder::instance().Init 中间件录包初始化
    ->创建全局静态的NodeHandle
     

  • NodeHandle

    • 构造nodehandle,并校验命名空间,内部调用

      void NodeHandle::construct()

      {

          SingleNodeManager::instance().increase_ref();

      }

    • param->从redis获取配置信息

  • create_generic_subscription 订阅接口

    -->topics_interface->add_subscription(subscription, options.callback_group);

       -->void CallbackGroup::add_subscription(const rclcpp::SubscriptionBase::SharedPtr subscription_ptr) {

              std::lock_guard<std::mutex> lock(mutex_);

              subscription_ptrs_.push_back(subscription_ptr); // timer service client waitable一样的逻辑

              subscription_ptrs_.erase(

              std::remove_if(

              subscription_ptrs_.begin(),

              subscription_ptrs_.end(),

              [](rclcpp::SubscriptionBase::WeakPtr x) {return x.expired();}),

              subscription_ptrs_.end());

          }

  • mogo::AsyncSpinner

    /**
    * AsyncSpinner 用来异步spin 某一个 callback group
    * 如果thread_num =1,将创建一个独立的线程,进行指定callback group的spin
    * 如果thread_num >1,将使用multithead spinner 执行callback group的spin
    *
    * 提示:若只是想要进行整个node的多线程的spin,请使用 mogo::multithread_spin
    */

    • 构造

    • start

      创建执行器,将构造中传入的callback_group对象传入执行器,传入node;单独开个线程spin;多线程spin就是开指定多个线程并行spin动作

      void AsyncSpinner::start()

      {

        std::lock_guard<std::mutex> lock(mutex_);

        if (is_started_) {

          return;

        }

        mogo::spin();

        if (thread_num_ == 1) {

          exec_ = rclcpp::executors::SingleThreadedExecutor::make_shared();

        else {

          exec_ = rclcpp::executors::MultiThreadedExecutor::make_shared(rclcpp::ExecutorOptions(), thread_num_);

        }

        exec_->add_callback_group(callback_group_,

                                  SingleNodeManager::instance().get_node()->get_node_base_interface()); // 将回调组跟节点对象存入map

        th_ = std::thread([this] {

                        long tid = syscall(SYS_gettid);

                        if (tid > 0) {

                          this->th_id_.store((int)tid);

                        }

                        exec_->spin();

                      });

        is_started_ = true;

      }

      void

      MultiThreadedExecutor::spin()

      {

        if (spinning.exchange(true)) {

          throw std::runtime_error("spin() called while already spinning");

        }

        RCPPUTILS_SCOPE_EXIT(this->spinning.store(false); );

        std::vector<std::thread> threads;

        size_t thread_id = 0;

        {

          std::lock_guard wait_lock{wait_mutex_};

          for (; thread_id < number_of_threads_ - 1; ++thread_id) {

            auto func = std::bind(&MultiThreadedExecutor::run, this, thread_id);

            threads.emplace_back(func);

          }

        }

        run(thread_id);

        for (auto & thread : threads) {

          thread.join();

        }

      }

      void

      SingleThreadedExecutor::spin()

      {

        if (spinning.exchange(true)) {

          throw std::runtime_error("spin() called while already spinning");

        }

        RCPPUTILS_SCOPE_EXIT(this->spinning.store(false); );

        while (rclcpp::ok(this->context_) && spinning.load()) {

          rclcpp::AnyExecutable any_executable;

          if (get_next_executable(any_executable)) { // 内部从map中取

            execute_any_executable(any_executable);

          }

        }

      }

  • mogo::spin

    void spin()

    {

      SingleNodeManager::instance().spin();

    }

    void SingleNodeManager::spin()

    {

      if (MOGO_UNLIKELY(!is_start_)) {

        throw std::runtime_error("SingleNodeManager is not running, please create NodeHandle before that!");

      }

      if (!is_join_exec_) {

        std::lock_guard<std::mutex> lock(exec_mutex_);

        if (!is_join_exec_) {

          exec_->add_node(node_ptr_);

          is_join_single_exec_ = true;

          is_join_exec_ = true;

        else {

          if (!is_join_single_exec_) {

            throw std::runtime_error("Node has been joined in another exec");

          }

        }

      }

      exec_->spin();

      {

        std::lock_guard<std::mutex> lock(exec_mutex_);

        exec_->remove_node(node_ptr_);

        is_join_single_exec_ = false;

        is_join_exec_ = false;

      }

    }

    // 以subscription为例,以下详细函数调用栈

    1. 获取可执行对象

    bool

    Executor::get_next_ready_executable_from_map(

      AnyExecutable & any_executable,

      const rclcpp::memory_strategy::MemoryStrategy::WeakCallbackGroupsToNodesMap &

      weak_groups_to_nodes)

    {

      TRACEPOINT(rclcpp_executor_get_next_ready);

      bool success = false;

      std::lock_guard<std::mutex> guard{mutex_};

      // Check the timers to see if there are any that are ready

      memory_strategy_->get_next_timer(any_executable, weak_groups_to_nodes);

      if (any_executable.timer) {

        success = true;

      }

      if (!success) {

        // Check the subscriptions to see if there are any that are ready

        memory_strategy_->get_next_subscription(any_executable, weak_groups_to_nodes);

        if (any_executable.subscription) {

          success = true;

        }

      }

    ...

    void

    get_next_subscription(

        rclcpp::AnyExecutable & any_exec,

        const WeakCallbackGroupsToNodesMap & weak_groups_to_nodes) override

      {

        auto it = subscription_handles_.begin();

        while (it != subscription_handles_.end()) {

          auto subscription = get_subscription_by_handle(*it, weak_groups_to_nodes);

          if (subscription) {

            // Find the group for this handle and see if it can be serviced

            auto group = get_group_by_subscription(subscription, weak_groups_to_nodes);

            if (!group) {

              // Group was not found, meaning the subscription is not valid...

              // Remove it from the ready list and continue looking

              it = subscription_handles_.erase(it);

              continue;

            }

            if (!group->can_be_taken_from().load()) {

              // Group is mutually exclusive and is being used, so skip it for now

              // Leave it to be checked next time, but continue searching

              ++it;

              continue;

            }

            // Otherwise it is safe to set and return the any_exec

            any_exec.subscription = subscription;

            any_exec.callback_group = group;

            any_exec.node_base = get_node_by_group(group, weak_groups_to_nodes);

            subscription_handles_.erase(it);

            return;

          }

          // Else, the subscription is no longer valid, remove it and continue

          it = subscription_handles_.erase(it);

        }

      }

    ...

    rclcpp::SubscriptionBase::SharedPtr

    MemoryStrategy::get_subscription_by_handle(

      const std::shared_ptr<const rcl_subscription_t> & subscriber_handle,

      const WeakCallbackGroupsToNodesMap & weak_groups_to_nodes)

    {

      for (const auto & pair : weak_groups_to_nodes) {

        auto group = pair.first.lock();

        if (!group) {

          continue;

        }

         

        // check传入的subscriber_handle跟之前创建的是否匹配

        auto match_subscription = group->find_subscription_ptrs_if(

          [&subscriber_handle](const rclcpp::SubscriptionBase::SharedPtr & subscription) -> bool {

            return subscription->get_subscription_handle() == subscriber_handle;

          });

        if (match_subscription) {

          return match_subscription;

        }

      }

      return nullptr;

    }

    ...

    template<typename Function>

    rclcpp::SubscriptionBase::SharedPtr

    find_subscription_ptrs_if(Function func) const

    {

      return _find_ptrs_if_impl<rclcpp::SubscriptionBase, Function>(func, subscription_ptrs_);

    }

    template<typename TypeT, typename Function>

    typename TypeT::SharedPtr _find_ptrs_if_impl(Function func, const std::vector<typename TypeT::WeakPtr> & vect_ptrs) const

    {

      std::lock_guard<std::mutex> lock(mutex_);

      for (auto & weak_ptr : vect_ptrs) {

        auto ref_ptr = weak_ptr.lock();

        if (ref_ptr && func(ref_ptr)) {

          return ref_ptr;

        }

      }

      return typename TypeT::SharedPtr();

    }

    至此就能匹配到对应的timer service client waitable subscription

    2. 构造执行器

     auto it = subscription_handles_.begin();

        while (it != subscription_handles_.end()) {

          auto subscription = get_subscription_by_handle(*it, weak_groups_to_nodes);

          if (subscription) {

            // Find the group for this handle and see if it can be serviced

            auto group = get_group_by_subscription(subscription, weak_groups_to_nodes);

            if (!group) {

              // Group was not found, meaning the subscription is not valid...

              // Remove it from the ready list and continue looking

              it = subscription_handles_.erase(it);

              continue;

            }

            if (!group->can_be_taken_from().load()) {

              // Group is mutually exclusive and is being used, so skip it for now

              // Leave it to be checked next time, but continue searching

              ++it;

              continue;

            }

            // Otherwise it is safe to set and return the any_exec

            any_exec.subscription = subscription;

            any_exec.callback_group = group;

            any_exec.node_base = get_node_by_group(group, weak_groups_to_nodes);

            subscription_handles_.erase(it);

            return;

          }

          // Else, the subscription is no longer valid, remove it and continue

          it = subscription_handles_.erase(it);

        }

    3. 执行

    void

    Executor::execute_any_executable(AnyExecutable & any_exec)

    {

      if (!spinning.load()) {

        return;

      }

      if (any_exec.timer) {

        TRACEPOINT(

          rclcpp_executor_execute,

          static_cast<const void *>(any_exec.timer->get_timer_handle().get()));

        execute_timer(any_exec.timer);

      }

      if (any_exec.subscription) {

        TRACEPOINT(

          rclcpp_executor_execute,

          static_cast<const void *>(any_exec.subscription->get_subscription_handle().get()));

        execute_subscription(any_exec.subscription);

      }

      if (any_exec.service) {

        execute_service(any_exec.service);

      }

      if (any_exec.client) {

        execute_client(any_exec.client);

      }

      if (any_exec.waitable) {

        any_exec.waitable->execute(any_exec.data);

      }

      // Reset the callback_group, regardless of type

      any_exec.callback_group->can_be_taken_from().store(true);

      // Wake the wait, because it may need to be recalculated or work that

      // was previously blocked is now available.

      try {

        interrupt_guard_condition_.trigger();

      catch (const rclcpp::exceptions::RCLError & ex) {

        throw std::runtime_error(

                std::string(

                  "Failed to trigger guard condition from execute_any_executable: ") + ex.what());

      }

    }

    callback如何传入?

    业务代码订阅

    subscription = node_handle_.get_node()->create_generic_subscription(

                    topic_meta.name,

                    topic_meta.type,

                    rosbag2_transport::Rosbag2QoS(queue_size),

                    [this, topic_meta](std::shared_ptr<mogo::SerializedMessage> message) { // TODO 超过Xs没有回调加事件上报

                        if (!mogo::ok())

                            return;

      

                        count_++;

                        static double now_timestamp = mogo::TimeHelper::to_sec(mogo::Time::now());

                        // calc hz every second

                        if (mogo::TimeHelper::to_sec(mogo::Time::now()) - now_timestamp >= mogo::TimeHelper::to_sec(mogo::Time::create(1))) {

                            MOGO_INFO_STREAM_THROTTLE(10"current callback frequency: " << count_);

                            count_ = 0;

                            now_timestamp = mogo::TimeHelper::to_sec(mogo::Time::now());

                        }

                        pushQueue(OutgoingMessage(message, topic_meta.name, topic_meta.type, mogo::Time::now()));

                    },

                    subscription_options

                );

    内层调用注册callback

    template<typename AllocatorT = std::allocator<void>>

      GenericSubscription(

        rclcpp::node_interfaces::NodeBaseInterface * node_base,

        const std::shared_ptr<rcpputils::SharedLibrary> ts_lib,

        const std::string & topic_name,

        const std::string & topic_type,

        const rclcpp::QoS & qos,

        // TODO(nnmm): Add variant for callback with message info. See issue #1604.

        std::function<void(std::shared_ptr<rclcpp::SerializedMessage>)> callback,

        const rclcpp::SubscriptionOptionsWithAllocator<AllocatorT> & options)

      : SubscriptionBase(

          node_base,

          *rclcpp::get_typesupport_handle(topic_type, "rosidl_typesupport_cpp", *ts_lib),

          topic_name,

          options.template to_rcl_subscription_options<rclcpp::SerializedMessage>(qos),

          true),

        callback_(callback),

        ts_lib_(ts_lib)

      {

        // This is unfortunately duplicated with the code in subscription.hpp.

        // TODO(nnmm): Deduplicate by moving this into SubscriptionBase.

        if (options.event_callbacks.deadline_callback) {

          this->add_event_handler(

            options.event_callbacks.deadline_callback,

            RCL_SUBSCRIPTION_REQUESTED_DEADLINE_MISSED);

        }

        if (options.event_callbacks.liveliness_callback) {

          this->add_event_handler(

            options.event_callbacks.liveliness_callback,

            RCL_SUBSCRIPTION_LIVELINESS_CHANGED);

        }

        if (options.event_callbacks.incompatible_qos_callback) {

          this->add_event_handler(

            options.event_callbacks.incompatible_qos_callback,

            RCL_SUBSCRIPTION_REQUESTED_INCOMPATIBLE_QOS);

        else if (options.use_default_callbacks) {

          // Register default callback when not specified

          try {

            this->add_event_handler(

              [this](QOSRequestedIncompatibleQoSInfo & info) {

                this->default_incompatible_qos_callback(info);

              },

              RCL_SUBSCRIPTION_REQUESTED_INCOMPATIBLE_QOS);

          catch (UnsupportedEventTypeException & /*exc*/) {

            // pass

          }

        }

        if (options.event_callbacks.message_lost_callback) {

          this->add_event_handler(

            options.event_callbacks.message_lost_callback,

            RCL_SUBSCRIPTION_MESSAGE_LOST);

        }

      }

    处理消息

    void

    GenericSubscription::handle_serialized_message(

      const std::shared_ptr<rclcpp::SerializedMessage> & message,

      const rclcpp::MessageInfo &)

    {

      callback_(message);

    }

    消息从哪里来?---

    bool

    SubscriptionBase::take_serialized(

      rclcpp::SerializedMessage & message_out,

      rclcpp::MessageInfo & message_info_out)

    {

      rcl_ret_t ret = rcl_take_serialized_message(

        this->get_subscription_handle().get(),

        &message_out.get_rcl_serialized_message(),

        &message_info_out.get_rmw_message_info(),

        nullptr);

      if (RCL_RET_SUBSCRIPTION_TAKE_FAILED == ret) {

        return false;

      else if (RCL_RET_OK != ret) {

        rclcpp::exceptions::throw_from_rcl_error(ret);

      }

      return true;

    }

    rcl_ret_t

    rcl_take_serialized_message(

      const rcl_subscription_t * subscription,

      rcl_serialized_message_t * serialized_message,

      rmw_message_info_t * message_info,

      rmw_subscription_allocation_t * allocation

    )

    {

      RCUTILS_LOG_DEBUG_NAMED(ROS_PACKAGE_NAME, "Subscription taking serialized message");

      if (!rcl_subscription_is_valid(subscription)) {

        return RCL_RET_SUBSCRIPTION_INVALID;  // error already set

      }

      RCL_CHECK_ARGUMENT_FOR_NULL(serialized_message, RCL_RET_INVALID_ARGUMENT);

      // If message_info is NULL, use a place holder which can be discarded.

      rmw_message_info_t dummy_message_info;

      rmw_message_info_t * message_info_local = message_info ? message_info : &dummy_message_info;

      *message_info_local = rmw_get_zero_initialized_message_info();

      // Call rmw_take_with_info.

      bool taken = false;

      rmw_ret_t ret = rmw_take_serialized_message_with_info(

        subscription->impl->rmw_handle, serialized_message, &taken, message_info_local, allocation);

      if (ret != RMW_RET_OK) {

        RCL_SET_ERROR_MSG(rmw_get_error_string().str);

        return rcl_convert_rmw_ret_to_rcl_ret(ret);

      }

      RCUTILS_LOG_DEBUG_NAMED(

        ROS_PACKAGE_NAME, "Subscription serialized take succeeded: %s", taken ? "true" "false");

      if (!taken) {

        return RCL_RET_SUBSCRIPTION_TAKE_FAILED;

      }

      return RCL_RET_OK;

    }

    注意:这里已经到rmw层了(DDS的封装层)

    rmw_ret_t

    rmw_take_serialized_message_with_info(

      const rmw_subscription_t * subscription,

      rmw_serialized_message_t * serialized_message,

      bool * taken,

      rmw_message_info_t * message_info,

      rmw_subscription_allocation_t * allocation)

    {

      return rmw_fastrtps_shared_cpp::__rmw_take_serialized_message_with_info(

        eprosima_fastrtps_identifier, subscription, serialized_message, taken, message_info,

        allocation);

    }

    核心代码---循环通过data_reader_->take(data_values, info_seq, 1)获取数据,最终内存拷贝到serialized_message中带出

    rmw_ret_t

    _take_serialized_message(

      const char * identifier,

      const rmw_subscription_t * subscription,

      rmw_serialized_message_t * serialized_message,

      bool * taken,

      rmw_message_info_t * message_info,

      rmw_subscription_allocation_t * allocation)

    {

      (void) allocation;

      *taken = false;

      RMW_CHECK_TYPE_IDENTIFIERS_MATCH(

        subscription handle,

        subscription->implementation_identifier, identifier,

        return RMW_RET_INCORRECT_RMW_IMPLEMENTATION)

      auto info = static_cast<CustomSubscriberInfo *>(subscription->data);

      RCUTILS_CHECK_FOR_NULL_WITH_MSG(info, "custom subscriber info is null"return RMW_RET_ERROR);

      eprosima::fastcdr::FastBuffer buffer;

      eprosima::fastdds::dds::SampleInfo sinfo;

      rmw_fastrtps_shared_cpp::SerializedData data;

      data.is_cdr_buffer = true;

      data.data = &buffer;

      data.impl = nullptr;    // not used when is_cdr_buffer is true

      eprosima::fastdds::dds::StackAllocatedSequence<void *, 1> data_values;

      const_cast<void **>(data_values.buffer())[0] = &data;

      eprosima::fastdds::dds::SampleInfoSeq info_seq{1};

      while (ReturnCode_t::RETCODE_OK == info->data_reader_->take(data_values, info_seq, 1)) {

        auto reset = rcpputils::make_scope_exit(

          [&]()

          {

            data_values.length(0);

            info_seq.length(0);

          });

        if (info_seq[0].valid_data) {

          auto buffer_size = static_cast<size_t>(buffer.getBufferSize());

          if (serialized_message->buffer_capacity < buffer_size) {

            auto ret = rmw_serialized_message_resize(serialized_message, buffer_size);

            if (ret != RMW_RET_OK) {

              return ret;           // Error message already set

            }

          }

          serialized_message->buffer_length = buffer_size;

          memcpy(serialized_message->buffer, buffer.getBuffer(), serialized_message->buffer_length);

          if (message_info) {

            _assign_message_info(identifier, message_info, &info_seq[0]);

          }

          *taken = true;

          break;

        }

      }

      return RMW_RET_OK;

    }

    fastrtps-fastdds---查数据

    ReturnCode_t DataReaderImpl::read_or_take(

            LoanableCollection& data_values,

            SampleInfoSeq& sample_infos,

            int32_t max_samples,

            const InstanceHandle_t& handle,

            SampleStateMask sample_states,

            ViewStateMask view_states,

            InstanceStateMask instance_states,

            bool exact_instance,

            bool single_instance,

            bool should_take)

    {

        if (reader_ == nullptr)

        {

            return ReturnCode_t::RETCODE_NOT_ENABLED;

        }

        ReturnCode_t code = check_collection_preconditions_and_calc_max_samples(data_values, sample_infos, max_samples);

        if (!code)

        {

            return code;

        }

    #if HAVE_STRICT_REALTIME

        auto max_blocking_time = std::chrono::steady_clock::now() +

                std::chrono::microseconds(::TimeConv::Time_t2MicroSecondsInt64(qos_.reliability().max_blocking_time));

        std::unique_lock<RecursiveTimedMutex> lock(reader_->getMutex(), std::defer_lock);

        if (!lock.try_lock_until(max_blocking_time))

        {

            return ReturnCode_t::RETCODE_TIMEOUT;

        }

    #else

        std::lock_guard<RecursiveTimedMutex> _(reader_->getMutex());

    #endif // if HAVE_STRICT_REALTIME

        set_read_communication_status(false);

        auto it = history_.lookup_available_instance(handle, exact_instance);

        if (!it.first)

        {

            if (exact_instance && !history_.is_instance_present(handle))

            {

                return ReturnCode_t::RETCODE_BAD_PARAMETER;

            }

            else

            {

                return ReturnCode_t::RETCODE_NO_DATA;

            }

        }

        code = prepare_loan(data_values, sample_infos, max_samples);

        if (!code)

        {

            return code;

        }

        detail::StateFilter states{ sample_states, view_states, instance_states };

        detail::ReadTakeCommand cmd(*this, data_values, sample_infos, max_samples, states, it.second, single_instance);

        while (!cmd.is_finished())

        {

            cmd.add_instance(should_take);

        }

        return cmd.return_value();

    }

  • create_publisher

  • publish

  • mogo::shutdown

    bool shutdown()

    {

      return rclcpp::shutdown();

    }

相关文章:

ROS2底层机制源码分析

init ->init_and_remove_ros_arguments ->init ->Context::init 保存初始化传入的信号 ->install_signal_handlers→SignalHandler::install 开线程响应信号 ->_remove_ros_arguments 移除ros参数 ->SingleNodeManager::instance().…...

超越 Transformer开启高效开放语言模型的新篇章

在人工智能快速发展的今天&#xff0c;对于高效且性能卓越的语言模型的追求&#xff0c;促使谷歌DeepMind团队开发出了RecurrentGemma这一突破性模型。这款新型模型在论文《RecurrentGemma&#xff1a;超越Transformers的高效开放语言模型》中得到了详细介绍&#xff0c;它通过…...

快速排序-Hoare 递归版 C语言

个人主页点这里~ 快速排序的简介: 快速排序是Hoare于1962年提出的一种 二叉树结构 的 交换 排序方法&#xff0c;其基本思想为&#xff1a;任取待排序元素序列中 的某元素作为 基准值 &#xff0c;按照该排序码将待排序集合分割成 两子序列 &#xff0c; 左子序列中所有元素均 …...

C语言经典指针运算笔试题图文解析

指针运算常常出现在面试题中&#xff0c;画图解决是最好的办法。 题目1&#xff1a; #include <stdio.h> int main() {int a[5] { 1, 2, 3, 4, 5 };int* ptr (int*)(&a 1);printf("%d,%d", *(a 1), *(ptr - 1));return 0; } //程序的结果是什么&…...

使用 KubeKey v3.1.1 离线部署原生 Kubernetes v1.28.8 实战

今天&#xff0c;我将为大家实战演示&#xff0c;如何基于操作系统 openEuler 22.03 LTS SP3&#xff0c;利用 KubeKey 制作 Kubernetes 离线安装包&#xff0c;并实战离线部署 Kubernetes v1.28.8 集群。 实战服务器配置 (架构 1:1 复刻小规模生产环境&#xff0c;配置略有不…...

DOS 命令

Dos&#xff1a; Disk Operating System 磁盘操作系统, 简单说一下 windows 的目录结构。 ..\ 到上一级目录 常用的dos 命令&#xff1a; 查看当前目录是有什么内容 dir dir d:\abc2\test200切换到其他盘下&#xff1a;盘符号 cd : change directory 案例演示&#xff1a;切换…...

如何用Java程序实现一个简单的消息队列?

在Java程序中&#xff0c;可以使用内置的java.util.concurrent.BlockingQueue作为消息队列存放的容器&#xff0c;来实现一个简单的消息队列。 具体实现如下&#xff0c;在这个例子中&#xff0c;我们创建了一个生产者线程和一个消费者线程&#xff0c;他们共享同一个阻塞队列…...

OpenAI 宕机事件:GPT 停摆的影响与应对

引言 2024年6月4日&#xff0c;OpenAI 的 GPT 模型发生了一次全球性的宕机&#xff0c;持续时间长达8小时。此次宕机不仅影响了OpenAI自家的服务&#xff0c;还导致大量用户涌向竞争对手平台&#xff0c;如Claude和Gemini&#xff0c;结果也导致这些平台出现故障。这次事件的广…...

linux常用的基础命令

ls - 列出目录内容。 cd - 更改目录。 pwd - 打印当前工作目录。 mkdir - 创建新目录。 rmdir - 删除空目录。 touch - 创建新文件或更新现有文件的时间戳。 cp - 复制文件或目录。 mv - 移动或重命名文件或目录。 rm - 删除文件或目录。 cat - 显示文件内容。 more - 分页显示…...

618家用智能投影仪推荐:这个高性价比品牌不容错过

随着科技的不断进步&#xff0c;家庭影院的概念已经从传统的大屏幕电视逐渐转向了更为灵活和便携的家用智能投影仪。随着618电商大促的到来&#xff0c;想要购买投影仪的用户们也开始摩拳擦掌了。本文将从投影仪的基础知识入手&#xff0c;为您推荐几款性价比很高的投影仪&…...

自愿离婚协议书

自愿离婚协议书 男方&#xff08;夫&#xff09;&#xff1a; 女方&#xff08;妻&#xff09;&#xff1a; 双方现因 原因&#xff0c;导致夫妻情感已破裂&#xff0c;自愿离婚…...

WPS JSA 宏脚本入门和样例

1入门 WPS window版本才支持JSA宏的功能。 可以自动化的操作文档中的一些内容。 参考文档&#xff1a; WPS API 参考文档&#xff1a;https://open.wps.cn/previous/docs/client/wpsLoad 微软的Word API文档&#xff1a;Microsoft.Office.Interop.Word 命名空间 | Microsoft …...

Printing and Exporting

打印 大多数DevExpress。NET控件&#xff08;XtraGrid、XtraPivotGrid、XttraTreeList、XtraScheduler、XtraCharts&#xff09;提供打印和导出功能。 所有可打印的DevExpress.NET控件是使用XtraPrinting库提供的方法打印的。 若要确定预览和打印选项是否可用&#xff0c;请检…...

c++【入门】正多边形每个内角的度数

限制 时间限制 : 1 秒 内存限制 : 128 MB 题目 根据多边形内角和定理&#xff0c;正多边形内角和等于&#xff1a;&#xff08;n &#xff0d; 2&#xff09;180(n大于等于3且n为整数&#xff09;&#xff08;如下图所示是三角形、四边形、五边形、六边形的形状&#xff09…...

spring boot3登录开发-邮箱登录/注册接口实现

⛰️个人主页: 蒾酒 &#x1f525;系列专栏&#xff1a;《spring boot实战》 &#x1f30a;山高路远&#xff0c;行路漫漫&#xff0c;终有归途 目录 写在前面 上文衔接 内容简介 功能分析 所需依赖 邮箱验证登录/注册实现 1.创建交互对象 2.登录注册业务逻辑实…...

数据结构-二叉搜索树

二叉搜索树&#xff1a;BST(Binary Search Tree) 二叉搜索树是二叉树&#xff0c;可以为空&#xff0c;如果不为空&#xff0c;满足以下性质&#xff1a; 非空左子树的所有键值小于其根节点的键值非空右子树的所有键值大于其根节点的键值左、右字数本身也都是二叉搜索树 二叉…...

JUnit:Java开发者不可或缺的单元测试框架

在软件开发过程中&#xff0c;测试是确保代码质量的关键环节。单元测试作为测试体系的基础&#xff0c;对提升代码质量、降低bug率、增强软件稳定性具有重要作用。JUnit 作为 Java 语言事实上的标准单元测试框架&#xff0c;已经成为 Java 开发者进行单元测试的首选工具。本文将…...

NG32单片机GPIO口配置方式

目录 一、引言 二、GPIO口基本结构 三、GPIO口配置方式 四、工作原理 五、总结 一、引言 NG32单片机是一款集成度高、功能强大的微控制器。其中&#xff0c;GPIO&#xff08;General Purpose Input/Output&#xff09;口作为单片机与外部设备通信的重要接口&#xff0c;具…...

SpringCloud-OpenFeign拓展-连接池、最佳使用方法、日志输出

目录 1 OpenFeign连接池 1.1 常见连接类型 1.2 连接池使用方法 1.2.1 引入依赖 1.2.2 开启连接池功能 1.2.3 配置完成&#xff0c;重启实例即可&#xff0c;底层将更改设置。 2 OpenFeign最佳使用方法 2.1 每个微服务都是单独的project&#xff0c;内部有三个独立模块 …...

跨链协议中Cosmos IBC、Polkadot/XCM、Celer Network的区别以及用途

跨链协议是实现不同区块链之间通信和价值转移的关键技术。Cosmos IBC、Polkadot/XCM 和 Celer Network 是三个在跨链领域内具有代表性的协议&#xff0c;它们各自有着独特的设计理念和应用场景。下面是这三个协议的详细对比&#xff1a; Cosmos IBC (Inter-Blockchain Communi…...

业务系统对接大模型的基础方案:架构设计与关键步骤

业务系统对接大模型&#xff1a;架构设计与关键步骤 在当今数字化转型的浪潮中&#xff0c;大语言模型&#xff08;LLM&#xff09;已成为企业提升业务效率和创新能力的关键技术之一。将大模型集成到业务系统中&#xff0c;不仅可以优化用户体验&#xff0c;还能为业务决策提供…...

【Python】 -- 趣味代码 - 小恐龙游戏

文章目录 文章目录 00 小恐龙游戏程序设计框架代码结构和功能游戏流程总结01 小恐龙游戏程序设计02 百度网盘地址00 小恐龙游戏程序设计框架 这段代码是一个基于 Pygame 的简易跑酷游戏的完整实现,玩家控制一个角色(龙)躲避障碍物(仙人掌和乌鸦)。以下是代码的详细介绍:…...

利用ngx_stream_return_module构建简易 TCP/UDP 响应网关

一、模块概述 ngx_stream_return_module 提供了一个极简的指令&#xff1a; return <value>;在收到客户端连接后&#xff0c;立即将 <value> 写回并关闭连接。<value> 支持内嵌文本和内置变量&#xff08;如 $time_iso8601、$remote_addr 等&#xff09;&a…...

React hook之useRef

React useRef 详解 useRef 是 React 提供的一个 Hook&#xff0c;用于在函数组件中创建可变的引用对象。它在 React 开发中有多种重要用途&#xff0c;下面我将全面详细地介绍它的特性和用法。 基本概念 1. 创建 ref const refContainer useRef(initialValue);initialValu…...

linux arm系统烧录

1、打开瑞芯微程序 2、按住linux arm 的 recover按键 插入电源 3、当瑞芯微检测到有设备 4、松开recover按键 5、选择升级固件 6、点击固件选择本地刷机的linux arm 镜像 7、点击升级 &#xff08;忘了有没有这步了 估计有&#xff09; 刷机程序 和 镜像 就不提供了。要刷的时…...

React19源码系列之 事件插件系统

事件类别 事件类型 定义 文档 Event Event 接口表示在 EventTarget 上出现的事件。 Event - Web API | MDN UIEvent UIEvent 接口表示简单的用户界面事件。 UIEvent - Web API | MDN KeyboardEvent KeyboardEvent 对象描述了用户与键盘的交互。 KeyboardEvent - Web…...

从零实现STL哈希容器:unordered_map/unordered_set封装详解

本篇文章是对C学习的STL哈希容器自主实现部分的学习分享 希望也能为你带来些帮助~ 那咱们废话不多说&#xff0c;直接开始吧&#xff01; 一、源码结构分析 1. SGISTL30实现剖析 // hash_set核心结构 template <class Value, class HashFcn, ...> class hash_set {ty…...

(转)什么是DockerCompose?它有什么作用?

一、什么是DockerCompose? DockerCompose可以基于Compose文件帮我们快速的部署分布式应用&#xff0c;而无需手动一个个创建和运行容器。 Compose文件是一个文本文件&#xff0c;通过指令定义集群中的每个容器如何运行。 DockerCompose就是把DockerFile转换成指令去运行。 …...

Fabric V2.5 通用溯源系统——增加图片上传与下载功能

fabric-trace项目在发布一年后,部署量已突破1000次,为支持更多场景,现新增支持图片信息上链,本文对图片上传、下载功能代码进行梳理,包含智能合约、后端、前端部分。 一、智能合约修改 为了增加图片信息上链溯源,需要对底层数据结构进行修改,在此对智能合约中的农产品数…...

MySQL JOIN 表过多的优化思路

当 MySQL 查询涉及大量表 JOIN 时&#xff0c;性能会显著下降。以下是优化思路和简易实现方法&#xff1a; 一、核心优化思路 减少 JOIN 数量 数据冗余&#xff1a;添加必要的冗余字段&#xff08;如订单表直接存储用户名&#xff09;合并表&#xff1a;将频繁关联的小表合并成…...