老王說ROS

1.   ROS結構

今天扯一下ros吧。拋開ros生態圈不講,單表ros軟件。(1)作爲通信中間件的ros是怎麼玩的。ros軟件提供了一個通信中間件來實現分佈式系統的構建。那麼既然是通信中間件,ros的通信機制是怎麼實現的呢?單從文檔上看,ros提供了訂閱發佈的通信機制,也就是有個發佈者發佈一個topic,訂閱者訂閱這個topic,當有發佈者就某個topic 發佈message的時候,訂閱這個topic的訂閱者就可以收到這個message 了。當訂閱者收到這個消息的時候,通過一個自定義的回調函數來處理消息,來完成自己的業務。那麼這個過程是具體是如何實現的呢?那就需要看源代碼了。看源代碼的過程中你會很容易罵人的,這個要忍住,但千萬不要抱着看佛祖的心態看,從通信中間件或者說軟件設計的角度來講,ros絕非一流水平。話歸正傳,據我有限的認知,ros是這樣實現訂閱發佈機制的。第一個概念,就是交易,訂閱者和發佈者是如何達成交易的呢?當然是在市場上呀,市場在哪裏,當然就是master這個程序了,沒有master就沒有交易。那麼第一個問題來了,訂閱者和發佈者怎麼去的市場呢?從外表看不出來呀,沒錯,外表看不出來,你在源碼裏搜master-uri就找到了,當你在環境變量裏搜master-uri也能找到。這個常量包含了ip和port的信息。有了這個信息你就會找到master 了。有了地址下一步是怎麼去,肯定是socket了,並且不是普通的socket而是經過打扮的xmlrpcclient,既然做買賣那就得像個買賣人,要不然master那裏的xmlrpcserver不搭理你。於是你打扮好了,到了master,你說我要adviser 一個topic,xmlrpcserver是個好公僕,有求必應,來做個登記吧,ip,port,upd or tcp,topic,登記完賣家就回家了,建立了一個tcp or udp的server,守在電話旁等買家。買家也是這麼去的市場,到那先登記自己的信息,然後就問都是誰賣閨女,查到地址後,回家就去建立與賣家的鏈接,閨女就源源不斷的送了過來。說完做買賣,再說倉庫,也就是消息緩衝區模型,送來這麼多閨女我該怎麼放,怎麼用呢?說實話,我還沒看到,但無外乎如下幾種,先進先出隊列,後進先出隊列,後進先出的環形隊列,再有就是沒有隊列只能放一個只要最新的;ros是那一種呢?記住這個倉庫在發送和接收端都存在,要併發不阻塞實現異步併發就得這麼處理,收支兩條線。加個評論:ros的這種做法,既不是完全得data centric,也不是完全的event driven,因爲它兼有兩者得特性。所以就缺少了dds的qos,隨便交易,是個閨女都有人賣,至少也要看個年齡吧。再有就是不能把event driven的實時性徹底發揮出來。這個不怪它,機器人的信息交互本來就複雜,想要易用只能這樣了,但無論如何也比傻乎乎的player/stage強,也比韓國人民在opros裏設計的美好實現靠笨拙的cobra更好用。

2.   ROS消息

(2)ros裏的消息

通信中間件的目的有很多,其中一個就是將易錯的部分封裝,讓使用者避免出錯,socket編程是容易出錯的,容易出錯的地方也有很多,其中一個就是將帶結構的數據序列化爲無結構的字節流以及從無結構的字節流裏重構數據結構,這兩個行爲,你可以叫它編碼和解碼,也可以叫打包和解包。

爲什麼說這個易錯呢?很多人不理解,不就是把各種數據類型放到一個字符型數組裏嗎?不就是再從字符型數組弄出各種數據類型嗎?小心點,就行了。那我只能告訴你,你用這樣的態度能夠解決問題靠的是幸運,而不是技術。這裏我就不闡述容易錯的地方在哪裏了。直接說ros是怎麼幹的吧。那就是抽象,用中性語言定義消息,讓交互雙方在文本空間達成一致,記住是文本空間,僅供人類閱讀,協議結果就是.msg和.srv那些文件(我們常用的裸奔做法是,寫個文本文件,畫個兩行的表格,第一行每個格子裏寫個名字叫字段名,對應的第二行標示數據類型,如果穿上內褲的話,就加點註釋解釋每一個字段的含義,不過這跟裸奔也差不多,我看過太多這樣的美其名曰的應用層協議了,很多人沒感覺有問題,那我告訴你,那是沒出門,你可能還要辯解說,我和別人通信的好好的呀,那我也只能說,那是你倆都在澡堂子,一起出門試試)。好了,有了這個協議,這個穿着西裝革履談做出的協議,ros會幫你脫衣服的,第一步,利用代碼生成工具幫你生成在代碼空間一致的依賴編程語言的消息定義頭文件,如.h文件,好了,大家放心的編碼吧;第二步,ros在運行期間幫你解碼編碼,在運行期間達成一致。最後需要解釋的是有兩點:

(1)ros肯定是生成了自定義消息的cpp文件的,要不然誰來打包和解析呀,至於什麼時候自動生成而又刪除了的,這個讀makefile什麼的應該不難發現,當然我還沒發現。

(2)ros在實際傳送的每個消息裏都包含了一個消息頭,這個頭至少包含了一個包標示字段、一個校驗碼段、一個包大小段。這是裸奔的內容,想在其他不支持ros的系統上做rosnode,必須要了解這個,當然還要理解美其名曰naming service的閨女交易。

3.   ROS Node

(3)node

在一個機器人軟件中,有各種各樣的node。別搶答,各種各樣不是指localization,slam ,action這些功能的多種多樣,而是指有消息的生產者,消費者,以及消費又生產的深加工者。先插一句吧,這次討論涉及到這樣的一個問題,在用ros構建一個機器人軟件時,僅僅從功能劃分的角度來構建node是否合適。是否還需要考慮讓消息的流動更順暢,讓node更簡潔高效呢?先說node是怎麼跑的吧。當你建立一個node後,要麼它按照一個固定的週期運行,要麼它阻塞等待事件發生,由事件驅動運行。無論是那種,node都要幹活,node乾的什麼活呢?callback queue裏的活。這個callback queue裏的callback是哪裏來的呢?常見的是subscriber的callback,當然還有其他的,包括publisher的,service的。那這些callback是什麼時候被調用的呢。那就是spin()或者spinonce()。spin調用在queue 裏所有的availiable的callback,如果沒有availible的,它就阻塞。spinonce,顯然只調用一次,看看有沒有準備好的callback,有就調用,沒有就返回。那什麼是availible的、準備好的呢?對於subscribe,準備好的就是那些有新消息的subscriber的callback。現在如果你明白了上述node的運行機理。你在感到舒服之餘,請準備接收心煩的問題。1)第一個問題就是,如果subscriber沒有收到新消息,那麼它的callback就不會被執行。如果你想每次都運行callback呢?對不起,沒辦法,不是virgin我不娶。

2)第二個問題,callback執行有個timeout值,如果設置不合理,要麼費時,要麼callback被中止。被中止呀,兄弟們,多可怕,萬一是一個重要邏輯的一環呢?今天就聊到這裏,最後送點福利,如果你想實現事件觸發的node就用spin;如果你要固定週期的node,那就用spinonce+sleep,但一定要牢記,在每個週期裏不是所有callback被執行。

4.   ROS Master

(4)master

前三次說了通信中間件、消息、node,這次聊貌似高大上實際很鱉孫的master。

ros吸引人的地方,兩個,一、有各種現成的解決領域問題的軟件包,二、有一個現成的構建分佈併發系統軟件的方案,既通信中間件。

先提一個問題,爲什麼要併發呢?併發的意義在哪呢?請想好了再回答。

在很久很久以前,計算機裏的程序是順序執行的,在很久很久以後計算機裏的程序還是順序執行的。

不同的是以前計算機解決的問題比較簡單,現在有點複雜。人類已經很難用結構化的思維來描述系統,很難用面向過程的語言把系統描述給計算機了。

於是,面向對象思維登場,把世界抽象爲對象以及對象間的交互。這個說法沒錯,但牛叉的說一切皆對象並奉若神明否定過程,這是不對的。因爲,對象從生到死是個過程,對象間交互也是個過程,沒有面向過程的思維也是不行的。

但面向過程思維不能太結構化,特別是解決複雜系統問題的時候,要面向多進程併發,把世界抽象爲過程以及過程間的交互。當然如果你作爲使用者,不關心過程如何實現,也希望更時髦點,那你就叫它面向服務吧。

這個思想在很久很久以前就有,叫做中斷。試想一下,如果你寫了一個主程序,寫了一坨中斷程序,主程序只是阻塞等待,如果合理安排中斷程序的運行,這就是多進程併發嘛,中斷程序有用還幹活你就叫它中斷服務程序嘛。

用併發的多個過程來抽像系統,特別適合用於機器人系統,因爲機器人在人腦子裏比較複雜(當然,也未必真不復雜,但的確也沒那麼複雜)。

說到這裏,答案就有了,併發對於順序執行代碼的計算機來說沒有意義,它不會更快,併發的意義在於用過程以及過程間的交互來抽象系統。

