1. 程式人生 > 其它 >dbus通訊與介面介紹

dbus通訊與介面介紹

DBUS是一種高階的程序間通訊機制。DBUS支援程序間一對一和多對多的對等通訊,在多對多的通訊時,需要後臺程序的角色去分轉訊息,當一個程序發訊息給另外一個程序時,先發訊息到後臺程序,再通過後臺程序將資訊轉發到目的程序。DBUS後臺程序充當著一個路由器的角色。

DBUS中主要概念為匯流排,連線到匯流排的程序可通過匯流排接收或傳遞訊息,匯流排收到訊息時,根據不同的訊息型別進行不同的處理。DBUS中訊息分為四類:

1. Methodcall訊息:將觸發一個函式呼叫;

2. Methodreturn訊息:觸發函式呼叫返回的結果;

3. Error訊息:觸發的函式呼叫返回一個異常;

4. Signal訊息:通知,可以看作為事件訊息。

1.2 DBUS應用場景

根據DBUS訊息型別可知,DBUS提供一種高效的程序間通訊機制,主要用於程序間函式呼叫以及程序間訊號廣播。

1 . 函式呼叫

DBUS可以實現程序間函式呼叫,程序A傳送函式呼叫的請求(Methodcall訊息),經過匯流排轉發至程序B。程序B將應答函式返回值(Method return訊息)或者錯誤訊息(Error訊息)。

2 . 訊息廣播

程序間訊息廣播(Signal訊息)不需要響應,接收方需要向匯流排註冊感興趣的訊息型別,當匯流排接收到“Signal訊息”型別的訊息時,會將訊息轉發至希望接收的程序。

1.3 DBUS通訊特點

DBUS是一種低延遲、低開銷、高可用性的程序間通訊機制。其協議是二進位制的,避免序列化的過程,通訊效率較高。DUBUS可以提供一些更高層的功能:

1. 結構化的名字空間;

2. 獨立於架構的資料格式;

3. 支援訊息中的大部分通用資料元素;

4. 帶有異常處理的通用遠端呼叫介面;

5. 支援廣播型別的通訊。

2. 技術實現

2.1 實現原理

DBUS是一種高階的IPC通訊機制,通訊流程如圖 2‑1所示。在DBUS通訊過程中,存在一個後臺程序(BUS Daemon Process)。後臺程序和普通程序間資訊互動是通過域套接字進行通訊。

圖 2-1 DBUS通訊原理

如圖 2‑1所示,程序1(Process1)需先連線到匯流排(dbus_bus_get),其次構造訊息(dbus_message_new_signal),然後傳送訊息(dbus_connection_send)到後臺程序。後臺程序接收訊息,然後根據訊息型別對訊息進行不同處理(bus_dispatch_matches)。

程序2(Process2)接收訊息前需要連線到匯流排,並告知匯流排自己希望得到的訊息型別(dbus_bus_add_match),然後等待接收訊息(dbus_connection_pop_message)。程序2(Process2)收到匯流排轉發的訊息時會根據訊息型別,做不同的處理(若是訊號型別則不需要傳送返回值給匯流排)。

2.2 連線到匯流排

程序間通訊前,需要連線到匯流排。呼叫dbus_bus_get函式連線程序到匯流排,建立程序和匯流排之間的連線(DBusConnection)。建立連線後,需要為這個連線註冊名稱,方便後面對這個連線進行操作,呼叫dbus_bus_request_name函式對連線進行註冊名稱。

建立連線和註冊名稱是在程式開始時執行,程式結束時,呼叫dbus_connection_close函式關閉一個連線。函式介面宣告如程式清單 2‑1所示。

程式清單 2-1 建立、註冊名稱和關閉連線

[plain]view plaincopy
  1. DBusConnection*dbus_bus_get(DBusBusTypetype,DBusError*error)/*建立和匯流排的連線*/
  2. intdbus_bus_request_name(DBusConnection*connection,
  3. constchar*name,
  4. unsignedintflags,
  5. DBusError*error)/*註冊連線名稱*/
  6. voiddbus_connection_close(DBusConnection*connection)/*關閉連線*/

2.3 訊號傳送與接收

2.3.1 訊號傳送

DBUS中訊號是一種廣播的訊息,當發出一個訊號,所有連線到 DBUS 總線上並註冊了接受對應訊號的程序,都會收到該訊號。

程序發出一個訊號前,需要建立一個 DBusMessage 物件來代表訊號,然後追加上一些需要發出的引數,就可以發向匯流排了。發完之後需要釋放訊息物件。訊號傳送的函式宣告如程式清單 2‑2所示。 程式清單2-2 訊號傳送介面 [cpp]view plaincopy
  1. DBusMessage*dbus_message_new_signal(constchar*path,
  2. constchar*iface,
  3. constchar*name)/*建立訊號型別訊息*/
  4. voiddbus_message_iter_init_append(DBusMessage*message,
  5. DBusMessageIter*iter)/*加入引數到訊號*/
  6. dbus_bool_tdbus_connection_send(DBusConnection*connection,
  7. DBusMessage*message,
  8. dbus_uint32_t*serial)/*傳送訊號到匯流排*/
  9. voiddbus_message_unref(DBusMessage*message)/*釋放訊息*/

2.3.2 訊號接收

程序接收訊號時,需先告知匯流排程序感興趣的訊息,然後等待接收訊息。訊號接收函式宣告如程式清單 2‑3所示。 程式清單 2-3 訊號接收介面 [cpp]view plaincopy
  1. voiddbus_bus_add_match(DBusConnection*connection,
  2. constchar*rule,
  3. DBusError*error)/*告知匯流排感興趣的訊息*/
  4. DBusMessage*dbus_connection_pop_message(DBusConnection*connection)/*接收訊息*/
  5. dbus_bool_tdbus_message_is_signal(DBusMessage*message,
  6. constchar*iface,
  7. constchar*signal_name)/*判斷訊息是否為訊號*/

2.4 函式呼叫和提供函式呼叫

2.4.1 函式呼叫

呼叫一個遠端函式與傳送一個訊號原理類似,需要先建立一個訊息(DBusMessage),然後通過註冊在 DBUS上的名稱指定傳送的物件。然後追加相應的引數,呼叫方法分為兩種,一種是阻塞式的,另一種為非同步呼叫。非同步呼叫的時候會得到一個“DBusMessage *” 型別的返回訊息,從這個返回訊息中可以獲取一些返回的引數。

函式呼叫的函式宣告如程式清單 2‑4所示。 程式清單 2-4 函式呼叫介面 [cpp]view plaincopy
  1. DBusMessage*dbus_message_new_method_call(constchar*destination,
  2. constchar*path,
  3. constchar*iface,
  4. constchar*method)/*建立一個函式呼叫訊息*/
  5. voiddbus_message_iter_init_append(DBusMessage*message,
  6. DBusMessageIter*iter)/*為訊息新增引數*/
  7. dbus_bool_tdbus_connection_send_with_reply(DBusConnection*connection,
  8. DBusMessage*message,
  9. DBusPendingCall**pending_return,
  10. inttimeout_milliseconds)/*傳送訊息*/
  11. voiddbus_pending_call_block(DBusPendingCall*pending)/*阻塞等待返回值*/
  12. DBusMessage*dbus_pending_call_steal_reply(DBusPendingCall*pending)/*獲得返回訊息*/
  13. dbus_bool_tdbus_message_iter_init(DBusMessage*message,
  14. DBusMessageIter*iter)/*獲取引數*/

2.4.2 接收函式呼叫

提供遠端函式呼叫,首先需告知匯流排程序感興趣的訊息,其次從匯流排獲取訊息並判定訊息是方法呼叫。然後從訊息中獲取引數進行函式執行,最後建立返回訊息,併發送訊息至匯流排,由匯流排轉發至呼叫的程序。函式宣告如程式清單 2‑5所示。 程式清單 2-5 接收函式呼叫介面 [cpp]view plaincopy
  1. voiddbus_bus_add_match(DBusConnection*connection,
  2. constchar*rule,
  3. DBusError*error)/*請求獲取呼叫訊息*/
  4. DBusMessage*dbus_connection_pop_message(DBusConnection*connection)/*從匯流排獲取訊息*/
  5. dbus_bool_tdbus_message_is_method_call(DBusMessage*message,
  6. constchar*iface,
  7. constchar*method)/*判定訊息是方法呼叫*/
  8. dbus_bool_tdbus_message_iter_init(DBusMessage*message,
  9. DBusMessageIter*iter)/*獲取引數*/
  10. DBusMessage*dbus_message_new_method_return(DBusMessage*method_call)/*建立返回訊息*/
  11. voiddbus_message_iter_init_append(DBusMessage*message,
  12. DBusMessageIter*iter)/*在訊息中填入引數*/
  13. dbus_bool_tdbus_connection_send(DBusConnection*connection,
  14. DBusMessage*message,
  15. dbus_uint32_t*serial)/*傳送返回訊息*/

3. 小結

DBUS是一種高效、易用的程序間通訊方式。本文件介紹了DBUS的通訊原理,以訊號收發和方法呼叫為框架,介紹了DBUS中常用的函式介面。

DBus分為兩種型別:system bus(系統匯流排),用於系統(Linux)和使用者程式之間進行通訊和訊息的傳遞;session bus(回話匯流排),用於桌面(GNOME, KDE等)使用者程式之間進行通訊。

