dbus通信和接口介紹


https://www.cnblogs.com/muxue/archive/2012/12/02/2798876.html 

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 建立、注冊名稱和關閉連接

DBusConnection  *dbus_bus_get  (DBusBusType  type,  DBusError   *error)             /*  建立和總線的連接  */  
  
int  dbus_bus_request_name  (DBusConnection   *connection,  
                             const char         *name,  
                             unsigned int        flags,  
                             DBusError        *error)                                   /*  注冊連接名稱      */  
  
void  dbus_connection_close  (DBusConnection  *connection)                           /*  關閉連接          */ 

2.3 信號發送與接收

 

2.3.1 信號發送

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

    進程發出一個信號前,需要創建一個 DBusMessage 對象來代表信號,然后追加上一些需要發出的參數,就可以發向總線了。發完之后需要釋放消息對象。信號發送的函數聲明如程序清單 2‑2所示。
程序清單2-2  信號發送接口
DBusMessage  *dbus_message_new_signal  (const  char  *path,  
                                       const  char  *iface,  
                                       const  char  *name)                       /*  創建信號類型消息      */  
  
void  dbus_message_iter_init_append  ( DBusMessage     *message,  
                           DBusMessageIter  *iter)                /*  加入參數到信號        */  
  
dbus_bool_t  dbus_connection_send  ( DBusConnection  *connection,  
                                     DBusMessage    *message,  
                                     dbus_uint32_t    *serial)                       /*  發送信號到總線        */  
  
void  dbus_message_unref  (DBusMessage *message)                                 /*  釋放消息              */

  

2.3.2 信號接收

    進程接收信號時,需先告知總線進程感興趣的消息,然后等待接收消息。信號接收函數聲明如程序清單 2‑3所示。
程序清單 2-3 信號接收接口
void  dbus_bus_add_match  ( DBusConnection  *connection,  
                            const char        *rule,  
                            DBusError       *error)                                 /*  告知總線感興趣的消息   */  
  
DBusMessage  *dbus_connection_pop_message  ( DBusConnection  *connection)         /*  接收消息               */  
  
dbus_bool_t  dbus_message_is_signal  (DBusMessage  *message,  
                                      const char      *iface,  
                                      const char     *signal_name)                    /*  判斷消息是否為信號     */

2.4 函數調用和提供函數調用

2.4.1 函數調用

     調用一個遠程函數與發送一個信號原理類似,需要先創建一個消息(DBusMessage),然后通過注冊在 DBUS上的名稱指定發送的對象。然后追加相應的參數,調用方法分為兩種,一種是阻塞式的,另一種為異步調用。異步調用的時候會得到一個“DBusMessage *” 類型的返回消息,從這個返回消息中可以獲取一些返回的參數。

    函數調用的函數聲明如程序清單 2‑4所示。
程序清單 2-4 函數調用接口
DBusMessage  *dbus_message_new_method_call  (const char  *destination,  
                                             const char  *path,  
                                             const char  *iface,  
                                             const char  *method)                    /*  創建一個函數調用消息    */  
  
void  dbus_message_iter_init_append  (DBusMessage     *message,  
                          DBusMessageIter  *iter)                     /*  為消息添加參數           */  
  
dbus_bool_t  dbus_connection_send_with_reply  (DBusConnection   *connection,  
                                               DBusMessage      *message,   
                                               DBusPendingCall  **pending_return,  
                                               int            timeout_milliseconds)       /*  發送消息                */  
  
void  dbus_pending_call_block  (DBusPendingCall  *pending)                           /*  阻塞等待返回值           */  
  
DBusMessage  *dbus_pending_call_steal_reply  (DBusPendingCall  *pending)             /*  獲得返回消息            */  
    
dbus_bool_t  dbus_message_iter_init  (DBusMessage     *message,  
                          DBusMessageIter  *iter)                     /*  獲取參數                */

  

2.4.2 接收函數調用

    提供遠程函數調用,首先需告知總線進程感興趣的消息,其次從總線獲取消息並判定消息是方法調用。然后從消息中獲取參數進行函數執行,最后創建返回消息,並發送消息至總線,由總線轉發至調用的進程。函數聲明如程序清單 2‑5所示。
