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开启高效开放语言模型的新篇章
在人工智能快速发展的今天,对于高效且性能卓越的语言模型的追求,促使谷歌DeepMind团队开发出了RecurrentGemma这一突破性模型。这款新型模型在论文《RecurrentGemma:超越Transformers的高效开放语言模型》中得到了详细介绍,它通过…...

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

C语言经典指针运算笔试题图文解析
指针运算常常出现在面试题中,画图解决是最好的办法。 题目1: #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 实战
今天,我将为大家实战演示,如何基于操作系统 openEuler 22.03 LTS SP3,利用 KubeKey 制作 Kubernetes 离线安装包,并实战离线部署 Kubernetes v1.28.8 集群。 实战服务器配置 (架构 1:1 复刻小规模生产环境,配置略有不…...

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

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

OpenAI 宕机事件:GPT 停摆的影响与应对
引言 2024年6月4日,OpenAI 的 GPT 模型发生了一次全球性的宕机,持续时间长达8小时。此次宕机不仅影响了OpenAI自家的服务,还导致大量用户涌向竞争对手平台,如Claude和Gemini,结果也导致这些平台出现故障。这次事件的广…...
linux常用的基础命令
ls - 列出目录内容。 cd - 更改目录。 pwd - 打印当前工作目录。 mkdir - 创建新目录。 rmdir - 删除空目录。 touch - 创建新文件或更新现有文件的时间戳。 cp - 复制文件或目录。 mv - 移动或重命名文件或目录。 rm - 删除文件或目录。 cat - 显示文件内容。 more - 分页显示…...

618家用智能投影仪推荐:这个高性价比品牌不容错过
随着科技的不断进步,家庭影院的概念已经从传统的大屏幕电视逐渐转向了更为灵活和便携的家用智能投影仪。随着618电商大促的到来,想要购买投影仪的用户们也开始摩拳擦掌了。本文将从投影仪的基础知识入手,为您推荐几款性价比很高的投影仪&…...
自愿离婚协议书
自愿离婚协议书 男方(夫): 女方(妻): 双方现因 原因,导致夫妻情感已破裂,自愿离婚…...

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

Printing and Exporting
打印 大多数DevExpress。NET控件(XtraGrid、XtraPivotGrid、XttraTreeList、XtraScheduler、XtraCharts)提供打印和导出功能。 所有可打印的DevExpress.NET控件是使用XtraPrinting库提供的方法打印的。 若要确定预览和打印选项是否可用,请检…...

c++【入门】正多边形每个内角的度数
限制 时间限制 : 1 秒 内存限制 : 128 MB 题目 根据多边形内角和定理,正多边形内角和等于:(n - 2)180(n大于等于3且n为整数)(如下图所示是三角形、四边形、五边形、六边形的形状)…...

spring boot3登录开发-邮箱登录/注册接口实现
⛰️个人主页: 蒾酒 🔥系列专栏:《spring boot实战》 🌊山高路远,行路漫漫,终有归途 目录 写在前面 上文衔接 内容简介 功能分析 所需依赖 邮箱验证登录/注册实现 1.创建交互对象 2.登录注册业务逻辑实…...
数据结构-二叉搜索树
二叉搜索树:BST(Binary Search Tree) 二叉搜索树是二叉树,可以为空,如果不为空,满足以下性质: 非空左子树的所有键值小于其根节点的键值非空右子树的所有键值大于其根节点的键值左、右字数本身也都是二叉搜索树 二叉…...

JUnit:Java开发者不可或缺的单元测试框架
在软件开发过程中,测试是确保代码质量的关键环节。单元测试作为测试体系的基础,对提升代码质量、降低bug率、增强软件稳定性具有重要作用。JUnit 作为 Java 语言事实上的标准单元测试框架,已经成为 Java 开发者进行单元测试的首选工具。本文将…...
NG32单片机GPIO口配置方式
目录 一、引言 二、GPIO口基本结构 三、GPIO口配置方式 四、工作原理 五、总结 一、引言 NG32单片机是一款集成度高、功能强大的微控制器。其中,GPIO(General Purpose Input/Output)口作为单片机与外部设备通信的重要接口,具…...

SpringCloud-OpenFeign拓展-连接池、最佳使用方法、日志输出
目录 1 OpenFeign连接池 1.1 常见连接类型 1.2 连接池使用方法 1.2.1 引入依赖 1.2.2 开启连接池功能 1.2.3 配置完成,重启实例即可,底层将更改设置。 2 OpenFeign最佳使用方法 2.1 每个微服务都是单独的project,内部有三个独立模块 …...
跨链协议中Cosmos IBC、Polkadot/XCM、Celer Network的区别以及用途
跨链协议是实现不同区块链之间通信和价值转移的关键技术。Cosmos IBC、Polkadot/XCM 和 Celer Network 是三个在跨链领域内具有代表性的协议,它们各自有着独特的设计理念和应用场景。下面是这三个协议的详细对比: Cosmos IBC (Inter-Blockchain Communi…...
利用ngx_stream_return_module构建简易 TCP/UDP 响应网关
一、模块概述 ngx_stream_return_module 提供了一个极简的指令: return <value>;在收到客户端连接后,立即将 <value> 写回并关闭连接。<value> 支持内嵌文本和内置变量(如 $time_iso8601、$remote_addr 等)&a…...

VB.net复制Ntag213卡写入UID
本示例使用的发卡器:https://item.taobao.com/item.htm?ftt&id615391857885 一、读取旧Ntag卡的UID和数据 Private Sub Button15_Click(sender As Object, e As EventArgs) Handles Button15.Click轻松读卡技术支持:网站:Dim i, j As IntegerDim cardidhex, …...

IT供电系统绝缘监测及故障定位解决方案
随着新能源的快速发展,光伏电站、储能系统及充电设备已广泛应用于现代能源网络。在光伏领域,IT供电系统凭借其持续供电性好、安全性高等优势成为光伏首选,但在长期运行中,例如老化、潮湿、隐裂、机械损伤等问题会影响光伏板绝缘层…...
大数据学习(132)-HIve数据分析
🍋🍋大数据学习🍋🍋 🔥系列专栏: 👑哲学语录: 用力所能及,改变世界。 💖如果觉得博主的文章还不错的话,请点赞👍收藏⭐️留言Ǵ…...

智能分布式爬虫的数据处理流水线优化:基于深度强化学习的数据质量控制
在数字化浪潮席卷全球的今天,数据已成为企业和研究机构的核心资产。智能分布式爬虫作为高效的数据采集工具,在大规模数据获取中发挥着关键作用。然而,传统的数据处理流水线在面对复杂多变的网络环境和海量异构数据时,常出现数据质…...
MySQL 8.0 事务全面讲解
以下是一个结合两次回答的 MySQL 8.0 事务全面讲解,涵盖了事务的核心概念、操作示例、失败回滚、隔离级别、事务性 DDL 和 XA 事务等内容,并修正了查看隔离级别的命令。 MySQL 8.0 事务全面讲解 一、事务的核心概念(ACID) 事务是…...

(一)单例模式
一、前言 单例模式属于六大创建型模式,即在软件设计过程中,主要关注创建对象的结果,并不关心创建对象的过程及细节。创建型设计模式将类对象的实例化过程进行抽象化接口设计,从而隐藏了类对象的实例是如何被创建的,封装了软件系统使用的具体对象类型。 六大创建型模式包括…...

9-Oracle 23 ai Vector Search 特性 知识准备
很多小伙伴是不是参加了 免费认证课程(限时至2025/5/15) Oracle AI Vector Search 1Z0-184-25考试,都顺利拿到certified了没。 各行各业的AI 大模型的到来,传统的数据库中的SQL还能不能打,结构化和非结构的话数据如何和…...
k8s从入门到放弃之HPA控制器
k8s从入门到放弃之HPA控制器 Kubernetes中的Horizontal Pod Autoscaler (HPA)控制器是一种用于自动扩展部署、副本集或复制控制器中Pod数量的机制。它可以根据观察到的CPU利用率(或其他自定义指标)来调整这些对象的规模,从而帮助应用程序在负…...

高考志愿填报管理系统---开发介绍
高考志愿填报管理系统是一款专为教育机构、学校和教师设计的学生信息管理和志愿填报辅助平台。系统基于Django框架开发,采用现代化的Web技术,为教育工作者提供高效、安全、便捷的学生管理解决方案。 ## 📋 系统概述 ### 🎯 系统定…...