如何抽象出多個過程,建個什麼樣的node跟master無關,但node間如何交互卻跟他有關。可惜,master實在是沒提供太多基礎服務,整個ros的通信機制也沒提供良好的交互機制。

master提供兩種基礎服務,naming service和parameter server。這兩個服務本質上沒有什麼不同,就是註冊和查詢,只不過一個是地址,一個是參數。

這些是遠遠不夠的。動態發現discovery,生存檢測liveness,訪問控制access control,這些基本的都沒有。

於是會有這樣的問題

1)master死了會怎樣?

2)node死了會怎樣?

3)怎麼知道master死了?怎麼知道node死了?

先說第一個master死了,可以重啓嘛,只是重啓之後,以前的node註冊信息會丟失,你無法和以前的node建立交互關係。node只在實例化nodehandler的時候註冊信息。

第二個,node非正常死亡後,master是不知道的,rosnode list的時候,你還會看到它。

第三個問題,答案很簡單。自己想辦法!

當然你也可以邊罵“你個鱉孫”,邊想辦法。

5.   ROS node裏的線程

2015.4.4

ros不能丟,繼續扯。

(5)node裏的線程

先說第一個我不太懂的問題,那就是多線程和多進程怎麼選擇的問題。

在系統分析和設計時是不用考慮這些問題的,但要動手幹了,那就不得不選擇了。可惜,我也沒有很好的建議。我只談談我的看法吧。我不在乎多線程和多進程。

第一,我覺着cpu哪裏不區分他們。第二,我很少操刀寫代碼,哪個寫起來複雜,哪個寫起來簡單我不在乎;第三、是否多佔資源、是否需要在物理內存那跳來跳去影響速度,我也不在乎,我玩的還沒高深到需要考慮那點差異。

不過有人在乎,一種是水平比較高,現實有約束,必須要考慮;一種是很有意識,思想有潔癖覺,覺着必須要在意,還有一種,在意只在書本上。最後一種,爲了方便,急於求成,避開多進程,愛搞多線程,用ros的不乏這樣的小夥伴。今天主要就說這個。

ros說得很明白,它設計時是有意的不讓你看到線程的存在的,是不希望用戶考慮這些事情的。說得樸素點,那就是node裏開用戶自定義的線程是不受鼓勵的。

爲什麼要這樣呢?這樣的思路有沒有問題?有問題又該如何解決呢?

爲什麼要這樣,第一,估計跟我對進程線程的看法差不多,不區分線程進程,想並行處理,就建node;第二,ros還是很用心的爲用戶着想的,不想讓你費勁。

可這樣做是有問題的。一個突出的問題是有時候,一個系統需要好多好多node來實現,切的很細碎,實現起來、用起來好麻煩;當然如果都tm有現成的也無所謂,攢起來就得了唄。可惜,哪有那麼現成,只好硬着頭皮自己做node,設計msg,實現交互。這樣也行,累就累點。

但當你把系統開發的差不多了,突然發現某一個node裏還藏着一個需要並行處理的過程時,就不是累點的問題了。你有點想罵人了,動架構,一個不結實的架構,跟從頭幹有啥區別。天無絕人之路,靈光一現,我弄個線程就得了唄,交互起來還方便,還不用寫msg,還能把多線程得各種交互機制用上,不用只靠一個pub/sub,於是輕車熟路得開始加線程。

哇,我好幸福呀,系統功能實現了,於是你在接下來得日子過上了能不加node就不加node,能不寫msg就不寫msg,能靠全局變量就靠全局變量的的好日子。

我也恭喜你,但同時也提醒一下這樣的壞處。第一個,你在意的,你可能已經給自己埋下了地雷;第二個,你不在意的,你失去了一個鍛鍊系統設計能力的機會。

我只談第一個,爲什麼說有可能是地雷,第一,因爲ros不鼓勵這麼做,它有可能在應付node裏用戶自定義的線程時,考慮不周。第二,用戶自定義的線程與node的線程間如何交互協調時,你只能大膽的去猜,你根本不清楚node在幹嘛。

所以,我的建議是,既然ros了,就嫁雞隨雞嫁狗隨狗,按ros的想法來,人家不是已經提供nodelet了嘛。

最後,說點有用的,在一個node裏除了主線程外,至少有兩個線程,一個好像是pollmanager裏有個類似於select()的線程,負責poll各個socket的讀寫事件;另外一個,我忘了在哪了,功能是監聽、建立新的pub/sub鏈接。

發佈了22 篇原創文章 · 獲贊 131 · 訪問量 22萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章