程序清單 2-5 接收函數調用接口
void  dbus_bus_add_match  ( DBusConnection  *connection,  
                            const char        *rule,  
                            DBusError       *error)                                   /*  請求獲取調用消息       */  
   
DBusMessage  *dbus_connection_pop_message  ( DBusConnection  *connection)           /*  從總線獲取消息         */  
  
dbus_bool_t  dbus_message_is_method_call (DBusMessage  *message,  
                                          const char     *iface,  
                                          const char     *method)                       /*  判定消息是方法調用     */  
    
dbus_bool_t  dbus_message_iter_init  (DBusMessage     *message,  
                          DBusMessageIter  *iter)                    /*  獲取參數               */  
  
DBusMessage  *dbus_message_new_method_return  (DBusMessage *method_call)            /*  創建返回消息           */  
  
void  dbus_message_iter_init_append  ( DBusMessage     *message,  
                           DBusMessageIter  *iter)                   /*  在消息中填入參數       */  
  
dbus_bool_t  dbus_connection_send  ( DBusConnection   *connection,  
                                     DBusMessage     *message,  
                                     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對其中 的resource name 包括 topic name 等 進行保護,使得name管理更加結構化,更多體現在對於代碼重用性的提高)

 

Computation graph: Computation graph是一個p2p的網絡結構。Node之間的鏈接關系的拓撲結構為 mesh(網狀)

 

Node:node是一個處理計算的進程。(操作系統中的一個進程,因為要占用端口號進行通訊,多機分布式系統中還要標明ip,詳見后面的 uri)Node在graph中使用topic service和parameter相互通訊,協調工作。在不同的系統設計中,node承載的功能粒度也 不一樣。比如大部分的設備驅動都是單獨占用一個node,為了提高代碼復用率經常會有細粒度的划分,而在很多視覺處理的系統避免使用多節點結構,傳遞圖像 數據會造成系統資源浪費(nodelet的應用)。Node的使用極大的增加了系統模塊化和代碼封裝的程度,也給系統帶來了一些錯誤容忍的能力。

 

Master node: master node 給ros系統中其他節點提供命名與注冊的服務。它跟蹤節點中的publisher與 subscriber,service 的server與client,記錄其他節點的位置(uri標明,host與port),並將這些信息通知給需要 建立鏈接的節點。(從分布式系統的角度分析,ros這樣p2p網絡中集中式管理peer信息,也為仿真環境提供虛擬時鍾 /clock 重要:ROS多機系統中,ros的全局時鍾僅是本機的系統時鍾,多機需要同步系統時間的工具來實現ros內時鍾同步,一般是ntp,pr2應用 linux中的chrony進行基於ntp的系統時間同步)

 

Message: node之間通訊規定的統一數據格式,ros內部有數據格式規定語言來定義,然后由相應語言的client library中的message_generation 組件生成目標代碼。提供統一的串行化/解串行化 方法。

 

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

 

Service: service 用於處理ros通訊中的同步通訊,采用server/client 語義。每個service type擁 有 request 與 response兩部分,對於service中的 server,ros不會檢查重名(name conflict),只有最后 注冊的server會生效,與client建立連接。

 

Parameter: parameter 可以看作為ros系統運行時中定義的全局變量,而master node 中有 parameter server 來維護這些變量。而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算法編碼的二進制字符串。

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

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

2》 請求與應答:

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

  1. 連接模式:

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

在之前對於節點與計算圖的介紹中,節點在master node處注冊 自己在publish /subscribe topic。通過 registerPublisher()和 registerSubscriber() 。節點在master處注冊完自己的 subscribe/publish topic后,master都會返回一個成功的應答,其中包含所有publisher節點的URI,然 后 subscriber去與相應的publisher建立連接傳輸 topic 相關信息(topic name,樹蕨類型,傳輸類型等)。有任何新的 節點去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開始 傳輸具體的數據。

service

 

而對稱的過程是 注銷(unregisteration), publisher 通過遠程調用unregisterPublisher(), 然 后subscriber通過unregisterSubscriber()注銷,然而,是否關閉publisher與 subscriber間的數據流傳 輸取決於節點本身。

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

subscribe_publisher

 

  • 數據流

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

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

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

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

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

Service client 選項:persistent 設置為1,則service的鏈接會一直開放給多個 service request

(下面是一些理解:

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM