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…...
论文解读:交大港大上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化学习框架(二)
HoST框架核心实现方法详解 - 论文深度解读(第二部分) 《Learning Humanoid Standing-up Control across Diverse Postures》 系列文章: 论文深度解读 + 算法与代码分析(二) 作者机构: 上海AI Lab, 上海交通大学, 香港大学, 浙江大学, 香港中文大学 论文主题: 人形机器人…...

简易版抽奖活动的设计技术方案
1.前言 本技术方案旨在设计一套完整且可靠的抽奖活动逻辑,确保抽奖活动能够公平、公正、公开地进行,同时满足高并发访问、数据安全存储与高效处理等需求,为用户提供流畅的抽奖体验,助力业务顺利开展。本方案将涵盖抽奖活动的整体架构设计、核心流程逻辑、关键功能实现以及…...
逻辑回归:给不确定性划界的分类大师
想象你是一名医生。面对患者的检查报告(肿瘤大小、血液指标),你需要做出一个**决定性判断**:恶性还是良性?这种“非黑即白”的抉择,正是**逻辑回归(Logistic Regression)** 的战场&a…...

.Net框架,除了EF还有很多很多......
文章目录 1. 引言2. Dapper2.1 概述与设计原理2.2 核心功能与代码示例基本查询多映射查询存储过程调用 2.3 性能优化原理2.4 适用场景 3. NHibernate3.1 概述与架构设计3.2 映射配置示例Fluent映射XML映射 3.3 查询示例HQL查询Criteria APILINQ提供程序 3.4 高级特性3.5 适用场…...
uni-app学习笔记二十二---使用vite.config.js全局导入常用依赖
在前面的练习中,每个页面需要使用ref,onShow等生命周期钩子函数时都需要像下面这样导入 import {onMounted, ref} from "vue" 如果不想每个页面都导入,需要使用node.js命令npm安装unplugin-auto-import npm install unplugin-au…...

k8s业务程序联调工具-KtConnect
概述 原理 工具作用是建立了一个从本地到集群的单向VPN,根据VPN原理,打通两个内网必然需要借助一个公共中继节点,ktconnect工具巧妙的利用k8s原生的portforward能力,简化了建立连接的过程,apiserver间接起到了中继节…...
爬虫基础学习day2
# 爬虫设计领域 工商:企查查、天眼查短视频:抖音、快手、西瓜 ---> 飞瓜电商:京东、淘宝、聚美优品、亚马逊 ---> 分析店铺经营决策标题、排名航空:抓取所有航空公司价格 ---> 去哪儿自媒体:采集自媒体数据进…...

Spring数据访问模块设计
前面我们已经完成了IoC和web模块的设计,聪明的码友立马就知道了,该到数据访问模块了,要不就这俩玩个6啊,查库势在必行,至此,它来了。 一、核心设计理念 1、痛点在哪 应用离不开数据(数据库、No…...

七、数据库的完整性
七、数据库的完整性 主要内容 7.1 数据库的完整性概述 7.2 实体完整性 7.3 参照完整性 7.4 用户定义的完整性 7.5 触发器 7.6 SQL Server中数据库完整性的实现 7.7 小结 7.1 数据库的完整性概述 数据库完整性的含义 正确性 指数据的合法性 有效性 指数据是否属于所定…...

ZYNQ学习记录FPGA(一)ZYNQ简介
一、知识准备 1.一些术语,缩写和概念: 1)ZYNQ全称:ZYNQ7000 All Pgrammable SoC 2)SoC:system on chips(片上系统),对比集成电路的SoB(system on board) 3)ARM:处理器…...