Wireless・のおと

MQTT のはなし

ブログ
規格 技術解説

「モノのインターネット」こと IoT(Internet of Things) が流行語になってからもう数年経ちました。このブログではなかば意図的に IoT に対して距離を取ってきたのですが、そうも言っていられなくなってきたようです。今回は IoT プロトコル標準候補のひとつ、MQTT に関するおはなしです。

MQTT とは

MQTT はブローカー型のメッセージ伝達・再分配プロトコルで、その起源は意外に古く 1999 年に IBM と Eurotech 社で開発されたものと言われます。IBM の MQ プロトコルを母体として開発されたため MQ Telemetry Transport の略で MQTT と名付けられたとも言われていますが、MQTT という名前自体には大した意味はありません。そもそも MQTT にはメッセージを溜める(Queue)機能はなく、MQTT という名前は「名が体を現わさない」妙なことになっています。

古典的なクライアント・サーバ型システムの場合、データの発信者と受信者は直接接続していました。これは WEB ブラウザから Amazon だとか Google に接続する場合には問題になりませんが、センサネットワークのように数百数千ものデータソースが存在する場合は問題です。「2階第3会議室の南側の窓の開閉センサーを管理しているノードの IP アドレスは何だ?」のように、クライアントはセンサーノードの数だけ IP アドレスを検出管理しなければなりません。一方、サーバ側では接続数が多くなると処理や消費メモリの負荷が上がり、「何が起きたとき誰に伝えれば良いか?」というデータ配布条件の設定も複雑になる傾向がありました。

クライアント・サーバモデル
サーバ数が少ない場合は単純

 

クライアント・サーバモデル
サーバ台数が増えると級数的に複雑度が増す

 

一方、「ブローカー型」というのは、データの発信者(Publisher / パブリッシャー)は再分配サーバー(Broker ブローカー)にデータを送り付けるだけでよく、そのデータをどんな条件で誰に再配布するのかはブローカーが処理する形態のシステムです。データの受け取り手はサブスクライバー(Subscriber)と呼ばれます。厄介事をブローカーがまとめて引き受ける中央集権型のシステムですが、ブローカーをクラウドサーバとして解釈するとわかりやすいかと思います。

ブローカーモデル
接続組み合わせの複雑度はブローカーが引き受ける

 

MQTT は (SOAP や AJAX のような) HTTP 上の相乗りではなく、独自の通信ポート(TCP 1883, TLS 8883)を使って動作します。プロトコル仕様も HTTP/HTML とは全く異なるバイト単位のバイナリフォーマットで、十数バイト程度の短いメッセージを交換する場合にデータ効率が優れることも特長です。

トピックとメッセージ

さて、データ伝達の基本は「誰」に「何」を伝えるかです。「何」は更に「それを識別する名前」と「その中身」から構成されます。MQTT において「何」は「トピック(Topic)」という文字列で識別されます。トピックはいわゆるファイルシステムのパス構造に似て、/ から始まる任意の階層の名前を付けることができます。MQTT のトピック名「/ から始まる Unicode UTF-8 文字列であること」「全長 65535 バイト以下に収まること」「ヌル文字を含まないこと」「$, #, + は予約語として特別な機能が与えられる」という程度しか既定がなく、非常に自由度が高い特徴があります(※註)。

※註:もっとも原仕様で既定していないというだけの話で、実装系によって異なる既定や上限がある可能性はあります。

MQTT において、トピックとパブリッシャーの間に直接の関係はありません。複数のパブリッシャーが1つのトピックにメッセージを送ることも可能です(※註)。メッセージがどのパブリッシャーから送られたのか MQTT は一切関知しませんし、複数パブリッシャーから「ほぼ同時」にメッセージが送られてきた場合の優先度や到着順序は管理も保証もしません。

※註:誰彼構わずメッセージを送られると困るような場合、ユーザ名とパスワードを使ってトピックへのアクセス条件を制限するようにブローカーを実装することは可能です。これは「パーミッション(Permission)」機能と呼ばれ多くの MQTT ブローカーに実装されていますが、MQTT 仕様は「ユーザ名とパスワードが指定可能である」という枠組みを設けているだけで、それをどのように利用してパーミッションを実現するかは実装に任されています。

