ROS2底层机制源码分析

news2024/11/27 17:38:36
  • 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();

    }

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1816248.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

D-Bus——system 调用session 报错

以下代码是一个 session 服务和 systemd 服务 demo &#xff1a; systemd DBus #include <QCoreApplication> #include <QDBusConnection> #include <QDBusInterface> #include <QDBusError> #include <QDebug>class TestObject : public QObje…

一文搞懂flex布局

上学读书的时候&#xff0c;学习flex布局&#xff0c;更多停留在理论知识层面。近来&#xff0c;经过工作实践&#xff0c;发现自己对flex布局的理解更加深入&#xff0c;运用起来更加熟练&#xff0c;也越发能感受到flex布局的强大灵活性&#xff0c;特来总结一波。若有错误之…

Android Glide loading Bitmap from RESOURCE_DISK_CACHE slow,cost time≈2 seconds+

Android Glide loading Bitmap from RESOURCE_DISK_CACHE slow,cost time≈2 seconds 加载一张宽高约100px多些的小图&#xff0c;是一张相当小的正常图片&#xff0c;loading Bitmap from RESOURCE_DISK_CACHE竟然耗时达到惊人的3秒左右&#xff01;&#xff08;打开Glide调试…

元宇宙数字化3D虚拟展馆

随着科技的飞速发展&#xff0c;我们迎来了一个全新的时代——元宇宙时代。在这个充满无限可能的虚拟世界中&#xff0c;元宇宙数字展馆搭建编辑器应运而生&#xff0c;以其卓越的技术和创新的理念&#xff0c;为用户带来了前所未有的沉浸式展览体验。 元宇宙数字展馆搭建编辑器…

普涨和补涨—2024年上半年全球投资趋势

全球大宗商品价格处在上涨周期&#xff0c;东吴证券认为上游能源行业股价还有继续上涨的空间。随着6月全球股指进一步上涨&#xff0c;预计港股可能还会迎来补涨行情。 一、我们观察的全球投资趋势&#xff1a; 1、全球股指普涨&补涨。全球风险资产进入2024年后普遍上涨&am…

Spark安装、解压、配置环境变量、WordCount

Spark 小白的spark学习笔记 2024/5/30 10:14 文章目录 Spark安装解压改名配置spark-env.sh重命名&#xff0c;配置slaves启动查看配置环境变量 工作流程maven创建maven项目配置maven更改pom.xml WordCount按照用户求消费额上传到spark集群上运行 安装 上传&#xff0c;直接拖拽…

一文讲清:生产报工系统的功能、报价以及如何选择

最近这几年&#xff0c;企业越来越注重生产的速度和成本&#xff0c;尤其是“性价比”&#xff0c;生产报工系统已经变成了制造业里不可或缺的一部分。不过&#xff0c;市场上生产报工系统的选择太多&#xff0c;价格也都不一样&#xff0c;这就给很多企业出了个难题&#xff1…

【Python数据魔术】:揭秘类型奥秘,赋能代码创造

文章目录 &#x1f680;一.运算符&#x1f308;1. 算术运算符&#x1f308;2. 身份运算符&#x1f308;3. 成员运算符⭐4. 增量运算符⭐5. 比较运算符⭐6. 逻辑运算符 &#x1f680;二.可变与不可变&#x1f680;三.字符串转义&#x1f680;四.编码与解码&#x1f4a5;1. 基础使…

SpringBoot 异常配置

系统异常处理 创建异常处理器类&#xff0c;类上添加ControllerAdvice注解。 package com.soft.exception;import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframewor…

Java的集合框架总结

Map接口和Collection接口是所有集合框架的父接口&#xff1a; Collection接口的子接口包括&#xff1a;Set接口和List接口 Map接口的实现类主要有&#xff1a;HashMap、TreeMap、Hashtable、ConcurrentHashMap以及Properties等 Set接口的实现类主要有&#xff1a;HashSet、Tr…

【数据结构】二叉树:一场关于节点与遍历的艺术之旅

