ROSチュートリアル第二回 -- ROSの通信モデル

  • 38
    いいね
  • 0
    コメント
この記事は最終更新日から1年以上が経過しています。

ROSにおけるプロセス間通信の重要性

ROSで推薦されている開発スタイルは以下の様なものである。
1. とにかくプロセス(ROSではこれをNodeと呼ぶ)を立ち上げまくる
2. プロセス間のデータはネットワーク経由で通信する

ros_flow_chart.png

例えば、上の図のようなシステム構成を考えてみよう
これは、ステレオ視によって外部の三次元の環境を認識し、それを回避するような動作計画を行うロボットシステムである。
このようなシステムをROSで実装する場合、角丸で囲われているものがそれぞれ独立したプロセスとして実行される。
そして、矢印で繋がれた部分がプロセス間通信である。

このようなスタイルで開発されたシステムにおける利点は、
1. 特に何も考えなくてもマルチコアの恩恵を受けられる
2. システムの一部(あるプロセス)がバグで死んでも、システム全体は死なない
3. 複数マシンでの分散システムに自然と拡張できる
4. コードの再利用性が高くなる
といったものがあげられる。

このような開発スタイルを実現するために、ROSはネットワーク経由でメッセージをやりとりできる通信ライブラリを各種プログラミング言語(C++. Python, etc)に提供している。

よくある質問に、「一つの計算機でわざわざ複数プロセスを立てて、間をTCP/IPで通信するのは無駄じゃないか?」というのがあるが、
自分でマルチスレッドのコードをメンテナンスすることを考えると、プロセス単位でモジュール化したほうが得策であろう(マルチスレッドムズカシイヨ)。

また、通信に関しては「なぜ共有メモリを使わないのか?」といった質問もよく見るが、「localhostでの通信は結構速いっぽいよ」というのがROS開発者の一貫した答えである。
たしかに、TCP/IPではリアルタイム性の要求が厳しい部分で不都合が発生するが、ROSではUDPベースの通信も実装されているので、それを利用することができる。

また、nodeletというスレッド単位の並列処理の枠組みも用意されているので、通信負荷を減らす必要があればこちらを利用することもできる。

ROSの基本的な通信モデル

ROSには大きく分けて二つの通信モデルがある。
1. pub/sub型のメッセージング
2. RPC(リモート関数呼び出し)

topicを利用したpub/sub型のメッセージング

ROSのほとんどのプロセス間通信はtopicを通じたpub/subによって行われる。

Nodeはtopicに対してデータを書き込む(これをpublishと呼ぶ)、およびデータを読み込む(これをsubscribeと呼ぶ)という二つの操作をする。

一つのtopicに対し、複数のNodeがpublish/subscribeを実行する。
あるNodeがあるtopicに対してデータをpublishすると、そのtopicをsubscribeしている全Nodeにデータが渡される。

topic.png

図のような例だと、Node Aがtopic Hogeにデータをpublishすると、HogeをsubscribeしているNode、つまりCとDに対してデータが渡される。

topicによる通信は、基本的に非同期で行われる。つまり、topicをpublishするNodeは、勝手に自分のタイミングでデータをpublishする。topicをsubscribeしているNodeは、データがきたら特定の処理を実行するcallbackを呼び出す。

pub/sub型の通信モデルだと、publishしている側はどのNodeにデータが行ったのか、subscribeしている側からはどのNodeからデータが来たのかを基本的には知ることが出来ない(気にする必要がない)。

serviceを利用したリモート関数呼び出し

topicを利用した通信だと、publishしている側から見るとデータが他のNodeに伝わったのかを知ることは基本的にできない。

これに対し、データの受け渡しを保証したい場合にserviceを用いる。

serviceは典型的なリモート関数呼び出し(Remote Procedure Call, RPC)と呼べる。

service.png

serviceによる通信では、Nodeは呼び出し側(Caller)とサーバ側(Callee)に分けられる。

サーバNodeはserviceを宣言することで外部からそのserviceを呼び出すことができるようになる(図だとHogeというserviceを提供している)。

serviceは呼び出すための引数と戻り値の二つによって型が決定される。クライアントNode(Caller)は、serviceに対して引数を与え戻り値を得る。

このリモート関数呼び出しの間、基本的にクライアントは同期的に処理がなされる。つまりクライアントはserviceを呼び出して戻り値を得るまでの間処理がブロックされる。

また、リモート関数呼び出しのため、クライアントは引数がサーバに渡ったこと、サーバから戻り値が返ってくることを保証できる。何かしらかの問題によって、例えばサーバが存在しないなど、通信に問題が会った場合はエラーとして報告される。

serviceでの通信は一対一の通信に限られる。つまり、serviceは一つのサーバNodeによってのみ提供されているということである。