データの「中身」については、MQTT は「既定しないこと」を既定しています(※註)。たとえば温度 25 ℃を伝えるのにバイナリの 0x00 0x19 でも単位なし文字列の "25" でも、あるいは単位付き文字列の "25C" でも "77F" でも "298K" でも、アプリケーションが解釈できればそれで良いというのです。従来のメッセージ型プロトコルが何らかのデータ型(整数や文字列)を持っていたのに対し、この自由度の高さは異様にすら思えます。とはいえ、本当にパブリッシャー毎にバラバラのメッセージを送られると破綻してしまうので、おそらく現実的には XML や JSON など、既に WEB 世界で用いられているフォーマットが使用されるでしょう。なお MQTT のメッセージ長はちょっと変わったエンコード形式を採用しており、表現可能な最大長は 256M バイトになります。

※註:仕様書原文では "The content and format of the data is application specific".

データを「誰に」伝えるかについては、MQTT はセッションをもって「誰」を識別します。つまり MQTT の受信側(サブスクライバー)側はつなぎっ放しで使うことを前提としており、例えば e-mail のように切断状態にあった「誰か」がログインして溜まっていたメッセージをまとめて受信するような運用は想定されていません(※註)。「MQTT という名前に反して Queue 機能はない」と冒頭に書いた通りです。

※註:ただし、トピックに送られた最後のメッセージを1つだけ溜める「リテイン(Retain)」という機能はオプション仕様として既定されています。

サブスクライバーはブローカーに対し、自分が受信したいトピックを指定します。そのトピックに対して(パブリッシャーから)メッセージが送られると、メッセージは全てのサブスクライバーに再配布されます。メッセージ配送システムにありがちな、ビットマスクや AND/OR 条件を組み合わせた配布条件フィルタのような難しい機能はありません(※註)。

※註:これも、ユーザ名に紐づけてメッセージの再配布条件を設定する実装は可能です。それを実装するかどうか、どれだけ複雑な条件設定を可能にするかは実装と運用に任されます。

MQTT のサブスクライバー側には、複数のトピックをまとめて受信する「ワイルドカード」という仕組みがあります。これは上で「予約語として特別な機能が与えられる」とした + と # の組み合わせで指定されます。
MQTT のトピックにおいて + は単一階層の任意一致、# は「それ以降の全ての一致」を示します。ワイルドカードは階層単位で用いられ、文字列の部分一致には用いられません。「/bedroom/#」というトピックは「/bedroom/ で始まる全てのトピック」、「/+/light」は「2階層で2層目が light の全てのトピック」を対象とします。「/+room」だとか「/bed#」のように文字列と組み合わせて使うことはできません。

ワイルドカードを使った場合、サブスクライバーは(当然ながら)複数の異なるトピックからのメッセージを受信することになります。しかし MQTT ではメッセージの優先度や到着順序は管理も保証もしませんし、それどころかメッセージがどのトピックから送られたかも示してくれません。この辺も「アプリケーション依存」なので、「メッセージにトピック情報を添付することにする」とか「そもそもワイルドカードには頼らない」とか、システムとして何らかの解決法を定める必要があります。

MQTT を使ってみる

MQTT には Python や JavaScript や Erlang といった「IT 屋さん言語」で書かれた実装も多く、ネット上の MQTT 解説にはそれらを駆使したものが多いのですが、今回は「組み込み屋さん」らしくベタの C 言語で書かれた mosquitto というパッケージを使ってました。バージョンは 1.4.9 ですが、バージョンが問題になるほど難しいことはやりません。

mosquitto のソースをダウンロードして make すると、src/ 下にブローカーである mosquitto が、client/ 下にパブリッシャーの mosquitto_pub とサブスクライバーの mosquitto_sub が生成されるはずです。
まず最初にブローカーを起動します。-v オプションを付けて接続や送受信の情報を逐一表示させるようにすると、何がどう動いているのかわかりやすいでしょう。
 

$ ./src/mosquitto -v
1470848533: mosquitto version 1.4.9 (build date 2016-08-09 09:39:33-0700) starting
1470848533: Using default config.
1470848533: Opening ipv4 listen socket on port 1883.
1470848533: Opening ipv6 listen socket on port 1883.


次に別ウィンドウを開けてサブスクライバーを起動します。-h は IP アドレスないしホスト名、-t はトピックを指定します。
 

$ ./client/mosquitto_sub -h 192.168.5.79 -t /bedroom/light


次にもう一つ別ウィンドウを開けてパブリッシャーを起動します。-h と -t はサブスクライバーと同じで、-m がメッセージを示します。
 