上節補存:

Name:圖模型中的Name在ROS的封裝體系中非常重要,所有的resource(從node到topic到service和 parameter等)都是在某個namespace中用特定的Name進行了定義。一般來說,resource可以在自己的namespace中創 建新的resource,訪問和使用已有的resource。連結可以建立在不同的資源之間,namespace保證了不同resource間的Name 不會衝突,也封裝了resource內部。

(可以參考C++中namespace的概念。因為ROS分散式系統的設計思路,對與不同主機上,不同功能區塊,都可以用namespace對其中 的resourcename包括topicname等進行保護,使得name管理更加結構化,更多體現在對於程式碼重用性的提高)

Computationgraph:Computationgraph是一個p2p的網路結構。Node之間的連結關係的拓撲結構為mesh(網狀)

Node:node是一個處理計算的程序。(作業系統中的一個程序,因為要佔用埠號進行通訊,多機分散式系統中還要標明ip,詳見後面的 uri)Node在graph中使用topicservice和parameter相互通訊,協調工作。在不同的系統設計中,node承載的功能粒度也 不一樣。比如大部分的裝置驅動都是單獨佔用一個node,為了提高程式碼複用率經常會有細粒度的劃分,而在很多視覺處理的系統避免使用多節點結構,傳遞影象 資料會造成系統資源浪費(nodelet的應用)。Node的使用極大的增加了系統模組化和程式碼封裝的程度,也給系統帶來了一些錯誤容忍的能力。

Masternode:masternode給ros系統中其他節點提供命名與註冊的服務。它跟蹤節點中的publisher與 subscriber,service的server與client,記錄其他節點的位置(uri標明,host與port),並將這些資訊通知給需要 建立連結的節點。(從分散式系統的角度分析,ros這樣p2p網路中集中式管理peer資訊,也為模擬環境提供虛擬時鐘/clock 重要:ROS多機系統中,ros的全域性時鐘僅是本機的系統時鐘,多機需要同步系統時間的工具來實現ros內時鐘同步,一般是ntp,pr2應用 linux中的chrony進行基於ntp的系統時間同步)

Message:node之間通訊規定的統一資料格式,ros內部有資料格式規定語言來定義,然後由相應語言的clientlibrary中的message_generation元件生成目的碼。提供統一的序列化/解序列化方法。

Topic:ros中廣為使用的是非同步的publish-subscribe通訊模式。這種方式將資訊的產生和使用雙方解耦。一般來說,節點沒 有通訊對方那邊的資訊。Node從需要的topic那取得訊息,topic可以有多個subscriber與publisher。Topic一般 用於單向,訊息流通訊。Node需要同步通訊交換資訊時一般使用service。Topic一般擁有很強的型別定義:一種型別的topic只能接受/ 傳送特定資料型別(messagetype)的message。Publisher沒有被要求型別一致性,但是接受時subscriber會檢查型別 的md5,進而報錯。

Service:service用於處理ros通訊中的同步通訊,採用server/client語義。每個servicetype擁 有request與response兩部分,對於service中的server,ros不會檢查重名(nameconflict),只有最後 註冊的server會生效,與client建立連線。

Parameter:parameter可以看作為ros系統執行時中定義的全域性變數,而masternode中有 parameterserver來維護這些變數。而namespace的存在使得parameter擁有了非常清晰的層次劃分,避免了重名,而且使 得parameter訪問可以單獨訪問也可以樹狀訪問(層層解析namespace)

URI:定位node在分散式系統中的位置,格式為:protocol://host:port,protocol一般為http或者rosrpc,host為hostname或者ip地址,port則為埠號。

TCPROS:基於tcp協議的ros應用層資料協議,用於解析topic與service的二進位制資料流。

  • 遠端過程呼叫(RPC)

ROS通訊中,節點通過遠端過程呼叫來實現建立連線,傳輸資料。ROS中遠端過程呼叫採用XML-RPC實現。遠端呼叫負責管理節點對計算圖 中資訊的獲取與更改,還有一些全域性的設定。RPC不直接支援資料的流傳輸(通過TCPROS與UDPROS支援)。XML-RPC優勢在於支援它的語 言型別很多,而XML本身的文字屬性導致方便人除錯。XML-RPC被封裝在http協議中傳輸,XML-RPC呼叫時無狀態的 (stateless),沒有狀態資訊需要追蹤,簡化了控制邏輯。

1》資料型別:

在XML-RPC中,方法引數和其返回值被封裝在value的實體中,value有以下幾種固定的型別可以選擇。(xml中體現為value的子標籤)

