來自:https://eprosima-fast-rtps.readthedocs.io/en/latest/pubsub.html
參考《FastRTPS User Manual.odt》第五章
Publisher-Subscriber接口層
eProsima Fast RTPS 提供了高層的Publisher-Subscriber層,該層是RTPS協議上的簡單抽象。通過這層,你可以直接編寫程序代碼而不用管低層RTPS配置,因爲這是由庫自己管理的。(底層RTPS有很多配置選項,可以改善此層的應用效果)
類介紹
Domain
創建Participant、Subscriber、Publisher的管理類,以及註冊在網絡上使用的數據類型和話題。
自定義話題類型例子:https://blog.csdn.net/JL_Gao/article/details/85685086
Participant
管理Publisher和Subscriber的分組類,保存了Publisher、Subscriber、TopicDataType等數據。
ParticipantAttributes
創建Participant配置選項,有一些參數是必須指定的:DomainId 和 Discovery
DomainId:用於計算PDP發現端口,非常適用於同一個網絡中有多個應用程序的情況,這樣就能將網絡中的程序分離。對於同一個計算機上同樣的DomainId的Participant來說,發送接收PDP消息使用的多播端口是一樣的,單播端口與ParticipantID有關,所以每個Participant用到的單播端口是不一樣的。對於網絡中不同的計算機來說,只要Participant的DomainId一樣,就可以通過同一個多播地址來發現彼此,DomainId不同的Participant,他們發送接收PDP消息的多播地址端口不一樣,也就不會彼此發現了。
Discovery:如果使用靜態發現(SEDP),需要提供相應的XML端點配置文件。
創建Participant的例子:
ParticipantAttributes PParam;
Pparam.rtps.setName("participant");
Pparam.rtps.builtin.domainId = 80;
Pparam.rtps.listenSocketBufferSize = 100000;
Participant* p = Domain::createParticipant(PParam);
if(p!=nullptr){
//Participant correctly created
}
//To remove:
Domain::removeParticipant(p);
從Participant修改RTPS底層網絡配置:
ParticipantAttributes Pparams;
auto myTransport = std::make_shared<UDPv6Transport::TransportDescriptor>();
myTransport->receiveBufferSize = 65536;
myTransport->granularMode = false;
Pparams.rtps.useBuiltinTransport = false;
Pparams.rtps.userTransports.push_back(myTransport);
Publisher
用於向網絡中的Subscriber發送數據。用戶通過Publisher發送數據,也可以通過PublisherListener處理一些事件。
自定義Publisher例子:https://blog.csdn.net/JL_Gao/article/details/85694276
Domain創建Publisher的驗證過程:
1. 如果Participant使用的SEDP靜態端點發現協議,則必須定義userDefinedId,且>0
2. 使用的數據類型必須提前註冊
3. 如果topic類型是WITH_KEY,註冊的數據類型必須實現getKey方法。
數據包拆分:
發送緩衝區大小的參數是從Participant繼承的,默認爲65KB。當RTPS消息很大不能一次發送時,該消息將會被分割。分割後的消息會通過多個數據包發送,此時需將 qos.m_publishMode 設置爲異步。
在高效模式下,數據包的大小正確就意味着數據包被正確接收了。
// Allows fragmentation.
publisher_attr.qos.m_publishMode.kind = ASYNCHRONOUS_PUBLISH_MODE;
流控制策略:
用戶可配置流控制策略,用來限制在特定條件下發送的數據量(可用不同的流控制器類型實現)
啓用內置吞吐量控制器:
// This controller allows 300kb per second.
ThroughputControllerDescriptor slowPublisherThroughputController{300000, 1000};
PublisherAttributes WparamSlow;
WparamSlow.terminalThroughputController = slowPublisherThroughputController;
mp_slow_publisher = Domain::createPublisher(mp_participant,WparamSlow,(PublisherListener*)&m_listener);
PublisherListener
實現的方法應避免耗時循環和阻塞語句,以防引起事件或偵聽線程阻塞。
Subscriber
自定義Subscriber例子:https://blog.csdn.net/JL_Gao/article/details/85694276
數據包拆分支持:能夠接收單個重量級消息的多個數據包
1. 利用Writer的流控制,使Reader能夠跟上發送的帶寬
2. 利用可靠QoS,當Reader無法跟上發送帶寬時,讓丟失的分片重新發送。
QoS
FastRTPS對標準的QoS提供了部分本地支持,此外,不能通過API訪問的QoS類型可以在用戶端實現。
SampleInfo_t:從History中讀取或獲取數據時提供的輔助結構,用在takeNextData 和 readNextData
sampleKind:ALIVE(活動的)、DISPOSED(已處理的)、UNREGISTERED(未註冊的)
ownershipStrength:接收到數據時,Writer的所有權強度
sourceTimestamp:數據發送的時間戳,可用於實現基於時間的過濾
sample_identity:Writer的GUID、數據的序號
MatchingInfo:兩個端點間的匹配信息,用在onPublicationMatched 和 onSubscriptionMatched
remoteEndpointGuid:匹配的Writer或Reader的GUID
status:MATCHED_MATCHING、REMOVED_MATCHING
基於時間的過濾和基於內容的過濾Qos例子:https://blog.csdn.net/JL_Gao/article/details/85700559
所有權Qos例子:https://blog.csdn.net/JL_Gao/article/details/85700559
期限和所有權強度QoS例子:參考源碼中example
如何使用Publisher-Subscriber層
第一步:創建Participant對象,相當於我們程序中用到的Publisher與Subscriber的容器。
ParticipantAttributes participant_attr; //Configuration structure
Participant *participant = Domain::createParticipant(participant_attr);
Domain -- 創建Participant、Subscriber、Publisher的管理類,以及註冊在網絡上使用的數據類型和話題。
Participant -- 管理Publisher和Subscriber的分組類,保存了Publisher、Subscriber、TopicDataType等數據。
ParticipantAttributes -- Participant配置,上面代碼中採用的默認配置。默認配置提供了一些基本選項包括通信用到的端口。
第二步:註冊Topic
HelloWorldPubSubType m_type; //Auto-generated type from FastRTPSGen
Domain::registerType(participant, &m_type);
HelloWorldPubSubType -- 由fastrtpsgen生成的類
第三步:創建Publisher對象
PublisherAttributes publisher_attr; //Configuration structure
PubListener m_listener; //Class that implements callbacks from the publisher
Publisher *publisher = Domain::createPublisher(participant, publisher_attr, (PublisherListener *)&m_listener);
PubListener -- 實現回調函數
Publisher 有一組可選的回調函數,它們會中事件發生時觸發,例如當Subscriber開始偵聽同一個Topic。可通過繼承PublisherListener實現對不同事件的處理。
class PubListener : public PublisherListener
{
public PubListener(){};
~PubListener(){};
void onPublicationmatched(Publisher* pub, MatchingInfo& info)
{
//Callback implementation. This is called each time the Publisher finds a Subscriber on the network that listens to the same topic.
}
} m_listener;
第四步:發佈數據
HelloWorld m_Hello; //Auto-generated container class for topic data from FastRTPSGen
m_Hello.msg("Hello there!"); // Add contents to the message
publisher->write((void *)&m_Hello); //Publish
第五步:創建Subscriber對象
SubscriberAttributes subscriber_attr; //Configuration structure
SubListener m_listener; //Class that implements callbacks from the Subscriber
Subscriber *subscriber = Domain::createSubscriber(participant,subscriber_attr,(SubsciberListener*)&m_listener);
SubListener類 -- 實現回調函數(實現同PubListener)
配置
通過參數sendSocketBufferSize和listenSocketBufferSize可配置底層UDP套接字的發送和接收緩衝區大小。因爲當要發送的數據類型大於底層發送緩衝區時,FastRTPS會將數據拆分成多個數據碎片,並在接收端重新組合構建。
Participant 配置
代碼方式:
ParticipantAttributes participant_attr;
participant_attr.setName("my_participant");
participant_attr.rtps.builtin.domainId = 80;
Participant *participant = Domain::createParticipant(participant_attr);
XML文件方式:
Participant *participant = Domain::createParticipant("participant_xml_profile");
<profiles>
<participant profile_name="participant_xml_profile">
<rtps>
<name>my_participant</name>
<builtin>
<domainId>80</domainId>
</builtin>
</rtps>
</participant>
</profiles>
配置項:
setName:設置Participant Name,這是RTPS協議元數據中一部分。
rtps.builtin.domainId:域ID,一般用於區分不同應用。
Publisher和Subscriber 配置
代碼方式:
PublisherAttributes publisher_attr;
publisher_attr.topic.topicDataType = "HelloWorldType";
publisher_attr.topic.topicName = "HelloWorldTopic";
publisher_attr.qos.m_reliability.kind = RELIABLE_RELIABILITY_QOS;
publisher_attr.topic.historyQos.kind =KEEP_LAST_HISTORY_QOS;
publisher_attr.topic.historyQos.depth = 5
publisher_attr.qos.m_durability.kind =TRANSIENT_LOCAL_DURABILITY_QOS;
publisher_attr.topic.resourceLimitsQos.max_samples = 200;
Locator_t unicast_locator;
unicast_locator.port = 7800;
publisher_attr.unicastLocatorList.push_back(unicast_locator);
Locator_t multicast_locator;
multicast_locator.set_IP4_address("239.255.0.4");
multicast_locator.port = 7900;
publisher_attr.multicastLocatorList.push_back(multicast_locator);
Publisher *publisher = Domain::createPublisher(participant, publisher_attr);
SubscriberAttributes subscriber_attr;
...
Subscriber *subscriber = Domain::createSubscriber(participant, subscriber_attr);
XML方式:
Publisher *publisher = Domain::createPublisher(participant, "publisher_xml_profile");
Subscriber *subscriber = Domain::createSubscriber(participant, "subscriber_xml_profile");
<profiles>
<publisher profile_name="publisher_xml_profile">
<topic>
<dataType>HelloWorldType</dataType>
<name>HelloWorldTopic</name>
<historyQos>
<kind>KEEP_LAST</kind>
<depth>5</depth>
</historyQos>
<resourceLimitsQos>
<max_samples>200</max_samples>
</resourceLimitsQos>
</topic>
<qos>
<reliability>
<kind>RELIABLE</kind>
</reliability>
<durability>
<kind>TRANSIENT_LOCAL</kind>
</durability>
</qos>
<unicastLocatorList>
<locator>
<port>7800</port>
</locator>
</unicastLocatorList>
<multicastLocatorList>
<locator>
<address>239.255.0.4</address>
<port>7900</port>
</locator>
</multicastLocatorList>
</publisher>
<subscriber profile_name="subscriber_xml_profile">
...
</subscriber>
</profiles>
配置項(藍色字體是代碼中的配置值,藍色字體後面的是XML文件中的配置值):
topic.topicDataType:Topic數據類型,
topic.topicName:Topic名稱,
topic.topicKind:WITH_KEY、NO_KEY(默認)
topic.resourceLimitsQos.max_samples:History的最大存儲大小
topic.resourceLimitsQos.max_instance:(Subscriber) 最多接收多少個關鍵字的數據
topic.resourceLimitsQos.max_samples_per_instance:(Subscriber)每個關鍵字的能接收的最大大小
注意:max_samples 必須大於max_samples_per_instance
topic.historyQos:RTPS底層History元素配置參數訪問點
topic.historyQos.kind:緩存策略,KEEP_ALL、KEEP_LAST(默認)
KEEP_ALL_HISTORY_QOS(KEEP_ALL)-- 保存所有的Change數據
KEEP_LAST_HISTORY_QOS(KEEP_LAST) -- 當數據條數大於depth時,保存最新的Change數據,並覆蓋舊數據
qos.m_publishMode:ASYNCHRONOUS_PUBLISH_MODE
qos.m_ownership.kind:EXCLUSIVE_OWNERSHIP_QOS(只能有一個Writer能更新實例,該Writer擁有最大所有權)
qos.m_reliability.kind:可靠性,默認BEST_EFFORT
BEST_EFFORT_RELIABILITY_QOS (BEST_EFFORT)-- 不需要接收者回復確認,較快,但可能有數據丟失。
RELIABLE_RELIABILITY_QOS (RELIABLE)-- 需要接收者回復確認,較慢,能防止數據丟失。
!!!注意配置的兼容性!!!
Publisher \ Subscriber |
Best Effort | Reliable |
---|---|---|
Best Effort | ✓ | ✕ |
Reliable | ✓ | ✓ |
qos.m_durability.kind:持久性,定義了在subscriber加入前,對topic上數據的存儲行爲。
publisher默認TRANSIENT_LOCAL,subscriber默認VOLATILE
VOLATILE_DURABILITY_QOS(VOLATILE)-- 忽略subscriber matched之前的數據
TRANSIENT_LOCAL_DURABILITY_QOS(TRANSIENT_LOCAL)-- 有新的subscriber時,會將過去的數據添加到History
TRANSIENT_DURABILITY_QOS(不瞭解)-- 有新的subscriber時,會將持久存儲中的數據添加到History
times.heartbeatPeriod:心跳週期,用seconds和fraction字段設置秒和毫秒,默認3s。主要用在底層StatefulWriter上面,用來週期性的檢查對方有沒有收到數據。減少心跳週期能提高不穩定網絡情況下的性能。
times.nackResponseDelay:返回ACKNACK消息前的延遲,主要用在底層StatefulWriter上面,用來向對方發送沒有收到的數據。
unicastLocator:單播定位器,定義接收數據的網絡端點(參考網絡配置)。Publisher和Subscriber會從Participant中繼承單播定位器,也可以通過該配置項配置不同的定位器。配置後會覆蓋Participant的默認配置。
multicastLocator:多播定位器,默認情況下Publisher和Subscriber都不會使用多播定位器,但是當有很多publisher/subscriber ,它們之間通過單播是每次都複製一份再發送,這樣就降低了publisher端的效率,所以此時採用多播能夠有效降低網絡使用率。配置後會覆蓋Participant的默認配置。
eProsima Fast RTPS 中的locator(定位器)就是網絡端點。
Locator定義:
class RTPS_DllAPI Locator_t
{
public:
int32_t kind; // 協議,LOCATOR_KIND_UDPv4/LOCATOR_KIND_UDPv6
uint32_t port;
octet address[16]; // IP address
}