FastDDS中Utils定义的那些数据结构(二)
1、constructor_macros.hpp
这个文件中定义的宏都是和C++编译器提供的默认函数相关的,这样做的目的有助于代码简洁,所有类的构造函数都适用。这个是可以复用的,自己的库也可以设计成这样的。代码如下:
#ifndef UTILS_CONSTRUCTOR_MACROS_HPP_
#define UTILS_CONSTRUCTOR_MACROS_HPP_
#ifndef FASTDDS_COPY_OPERATIONS
#define FASTDDS_COPY_OPERATIONS(ClassName, access) \
ClassName(const ClassName&) = access; \
ClassName& operator = (const ClassName&) = access
#endif // !FASTDDS_COPY_OPERATIONS
#ifndef FASTDDS_MOVE_OPERATIONS
#define FASTDDS_MOVE_OPERATIONS(ClassName, access) \
ClassName(ClassName &&) = access; \
ClassName& operator = (ClassName &&) = access
#endif // !FASTDDS_COPY_OPERATIONS
#ifndef FASTDDS_DEFAULT_COPY
#define FASTDDS_DEFAULT_COPY(ClassName) FASTDDS_COPY_OPERATIONS(ClassName, default)
#endif // !FASTDDS_DEFAULT_COPY
#ifndef FASTDDS_DELETED_COPY
#define FASTDDS_DELETED_COPY(ClassName) FASTDDS_COPY_OPERATIONS(ClassName, delete)
#endif // !FASTDDS_DELETED_COPY
#ifndef FASTDDS_DEFAULT_MOVE
#define FASTDDS_DEFAULT_MOVE(ClassName) FASTDDS_MOVE_OPERATIONS(ClassName, default)
#endif // !FASTDDS_DEFAULT_COPY
#ifndef FASTDDS_DELETED_MOVE
#define FASTDDS_DELETED_MOVE(ClassName) FASTDDS_MOVE_OPERATIONS(ClassName, delete)
#endif // !FASTDDS_DELETED_COPY
2、DBQueue双缓存队列
这个类是一个双缓存队列,生产者线程将生产的数据放到mForegroundQueue队列,消费者线程从mBackgroundQueue队列取数据。当mBackgroundQueue队列的数据空时,就会交换两个队列。核心代码如下:
//! Clears foreground queue and swaps queues.
// 这个函数一般在前缓冲区队列为空时调用,消费者没有数据可以继续消费了
void Swap()
{
std::unique_lock<std::mutex> fgGuard(mForegroundMutex);
std::unique_lock<std::mutex> bgGuard(mBackgroundMutex);
// Clear the foreground queue.
std::queue<T>().swap(*mForegroundQueue);
auto* swap = mBackgroundQueue;
mBackgroundQueue = mForegroundQueue;
mForegroundQueue = swap;
}
//! Pushes to the background queue. Copy constructor.
// 生产者将数据放入后缓冲区队列
void Push(
const T& item)
{
std::unique_lock<std::mutex> guard(mBackgroundMutex);
mBackgroundQueue->push(item);
}
//! Pushes to the background queue. Move constructor.
void Push(
T&& item)
{
std::unique_lock<std::mutex> guard(mBackgroundMutex);
mBackgroundQueue->push(std::move(item));
}
//! Returns a reference to the front element
//! in the foregrund queue.
// 消费者从前缓冲区队列取数据
T& Front()
{
std::unique_lock<std::mutex> guard(mForegroundMutex);
return mForegroundQueue->front();
}
3、IPFinder查找IP工具类
其实这个类的主要作用是提供接口,利用系统函数获取机器的ip地址和mac地址,然后再通过一些列的转换,将获取到的数据转化为DDS中存储ip的数据结构中。下面代码可以表示该类的核心作用,返回的数据结构是LocatorList_t*,getIPs函数中会一层一层的调用系统函数。
bool IPFinder::getAllIPAddress(
LocatorList_t* locators)
{
std::vector<info_IP> ip_names;
if (IPFinder::getIPs(&ip_names))
{
locators->clear();
for (auto it = ip_names.begin();
it != ip_names.end(); ++it)
{
if (it->type == IP6)
{
locators->push_back(it->locator);
}
else if (it->type == IP4)
{
locators->push_back(it->locator);
}
}
return true;
}
return false;
}
4、Host主机类和Semaphore类
Host主机类会获取两个id,一个是mac地址(48个字节),另一个是机器的ip地址(16个字节),FastDDS中是用这两个id唯一标识一台机器的。
Semaphore类是FastDDS自定义的信号量,代码并不难,但是有一个值得学习的点,那就是成员变量中的disable_。disable_ 变量用于控制信号量是否处于禁用状态,其主要作用是在某些情况下避免线程阻塞,比如:
- 用于确保 Semaphore 在被销毁时不会造成死锁,在析构 Semaphore 之前,可以将 disable_ 设为 true,然后通知所有等待中的线程,以防止它们进入等待状态,导致程序死锁。
- 避免在不安全的状态下调用 wait(),disable_ 可能会在某些异常情况下被设为 true,这样当 wait() 被调用时,可以直接返回,而不会让线程进入阻塞等待。
代码如下:
class Semaphore
{
public:
explicit Semaphore(
size_t count = 0);
Semaphore(
const Semaphore&) = delete;
Semaphore& operator =(
const Semaphore&) = delete;
void post();
void wait();
void disable();
void enable();
void post(
int n);
private:
size_t count_;
std::mutex mutex_;
std::condition_variable cv_;
bool disable_;
};
5、SystemInfo系统信息类
这个类中主要是封装了一些获取系统信息的一些方法。比如获取进程id、主机ip、获取某个环境变量的值、获取当前进程属于哪个用户、文件是否存在、设置环境变量等方法。需要注意的是,在获取环境变量时,一般是先从配置文件中获取,当配置文件中找不到时,从环境变量中获取。该类中还提供这样一个接口,支持注册一些回调函数,这些回掉函数在监听的文件发生变化时调用,也支持取消监听文件的变化。
FileWatchHandle SystemInfo::watch_file(
std::string filename,
std::function<void()> callback,
const fastdds::rtps::ThreadSettings& watch_thread_config,
const fastdds::rtps::ThreadSettings& callback_thread_config)
{
#if defined(_WIN32) || defined(__unix__)
// 次函数的实现是依赖于第3方库的。
return FileWatchHandle (new filewatch::FileWatch<std::string>(filename,
[callback](const std::string& /*path*/, const filewatch::Event change_type)
{
switch (change_type)
{
case filewatch::Event::modified:
callback();
break;
default:
// No-op
break;
}
}, watch_thread_config, callback_thread_config));
#else // defined(_WIN32) || defined(__unix__)
static_cast<void>(filename);
static_cast<void>(callback);
static_cast<void>(watch_thread_config);
static_cast<void>(callback_thread_config);
return FileWatchHandle();
#endif // defined(_WIN32) || defined(__unix__)
}
void SystemInfo::stop_watching_file(
FileWatchHandle& handle)
{
#if defined(_WIN32) || defined(__unix__)
handle.reset();
#endif // if defined(_WIN32) || defined(__unix__)
static_cast<void>(handle);
}
6、time_t_helpers.hpp和TimeConversion.hpp
后面这个文件定义了一系列的时间转换函数,前面这个文件中主要定义了3个函数,都是用作时间转换,主要针对分数时间和计算unix时间。什么是分数时间、unix时间、utc时间、GPS时间呢?
- 分数时间(Fractional Time):常用于高精度时间计算,特别是在 FastDDS、RTPS、NTP 等协议中。它将时间表示为秒的整数部分 + 小数部分,其中小数部分通常用 32 位无符号整数存储,表示秒的小数部分。例如,fraction = 0x80000000 对应 0.5 秒。这种表示方式能够提供纳秒级的时间精度。
- UTC时间(Coordinated Universal Time,协调世界时):UTC 是当前全球使用的标准时间,基于 国际原子时(TAI),但会加入 闰秒 以保持与地球自转(UT1)同步。例如,2024 年 UTC = TAI - 37 秒,而未来如果有新的闰秒调整,这个差值可能会变化。UTC 被广泛应用于全球通信、金融、互联网等领域。
- Unix时间(Unix Time / Epoch Time):Unix 时间是从 1970 年 1 月 1 日 00:00:00 UTC 开始计算的秒数(不包括闰秒)。例如,2025 年 1 月 1 日 00:00:00 UTC 的 Unix 时间戳是 1735689600。由于 Unix 时间不考虑闰秒,因此它与 UTC 在某些精确应用中可能存在偏差。
- GPS时间(GPS Time):GPS 时间起始于 1980 年 1 月 6 日 00:00:00 UTC,并且不会受闰秒影响。它的计算方式为 GPS 时间 = TAI - 19 秒(固定值)。相比 UTC,GPS 时间更稳定,非常适用于高精度定位和授时系统。