专栏引入 哈喽大家好&#xff0c;我是野生的编程萌新&#xff0c;首先感谢大家的观看。数据结构的学习者大多有这样的想法&#xff1a;数据结构很重要&#xff0c;一定要学好&#xff0c;但数据结构比较抽象&#xff0c;有些算法理解起来很困难&#xff0c;学的很累。我想让大家…

数据结构之链表的经典笔试题

找往期文章包括但不限于本期文章中不懂的知识点&#xff1a; 个人主页&#xff1a;我要学编程(ಥ_ಥ)-CSDN博客 所属专栏&#xff1a;数据结构&#xff08;Java版&#xff09; 目录 203. 移除链表元素 206. 反转链表 876. 链表的中间节点 面试题 02.02. 返回倒数第k个节点 …

零基础非科班也能掌握的C语言知识22 预处理详解(完结)

预处理详解 1.预处理符号2.#define 定义常量3.#define 定义宏4.带有副作用的宏参数5.宏替换的规则6.宏函数的对比6.1 例子6.1 .16.1.26.1.3 7.命名约定8.undefin9.命令行定义(博主没办法演示)10.条件编译11.头文件的包含11.1本地文件11.2库文件的包含11.3 嵌套文件的包含 12.其…

软件安全测评有哪些测试流程?第三方检测机构进行安全测评的好处

在今天的高科技时代&#xff0c;软件产品已经成为人们生活和工作的重要组成部分。然而&#xff0c;与其普及和深入应用的&#xff0c;软件安全问题也日益凸显。 为了保障软件产品在使用过程中的安全性&#xff0c;进行安全测评是必不可少的。安全测评可以全面评估软件系统的安…

GPT-4o多模态大模型的架构设计

GPT-4o&#xff1a;大模型风向&#xff0c;OpenAI大更新 OpenAI震撼发布两大更新&#xff01;桌面版APP与全新UI的ChatGPT上线&#xff0c;简化用户操作&#xff0c;体验更自然。同时&#xff0c;全能模型GPT-4o惊艳亮相&#xff0c;跨模态即时响应&#xff0c;性能卓越且性价比…

Java集合自测题

文章目录 一、说说 List , Set , Map 三者的区别&#xff1f;二、List , Set , Map 在 Java 中分别由哪些对应的实现类&#xff1f;底层的数据结构&#xff1f;三、有哪些集合是线程不安全的&#xff1f;怎么解决呢&#xff1f;四、HashMap 查询&#xff0c;删除的时间复杂度五…

k8s中的pod域名解析失败定位案例

问题描述 我在k8s中启动了一个Host网络模式的pod&#xff0c;这个pod的域名解析失败了。 定位步骤 敲kubectl exec -it [pod_name] -- bash进入pod后台&#xff0c;查看/etc/resolv.conf&#xff0c;发现nameserver配的有问题。这里我预期的nameserver应该使用宿主机的&…

【Linux】线程(一)

谈论之前需要先谈论一些线程的背景知识 其中就有进程地址空间&#xff0c;又是这个让我们又爱又恨的东西 目录 背景知识&#xff1a;地址空间&#xff1a; 背景知识&#xff1a; 地址空间&#xff1a; 说在前边&#xff0c;OS通常分为4个核心模块&#xff1a;执行流管理&…

【qt】绘图

绘图 一.画家二.绘图事件三.坐标体系四.画笔1.setColor2.setWidth3.setStyle4.setCapStyle5.setJoinStyle6.给画家配置笔 五.画刷1.setColor2.setStyle3.给画家设置刷子 六.用到的类汇总1.QRect 矩形2.QPoint 点3.QImage 图片4.QPixmap 图片5.QLine 线6.QPainterPath 路径 七.开…

如何关闭端口被占用的进程

如何关闭端口被占用的进程 操作步骤一、打开命令提示符&#xff08;Command Prompt&#xff09;二、查看占用端口的进程三、kill杀死占用端口的进程 操作步骤 一、打开命令提示符&#xff08;Command Prompt&#xff09; 使用 win R 打开命令行模式 然后在命令行窗口输入下…