CacheChange
前言
CacheChange_t在Fast DDS中扮演着重要的角色,并且对于实时通信和数据发布-订阅模型的实现具有关键意义。
它是Fast DDS中用于管理数据变化的关键组件之一,用于表示在数据发布者和订阅者之间交换的数据单元,并在DDS中的发布-订阅过程中起到关键的作用。CacheChange_t结构中并没有包含实际的数据,而是包含了数据的指针(参考SerializedPayload_t),以及与数据相关的元数据,如时间戳、序列号等。通过CacheChange_t,Fast DDS能够有效地跟踪和传输数据更新,以保证可靠的通信和数据一致性。
在本文中,我将深入探讨CacheChange_t结构的内部实现细节和关键功能。通过对CacheChange_t的深入分析,读者将能够更好地理解Fast DDS内部的数据管理机制。
CacheChange_t
struct CacheChangeWriterInfo_t
{
//!Number of DATA / DATA_FRAG submessages sent to the transport (only used in Writers)
size_t num_sent_submessages = 0;
//! Used to link with previous node in a list. Used by FlowControllerImpl.
//! Cannot be cached because there are several comparisons without locking.
CacheChange_t* volatile previous = nullptr;
//! Used to link with next node in a list. Used by FlowControllerImpl.
//! Cannot be cached because there are several comparisons without locking.
CacheChange_t* volatile next = nullptr;
};
/*!
* Specific information for a reader.
*/
struct CacheChangeReaderInfo_t
{
//!Reception TimeStamp (only used in Readers)
Time_t receptionTimestamp;
//! Disposed generation of the instance when this entry was added to it
int32_t disposed_generation_count;
//! No-writers generation of the instance when this entry was added to it
int32_t no_writers_generation_count;
};
struct RTPS_DllAPI CacheChange_t
{
//!Kind of change, default value ALIVE.
ChangeKind_t kind = ALIVE;
//!GUID_t of the writer that generated this change.
GUID_t writerGUID{};
//!Handle of the data associated with this change.
InstanceHandle_t instanceHandle{};
//!SequenceNumber of the change
SequenceNumber_t sequenceNumber{};
//!Serialized Payload associated with the change.
SerializedPayload_t serializedPayload{};
//!CDR serialization of inlined QoS for this change.
SerializedPayload_t inline_qos{};
//!Indicates if the cache has been read (only used in READERS)
bool isRead = false;
//!Source TimeStamp
Time_t sourceTimestamp{};
union
{
CacheChangeReaderInfo_t reader_info;
CacheChangeWriterInfo_t writer_info;
};
WriteParams write_params{};
bool is_untyped_ = true;
...
}
copy
copy数据,如果is_untyped_并且现在data的size不够大会返回false。
bool copy(
const CacheChange_t* ch_ptr)
{
kind = ch_ptr->kind;
writerGUID = ch_ptr->writerGUID;
instanceHandle = ch_ptr->instanceHandle;
sequenceNumber = ch_ptr->sequenceNumber;
sourceTimestamp = ch_ptr->sourceTimestamp;
reader_info.receptionTimestamp = ch_ptr->reader_info.receptionTimestamp;
write_params = ch_ptr->write_params;
isRead = ch_ptr->isRead;
fragment_size_ = ch_ptr->fragment_size_;
fragment_count_ = ch_ptr->fragment_count_;
first_missing_fragment_ = ch_ptr->first_missing_fragment_;
return serializedPayload.copy(&ch_ptr->serializedPayload, !ch_ptr->is_untyped_);
}
copy_not_memcpy
只copy信息,不copy数据。
void copy_not_memcpy(
const CacheChange_t* ch_ptr)
{
kind = ch_ptr->kind;
writerGUID = ch_ptr->writerGUID;
instanceHandle = ch_ptr->instanceHandle;
sequenceNumber = ch_ptr->sequenceNumber;
sourceTimestamp = ch_ptr->sourceTimestamp;
reader_info.receptionTimestamp = ch_ptr->reader_info.receptionTimestamp;
write_params = ch_ptr->write_params;
isRead = ch_ptr->isRead;
// Copy certain values from serializedPayload
serializedPayload.encapsulation = ch_ptr->serializedPayload.encapsulation;
// Copy fragment size and calculate fragment count
setFragmentSize(ch_ptr->fragment_size_, false);
}
setFragmentSize
收到change的第一个fragment的时候,create_fragment_list应该传true。如果是发送,我们肯定是先把数据整个给到change,然后再发送的,也就是说没有丢任何一个fragment,此时first_missing_fragment_ = fragment_count_。
这里算fragment_count_的时候有个小技巧fragment_count_ = (serializedPayload.length + fragment_size - 1) / fragment_size
,就是先加上fragment_size - 1再除以fragment_size,类似std::ceil。
如果create_fragment_list为true,假设fragment size = 8,在用户data的内存上,第一个fragment开始位置的四个字节,存入missing的frament是1,在第二个fragment的开头四个字节会存入missing的frament是2,以此类推。
void setFragmentSize(
uint16_t fragment_size,
bool create_fragment_list = false)
{
fragment_size_ = fragment_size;
fragment_count_ = 0;
first_missing_fragment_ = 0;
if (fragment_size > 0)
{
// This follows RTPS 8.3.7.3.5
fragment_count_ = (serializedPayload.length + fragment_size - 1) / fragment_size;
if (create_fragment_list)
{
// Keep index of next fragment on the payload portion at the beginning of each fragment. Last
// fragment will have fragment_count_ as 'next fragment index'
size_t offset = 0;
for (uint32_t i = 1; i <= fragment_count_; i++, offset += fragment_size_)
{
set_next_missing_fragment(i - 1, i); // index to next fragment in missing list
}
}
else
{
// List not created. This means we are going to send this change fragmented, so it is already
// assembled, and the missing list is empty (i.e. first missing points to fragment count)
first_missing_fragment_ = fragment_count_;
}
}
}
set_next_missing_fragment
void set_next_missing_fragment(
uint32_t fragment_index,
uint32_t next_fragment_index)
{
uint32_t* ptr = next_fragment_pointer(fragment_index);
*ptr = next_fragment_index;
}
get_next_missing_fragment
uint32_t get_next_missing_fragment(
uint32_t fragment_index)
{
uint32_t* ptr = next_fragment_pointer(fragment_index);
return *ptr;
}
next_fragment_pointer
最后返回的serializedPayload.data[offset],是一个用来存储用户data的内存地址。这里offset = (offset + 3u) & ~3u;
与前面说的std::ceil类似,四字节对齐。
uint32_t* next_fragment_pointer(
uint32_t fragment_index)
{
size_t offset = fragment_size_;
offset *= fragment_index;
offset = (offset + 3u) & ~3u;
return reinterpret_cast<uint32_t*>(&serializedPayload.data[offset]);
}
get_missing_fragments
循环获取所有missing的fragment,开始初始化的时候fragment 0的位置存的next missing 是1,fragment 1位置存2,fragment 2位置存3,再收到fragment的时候一定会更新这个next missing,比如第一次收到的是fragment 1,那么fragment 0的位置一定会被更新为2。其实这里就是在用户的data数据中隐藏了一个missing fragment的列表。
/*!
* Fills a FragmentNumberSet_t with the list of missing fragments.
* @param [out] frag_sns FragmentNumberSet_t where result is stored.
*/
void get_missing_fragments(
FragmentNumberSet_t& frag_sns)
{
// Note: Fragment numbers are 1-based but we keep them 0 based.
frag_sns.base(first_missing_fragment_ + 1);
// Traverse list of missing fragments, adding them to frag_sns
uint32_t current_frag = first_missing_fragment_;
while (current_frag < fragment_count_)
{
frag_sns.add(current_frag + 1);
current_frag = get_next_missing_fragment(current_frag);
}
}
received_fragments
if (initial_fragment <= first_missing_fragment_)
说明新来的fragments之前没有missing的fragment了,只需要将first_missing_fragment_后移即可。否则需要更新隐藏在用户data里的 missing fragment 列表。
/*!
* Mark a set of consecutive fragments as received.
* This will remove a set of consecutive fragments from the missing list.
* Should be called BEFORE copying the received data into the serialized payload.
*
* @param initial_fragment Index (0-based) of first received fragment.
* @param num_of_fragments Number of received fragments. Should be strictly positive.
* @return true if the list of missing fragments was modified, false otherwise.
*/
bool received_fragments(
uint32_t initial_fragment,
uint32_t num_of_fragments)
{
bool at_least_one_changed = false;
if ((fragment_size_ > 0) && (initial_fragment < fragment_count_))
{
uint32_t last_fragment = initial_fragment + num_of_fragments;
if (last_fragment > fragment_count_)
{
last_fragment = fragment_count_;
}
if (initial_fragment <= first_missing_fragment_)
{
// Perform first = *first until first >= last_received
while (first_missing_fragment_ < last_fragment)
{
first_missing_fragment_ = get_next_missing_fragment(first_missing_fragment_);
at_least_one_changed = true;
}
}
else
{
// Find prev in missing list
uint32_t current_frag = first_missing_fragment_;
while (current_frag < initial_fragment)
{
uint32_t next_frag = get_next_missing_fragment(current_frag);
if (next_frag >= initial_fragment)
{
// This is the fragment previous to initial_fragment.
// Find future value for next by repeating next = *next until next >= last_fragment.
uint32_t next_missing_fragment = next_frag;
while (next_missing_fragment < last_fragment)
{
next_missing_fragment = get_next_missing_fragment(next_missing_fragment);
at_least_one_changed = true;
}
// Update next and finish loop
if (at_least_one_changed)
{
set_next_missing_fragment(current_frag, next_missing_fragment);
}
break;
}
current_frag = next_frag;
}
}
}
return at_least_one_changed;
}
add_fragments
将fragment加入到serializedPayload.data中,返回是否收全整个消息。
bool add_fragments(
const SerializedPayload_t& incoming_data,
uint32_t fragment_starting_num,
uint32_t fragments_in_submessage)
{
uint32_t original_offset = (fragment_starting_num - 1) * fragment_size_;
uint32_t incoming_length = fragment_size_ * fragments_in_submessage;
uint32_t last_fragment_index = fragment_starting_num + fragments_in_submessage - 1;
// Validate fragment indexes
if (last_fragment_index > fragment_count_)
{
return false;
}
// validate lengths
if (last_fragment_index < fragment_count_)
{
if (incoming_data.length < incoming_length)
{
return false;
}
}
else
{
incoming_length = serializedPayload.length - original_offset;
}
if (original_offset + incoming_length > serializedPayload.length)
{
return false;
}
if (received_fragments(fragment_starting_num - 1, fragments_in_submessage))
{
memcpy(
&serializedPayload.data[original_offset],
incoming_data.data, incoming_length);
}
return is_fully_assembled();
}
总结
CacheChange_t在DDS中的发布-订阅过程中起到关键的作用,所有的消息传递都要使用这个结构,单次传递消息过大时,CacheChange_t会把它分割成多个小块数据传输,收到后再组装成一个完成的消息。里面的时间信息可以用来计算系统延时,这对于计算一个实时通信系统的性能指标非常重要,其他字段我们在使用的地方详细的说明。