$ ./client/mosquitto_pub -h 192.168.5.79 -t /bedroom/light -m ON


するとサブスクライバーのウィンドウに再配布されたメッセージ「ON」が表示されるはずです。
 

$ ./client/mosquitto_sub -h 192.168.5.79 -t /bedroom/light
ON


MQTT の簡単な動作例は以上です...これだけ?!そう、これだけ。このように、MQTT はとても単純なプロトコルです。進化の過程で(※註) QoS とか Retain とか Will とかのオプション機能も追加されてきたのですが、その辺を全く使わなくとも、もっとも単純なかたちで「とりあえず動かしてみる」ことができるのが MQTT の魅力の一つです。

※註:記事執筆時点で MQTT 仕様の最新バージョンは 3.1.1 ですが、前バージョンの 3.1 からかなり大幅な改訂が行われ直接互換性の無い部分すらあり少々の混乱を招いたようです。何で 3.2 とか 4.0 にせず 3.1.1 にしたんでしょうかね?

MQTT とセキュリティ

現実的には、素の TCP 1883 ポートで MQTT を動かすことはまず無いでしょう。クラウド上で稼動させるなら特に、TLS セキュリティ層を挟んで少なくとも片方向(ブローカー/サーバ側)の認証と暗号化を行う運用がほぼ必須になると思います。パブリッシャーやサブスクライバーの認証は TLS 暗号回線上で Username/Password を用いて行うことになると思われますが、クライアント証明書を用いた TLS の双方向認証を必須とすることも可能で、このへんは使用する実装系と運用上の要件に応じたインテグレーションが必要になります。

TLS を用いると接続・切断のたびに証明書の交換と検証が発生し、これは通信時間と消費電力の増大を招きます。バッテリ駆動で間欠的なデータ発行を行うセンサノードでは大問題で、ここをどう工夫するかがインテグレーターに問われることになります。データ送信間隔が短い場合は下手に切断・再接続するより、TLS 回線を接続しっ放しで省電力モードに入れたほうが効率的かも知れません。データ送信間隔が充分に長いなら切断・電源遮断して毎回再起動・再接続したほうが効率的かも知れません。何をもって「短い」とか「長い」と判断するかは待機時消費電力と再接続時・再認証時の消費電力との兼ね合いで、使用する MCU の性能や証明書の鍵長にも左右されるので一概には言えません。TLS を常時通電のアップリンクルータにトンネルとして実装して、末端ノードとアップリンクルータ間はデータリンク層のセキュリティ機能(WiFi WPA2 など)に頼ることが正解になるかも知れません。原理が単純で自由度が高いだけに、使いこなすにはそれなりにノウハウが要求されるのも MQTT の特徴ではないかと思います。

まとめ

以上、MQTT について簡単にまとめてみました。コマンドやパケットフォーマットの詳細にはあえて踏み込んでいませんが、MQTT は日本でも盛んに実装が行われていて、開発者による充実した日本語の(ホンネの)情報もネットで読むことができます。日本語情報が少ないうえ、往々にして内容が古かったり誤解混じりだったりする Bluetooth とは大違いです。

MQTT はとにかく「アホみたいに単純」なプロトコルで、メッセージフォーマットすら既定せず、運用で直面するであろう多くの課題を「アプリケーション依存」として実装/運用者に丸投げしている印象を受けます。MUST, MUST NOT, SHOULD, SHOULD NOT でガンジガラメにされた過保護管理主義な最近の RFC 仕様にくらべると、何だか3桁時代の RFC を見ているような気になります。こういう仕様が IETF/RFC の枠外から出てきて市場に受け入れつつあるというのは象徴的ですね。

「IPv6 over Bluetooth のはなし」では Bluetooth LE の IPSP を実働させて「スループットは 10Kbit/sec くらい」「こんなので HTTPS を通すのはかなり無謀」と書きましたが、MQTT はこういった低速ネットワークに向くかも知れません。とはいえセキュリティ層に TLS を使用して毎回接続・認証すれば毎回数キロバイトの証明書交換手続きが入るので、HTTP と MQTT のヘッダ長差なんて大した違いではなくなります。MQTT の特長を活かしたシステムとして稼動させるには要求条件と使用するコンポーネントの特性を把握し、利害得失を判断して適切なセッティングを選ぶインテグレーターの手腕が問われることに変わりはないでしょう。

関連リンク

製品のご購入・サービスカスタマイズ・資料請求など
お気軽にお問い合わせください