string為ascii碼的字串,為value的預設格式。不合法的字元只有&和<,s&<;表示。

int或者i4是32位的有符號整型,十進位制表示中,字首-號則為負數。

boolean只有兩種取值,0和1,用於表示布林型別。

double實數型別,字首-號表示負數。

dataTime.iso8601日期時間,用iso-8601格式表示。

base64用base64演算法編碼的二進位制字串。

arrayvalues組成的表,在data實體下一層

Struct又稱為map表示關係的集合,每一個struct實體是由一個name與value的鍵值對組成。

2》請求與應答:

遠端過程呼叫由兩個階段組成:請求(request)與應答(response)。一個呼叫者將方法呼叫請求發給被呼叫者,然後被呼叫者返回呼叫是否成功與相應返回值。

  1. 連線模式:

下面將簡述節點與其他節點如何進行連線,最後初始化一個topicdata的流傳輸,service的實現有些許不同。

在之前對於節點與計算圖的介紹中,節點在masternode處註冊自己在publish/subscribetopic。通過 registerPublisher()和registerSubscriber()。節點在master處註冊完自己的 subscribe/publishtopic後,master都會返回一個成功的應答,其中包含所有publisher節點的URI,然 後subscriber去與相應的publisher建立連線傳輸topic相關資訊(topicname,樹蕨型別,傳輸型別等)。有任何新的 節點去publisher一個topic並且在master處已經完成自己的註冊時,master會給所有subscribe這個topic的節點發 出一個publisherUpdate()請求,裡面包含所有可用的publisher的URI,topic訊息的資料由TCPROS協議傳輸。

簡單來說就是各個節點在master處註冊資訊,master發現有節點subscribe/publish相同的topic時,將 publisher的資訊通過RPC分發給各個subscriber,subscriber與publisher建立第一次連線,傳輸topic信 息,然後再根據publisher返回的topic資訊,建立第二次連線,publisher開始傳輸具體的資料。

而對稱的過程是登出(unregisteration),publisher通過遠端呼叫unregisterPublisher(),然 後subscriber通過unregisterSubscriber()登出,然而,是否關閉publisher與subscriber間的資料流傳 輸取決於節點本身。

Service的工作原理與topic有些許不同,同一個service能被多個節點註冊,但是隻有最後一個能被其他節點接受。一個節點呼叫 service時,通過lookupService()遠端呼叫在master處查詢相應service的URI。然後它將通過一個request消 息呼叫service的提供者,如果成功了,service提供者將返回一個相應的response訊息,失敗了返回相應錯誤訊息,(所有的訊息傳輸默 認都是通過TCPROS協議。)

  • 資料流

XML-RPC為我們提供了方便整潔的遠端呼叫協議,但是它的冗長與以文字為中心的編碼格式使得它不適合高頻寬,低延遲的資料傳輸任務。ROS定義 了自己的二進位制資料流傳輸協議,減少了冗餘的協議增加從而增加頻寬,協議設計使得資料幾乎不需要解析(相對於rpc),從而減少延遲。詳細的TCPROS 協議內容可以在wiki.ros.org/ROS/TCPROS找到。

(現在ROS中也有實現的UDPROS協議,可以通過TransportHints資料結構指定下層傳輸協議,甚至通過繼承Trasport類自己實現本機的程序間通訊方法)。

下面撿一些TCPROS中的重點說一下:

Md5:TCPROS為了保證兩邊傳輸資料型別一致,會在協議頭中給出topicname的md5hash演算法處理過的值,而每次你生成新的msg時,md5的值都會因為你內部資料型別的變動而改變,這樣就避免了新msg與舊msg傳輸型別不一致的問題。

Subscriber選項tcp_nodelay:如果是“1”則給socket設定TCP_NODELAY選項,降低延遲,可能會降低傳輸效率。

Serviceclient選項:persistent設定為1,則service的連結會一直開放給多個servicerequest

(下面是一些理解:

  • TCPROS的協議頭佔的位元組數比較固定,所以傳輸一幀中只傳輸有效位幾個位元組是非常不划算的,很多情況下可以附上std_msgs/header,seq可以檢測丟包,stamp可以檢測訊息實時性,frame_id很多情況下必備。
  • Roscpp對資料流的控制api比rospy要豐富很多,比如roscpp中有對callbackqueue的操作,多執行緒回撥函式的支援等,對資料傳輸要求比較高的節點還是老老實實用cpp吧
  • 在一些對延遲要求比較高而又有一些無線通訊等高延遲傳輸介質存在的應用中,可以考慮用低延遲的方式互聯,比如xbee模組代替wifi(自己寫一些與其他節點的bridge),或者udpros代替tcpros,並且避免tcpros的協議頭佔用過多頻寬)