FastDDS-4.RTPS層



4. RTPS層

eprosima Fast DDS的較低層RTPS層是RTPS標準協議的實現。與DDS層相比,該層提供了對通信協議內部的更多控制,因此高級用戶可以更好地控制庫的功能。



4.1 與DDS層的關係

該層的元素與DDS層的元素一一對應,並添加了一些元素。該對應關係如下表所示:



4.2 如何使用RTPS層

現在我們將介紹RTPS層的使用,就像我們在DDS層1中所做的那樣,解釋它的新特性。

我們建議您在閱讀本節的同時,查看兩個描述如何使用RTPS層的示例。它們位於examples/cpp/rtps/AsSocket和examples/cpp/rtps/Registered中。



4.2.1 管理Participant

使用RTPSDomain::createParticipant()創建RTPSParticipant。RTPSParticipantAttributes結構用於在創建時配置RTPSPParticipant。

RTPSParticipantAttributes participant_attr;
participant_attr.setName("participant");
RTPSParticipant* participant = RTPSDomain::createParticipant(0, participant_attr);


4.2.2 管理Writer和Reader

正如RTPS標準所規定的,RTPSWriter和RTPSReader始終與History元素相關聯。在DDS層中,History的創建和管理是隱藏的,但在RTPS層中,您可以完全控制History的創建和配置。

Writer使用RTPSDomain::createRTPSWriter()創建,並使用WriterAttributes結構配置。他們還需要一個WriterHistory,它的配置是HistoryAttributes結構。

HistoryAttributes history_attr;
WriterHistory* history = new WriterHistory(history_attr);
WriterAttributes writer_attr;
RTPSWriter* writer = RTPSDomain::createRTPSWriter(participant, writer_attr, history);

與Writer的創建類似,Reader使用RTPSDomain::createRTPSReader()創建,並使用ReaderAttributes結構配置。HistoryAttributes結構用於配置所需的ReaderHistory。注意,在這種情況下,可以提供實現回調的ReaderListener類:

class MyReaderListener : public ReaderListener
{
    // Callbacks override
};
MyReaderListener listener;
HistoryAttributes history_attr;
ReaderHistory* history = new ReaderHistory(history_attr);
ReaderAttributes reader_attr;
RTPSReader* reader = RTPSDomain::createRTPSReader(participant, reader_attr, history, &listener);


4.2.3 使用History去發送和接收數據

在RTPS協議中,讀者和作家將有關主題的數據保存在其關聯的歷史中。每條數據都由一個Change表示,eprosima Fast DDS將其實現爲CacheChange_t。Change始終由History管理。

您可以將新的CacheChange_t添加到Writer的History中以發送數據。流程如下:

  1. 使用RTPSWriter::new_change()向Writer請求CacheChange_t。爲了分配足夠的內存,需要提供一個回調,該回調返回有效負載中的最大字節數。
  2. 用數據填充CacheChange_t。
  3. 使用WriterHistory::add_change()將其添加到History中。

Writer將負責將數據傳達給Reader。

//Request a change from the writer
CacheChange_t* change = writer->new_change([]() -> uint32_t
                {
                    return 255;
                }, ALIVE);
//Write serialized data into the change
change->serializedPayload.length = sprintf((char*) change->serializedPayload.data, "My example string %d", 2) + 1;
//Insert change into the history. The Writer takes care of the rest.
history->add_change(change);

如果主題數據類型有多個字段,則必須提供函數來序列化和反序列化CacheChange_t中的數據。Fast DDS Gen爲您提供這一功能。

您可以從ReaderListener::onNewCacheChangeAdded回調中接收數據,就像我們在DDS層中所做的那樣:

  1. 回調接收包含接收數據的CacheChange_t參數。
  2. 處理接收到的CacheChange_t內的數據。(這一步是Reader處理接收到的數據,也就是業務處理)
  3. 告知Reader的History,不再需要Change,將Change移除。()
class MyReaderListener : public ReaderListener
{
public:

    MyReaderListener()
    {
    }

    ~MyReaderListener()
    {
    }

    void onNewCacheChangeAdded(
            RTPSReader* reader,
            const CacheChange_t* const change)
    {
        // The incoming message is enclosed within the `change` in the function parameters
        printf("%s\n", change->serializedPayload.data);
        // Once done, remove the change
        reader->getHistory()->remove_change((CacheChange_t*)change);
    }

};


4.3 配置Readers和Writers

使用RTPS層的好處之一是它提供了新的配置選項,同時保留了DDS層的選項。例如,可以將Writer或Reader設置爲Reliable或Best Effort端點,如前所述。

writer_attr.endpoint.reliabilityKind = BEST_EFFORT;


4.3.1 設置數據的持久類型(durability kind)(持久類型就是對已經發送的change的處理方式)

Durability參數定義了當新的Reader匹配時,Writer對已發送的樣本的行爲。eProsima Fast DDS提供三種耐久性選項:

  • Volatile(default): 消息在發送時被丟棄。如果新的讀取器在消息n之後匹配,它將從消息n+1開始接收。
  • Transient_local: Writer將保存其最近發送的k條消息的記錄。如果新的讀取器在消息n之後匹配,它將從消息n-k開始接收。
  • Transient: 類似於Transient_local,但消息的記錄將保存到持久性存儲中,因此,如果寫入程序被銷燬並重新創建,或者在應用程序崩潰的情況下,已發送數據仍將可用。

要選擇你喜歡的選項:

writer_attr.endpoint.durabilityKind = TRANSIENT_LOCAL;

因爲在RTPS層中,您可以控制History,所以在Transient_local和Transient模式下,Writer會發送您尚未從歷史中明確釋放的所有Change。



4.4 配置History

History有自己的配置結構HistoryAttributes。



4.4.1 更改有效負載的最大大小

您可以選擇可以進入CacheChange_t的有效負載的最大大小。請確保選擇的大小能夠容納儘可能多的數據:

history_attr.payloadMaxSize  = 250;//Defaults to 500 bytes

4.4.2 修改History的大小

您可以指定要保留的歷史記錄的最大值,並分配歷史記錄中Change的初始數量:(也就是History中最大值和初始值)

history_attr.initialReservedCaches = 250; //Defaults to 500
history_attr.maximumReservedCaches = 500; //Defaults to 0 = Unlimited Changes

當保留Change的初始數量低於最大值時,History在未來根據需要分配更多更改,直到達到最大大小。



4.5 使用自定義的負載池

有效負載定義爲用戶希望在Writer和Reader之間傳輸的數據。RTPS需要向該有效負載添加一些元數據,以便管理端點之間的通信。因此,此Payload被封裝在CacheChange_t的SerializedPayload_t字段中,而CacheChange_t的其餘字段提供所需的元數據。

WriterHistory和ReaderHistory爲用戶提供了一個與這些更改交互的界面:將由Writer傳輸的更改添加到其WriterHistory,並且可以從ReaderHistory中刪除已在Reader上處理的更改。從這個意義上說,歷史記錄充當了尚未完全處理的更改的緩衝區。

在正常執行過程中,新的更改將添加到歷史記錄中,舊的更改將從歷史記錄中刪除。爲了管理這些更改中包含的Payload的生命週期,Reader和Writer使用Pool對象,這是IPayloadPool接口的實現。不同的池實現允許不同的優化。例如,可以從不同的預分配內存塊中取出不同大小的Payload。

Writer和Reader可以自動選擇最適合HistoryAttributes中給出的配置的默認Payload池實現。但是,可以爲RTPSDomain::createRTPSWriter()和RTPSDomain::createRTPSReader()函數提供自定義負載池。當請求或釋放新的CacheChange_t時,Writer和Reader將使用提供的池。



4.5.1 IPayloadPool 接口

  • 重載帶大小參數的函數IPayloadPool::get_payload
    將參數指定大小的空Payload綁定到CacheChange_t實例。然後可以用所需的數據填充有效負載。

  • 用SerializedPayload參數重載IPayloadPool::get_payload
    將給定的Payload數據複製到池中的新Payload,並將其綁定到CacheChange_t實例。此重載還獲取一個指向擁有原始Payload的池的指針。這允許某些優化,例如如果原始負載來自同一個池,則共享Payload,從而避免複製操作。

  • IPayload::release_payload:
    將綁定到CacheChange_t的Payload放回到池Pool,並將payload和CacheChange_t之間的解綁。

**重要**

在實現自定義Payload池時,確保分配的Payload滿足標準RTPS序列化的要求。具體而言,Payloads必須足夠大,以容納序列化用戶數據加上RTPS標準第10.2節中規定的SerializedPayloadHeader的4個八位字節。

例如,如果我們知道序列化用戶數據的上限,我們可以考慮實現一個池,該池總是分配固定大小的Payload,足夠大,可以容納這些數據。如果串行化的用戶數據最多有N個八位字節,則分配的有效負載必須至少有N+4個八位字符。

請注意,請求IPayloadPool::get_payload的大小已經考慮了這個4個八位字節的標頭。


4.5.2 默認的負載池實現

如果沒有向Writer或Reader提供自定義Payload池,Fast DDS將自動使用與History的memoryPolicy策略最匹配的默認實現。

preallocated_memory_mode

所有有效載荷都將有一個固定大小的數據緩衝區,等於有效載荷MaxSize的值,而不考慮請求IPayloadPool::get_payload的大小。已發佈的有效負載可以重新用於另一個CacheChange_t。這以更高的內存使用率爲代價減少了內存分配操作。

在歷史的初始化過程中,initialReservedCaches Payloads爲初始分配的CacheChange_t預先分配。

preallocated_with_realloc_memory_mode
Payloads保證數據緩衝區至少與請求大小和payloadMaxSize之間的最大值一樣大。已發佈的有效負載可以重新用於另一個CacheChange_t。如果至少有一個空閒有效負載的緩衝區大小等於或大於請求的有效負載,則不進行內存分配。

在歷史的初始化過程中,initialReservedCaches Payloads爲初始分配的CacheChange_t預先分配。

dynamic_reserve_memory_mode
每次請求Payload時,都會在內存中分配一個具有適當大小的新Payload。payloadMaxSize被忽略。釋放的Payload的內存總是被釋放的,因此池中永遠不會有空閒的Payload。這以頻繁的內存分配爲代價減少了內存使用。

在歷史的初始化過程中不進行有效負載的預分配。

dynamic_resuable_memory_mode
保證有效負載的數據緩衝區至少與請求的大小一樣大。payloadMaxSize被忽略。
已發佈的有效負載可以重新用於另一個CacheChange_t。如果至少有一個空閒有效負載的緩衝區大小等於或大於請求的有效負載,則不進行內存分配。



4.5.3 使用自定義負載池的案例

// A simple payload pool that reserves and frees memory each time
class CustomPayloadPool : public IPayloadPool
{
    bool get_payload(
            uint32_t size,
            CacheChange_t& cache_change) override
    {
        // Reserve new memory for the payload buffer
        octet* payload = new octet[size];

        // Assign the payload buffer to the CacheChange and update sizes
        cache_change.serializedPayload.data = payload;
        cache_change.serializedPayload.length = size;
        cache_change.serializedPayload.max_size = size;

        // Tell the CacheChange who needs to release its payload
        cache_change.payload_owner(this);

        return true;
    }

    bool get_payload(
            SerializedPayload_t& data,
            IPayloadPool*& /* data_owner */,
            CacheChange_t& cache_change) override
    {
        // Reserve new memory for the payload buffer
        octet* payload = new octet[data.length];

        // Copy the data
        memcpy(payload, data.data, data.length);

        // Assign the payload buffer to the CacheChange and update sizes
        cache_change.serializedPayload.data = payload;
        cache_change.serializedPayload.length = data.length;
        cache_change.serializedPayload.max_size = data.length;

        // Tell the CacheChange who needs to release its payload
        cache_change.payload_owner(this);

        return true;
    }

    bool release_payload(
            CacheChange_t& cache_change) override
    {
        // Ensure precondition
        assert(this == cache_change.payload_owner());

        // Dealloc the buffer of the payload
        delete[] cache_change.serializedPayload.data;

        // Reset sizes and pointers
        cache_change.serializedPayload.data = nullptr;
        cache_change.serializedPayload.length = 0;
        cache_change.serializedPayload.max_size = 0;

        // Reset the owner of the payload
        cache_change.payload_owner(nullptr);

        return true;
    }

};

std::shared_ptr<CustomPayloadPool> payload_pool = std::make_shared<CustomPayloadPool>();

// A writer using the custom payload pool
HistoryAttributes writer_history_attr;
WriterHistory* writer_history = new WriterHistory(writer_history_attr);
WriterAttributes writer_attr;
RTPSWriter* writer = RTPSDomain::createRTPSWriter(participant, writer_attr, payload_pool, writer_history);

// A reader using the same instance of the custom payload pool
HistoryAttributes reader_history_attr;
ReaderHistory* reader_history = new ReaderHistory(reader_history_attr);
ReaderAttributes reader_attr;
RTPSReader* reader = RTPSDomain::createRTPSReader(participant, reader_attr, payload_pool, reader_history);

// Write and Read operations work as usual, but take the Payloads from the pool.
// Requesting a change to the Writer will provide one with an empty Payload taken from the pool
CacheChange_t* change = writer->new_change([]() -> uint32_t
                {
                    return 255;
                }, ALIVE);

// Write serialized data into the change and add it to the history
change->serializedPayload.length = sprintf((char*) change->serializedPayload.data, "My example string %d", 2) + 1;
writer_history->add_change(change);




發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章