小さな小さなインターネット

ご無沙汰してました。今回は、このソフトウェアの開発経緯を書いてみます。

開発経緯

私は今、主にB2B製品向けのアプリケーション、ミドルウェアからファームウェアまで幅広く関わらせて頂いていますが、メーカーに於けるソフトウェアエンジニアって何してるんだか、詳しく知っている方はあまりいらっしゃらないかと思います。あまり深い話は書けませんが、書ける範囲で少しだけ。 

 現在では、B2B製品と言っても一般的なコンピュータとあまり変わらず、OSに組み込みLinux組み込みWindowsを利用したものも多く、また一般的な家電(コンシューマ製品)と同じく、カムコーダレコーダスイッチャーなどと幅広い製品群を揃えます。また、カムコーダなどは記録できる映像の形式やサイズ、機能などによって様々な機種が出ています。

これら製品は基本的に単体で動作し、例えばカムコーダだと映像を如何に綺麗かつ滑らかに長時間記録できるか、とか、どれだけ重量が軽く、かつ軽快に動作可能とするか、といったことが重要視されます。

ネットワーク経由で機材をコントロールする、であるとか、ネットワーク経由で映像素材を配信するであるとかは、さほど重要視はされていませんでした。 

しかし、ここ数年でコンシューマ製品と同じく状況が変化しまして、皆さまご存知の通りの状況です。様々なメーカーの機材を組み合わせ、また、ネットワーク経由でのコントロールや配信が当たり前になってきました。勿論、弊社もそれに追随し、様々なネットワーク機能を実装してきていました。

 

しかし、です。様々な製品が、様々な時期にリリースされる、という現実があります。

 

すると、製品によって、またリリースされる時期によって、それこそ星の数ほどのプロトコルが生まれるわけです。例えば、TCP+独自プロトコル。例えば、HTTP+REST。例えば、Socket.IO。FTP+独自プロトコル、なんてものさえもあります。

それぞれに良い点もあり、またできる限り使いやすいように設計しよう、という思いはあるのですが、やはり問題点も多いです。

例えば、 それらの機材をコントロールするためのソフトウェアを考えてみると、事態は深刻です。

 

「この機種はHTTPだっけ?」「あの機種はFTPだし」「これはSocket.IOだ」などなどなど。クライアント側のネットワークプロトコルスタックは膨れ上がり、また、製品とプロトコルを結びつけるデータベースは膨れ上がります。あたかも、クローズドなネットワークに於ける小さな小さなインターネットの様相を呈してきます。

 

サービスが違えばプロトコルが違う。それはしょうがないことかな、と思います。しかし、同じ会社の、似たような製品であるのに、この機種ではこのトランスポートとプロトコル、別の機種では別のトランスポートとプロトコル。これではCPUやメモリリソース、技術者と時間がどれだけあっても足りません。悪夢です。

そのような問題に対処するため、「少なくとも機器共通で接続性を担保するプロトコルが欲しいよね」というのがこのソフトウェアを開発し始めた経緯となります。 

 

必要とされる機能

次に、必要とされる機能について。ここでは、コントロールされる様々な機材、つまりサーバ機能を担う製品について考えてみます。

基本的に新規ハードウェアを起こす場合、少なくとも1-2年前にはプラットフォームが決まります。また、製品価格の問題もあるため、潤沢なCPUやメモリリソースというものはあまり存在しません。(特に映像関連の機器では、潤沢なリソースがあってもCODEC処理などに費やされがちです)

また、製品内部にsqlitememcachedなどデータベースを持つような機材も多くありません。簡易なリストをオンメモリで持っていたり、ステートマシンだけで動作するようなものも存在します。

そして、ハードウェアイベントハンドラが様々設定されており、例えば、あるボタンが押されたら割り込みがかかりそれに対応する動作を並列・平行で行う、などといった仕組みやイベントループが回っています。とにかくハード・ソフト双方を纏めあげて動作速度や応答性を上げるような工夫がされているシステムが多くなっている、と捉えていただければ大体あっているかな、と思います。

 

以上からまずは、

通常の機材の動作を阻害しない形でネットワーク機能を追加したい」

という要求が生まれます。

 

そして次に、ネットワーク機能というものも、ハードウェアボタンを押すのとほぼ同等の性能が求められます。

例えば、録画スタートボタンを押してから1フレーム以内(今は大体1秒間に60フレーム撮影を基本としていて、約16ms以内、となります)に録画開始してほしい、とか、Pan/Tilt(左右・上下にカメラを振る)コマンドを受けたら即時動作してほしい、など。

閉域網とはいえ、CSMA/CDやCSMA/CAだとどうしても限界(jitterの発生)はあるのですが、出来る限り改善してほしい、となります。

 

そのため、

「ネットワーク上に流すコマンドやデータを絞り込む(出来る限りネットワーク上のやり取りを少なくする)」

「ネットワーク上を流れるデータフレームを出来る限り小さくする」

シリアライズ・デシリアライズにかかる負荷を小さく、時間を短くする」

といった、一般的なネットワークアプリケーションの要求も満たす必要が出てきます。

 

そして最後に、開発の容易性ももちろん求められます。

私は大規模な製品開発そのものに携わることが殆どないので間違っているかもしれませんが、100名単位でのソフトウェア開発もあると聞いています。

すると、どうしても上位アプリケーションが複雑になるため、下回りに用いるソフトウェアでは、出来る限り一意かつ安全にアクセス可能なI/Fを用意しておかないと、利用方法によっては上位アプリケーション側で問題(BUG)が発生し、問題発見に時間がかかることがあります。

つまり、

簡単・安全に利用できる一意なAPIをもつミドルウェア

が求められます。

要求をまとめると…

  1. 機材の通常動作を阻害しない負荷、かつ既存ソフトウェアの大きな構造変更を伴わない形でネットワーク機能を追加できるソフトウェア
  2. 効率的な通信が行えるネットワークソフトウェア
  3. 簡単・安全に利用できる一意なAPIをもつソフトウェア

以上3つが求められる機能となります。

 

この3つを満たす何かを探していたところ、msgpackが浮かび上がりました

改めて見直してみると、小飼さんの2010年の記事ですね。思い起こせば、開発を始めた頃はWebSocketがまだhixie-draftだったり、msgpackがSTR typeをもっていなかったり、uupaaさんedvakfさんがmsgpack-jsの実装を話し合ってたり、msgpack-cのメンテナが不在(というか、古橋さん)で、今のようにnobu-kさんredboltzさんが頻繁にcommitしている状況ではなかった記憶があります。

と、思い出はさておき、内部でもJSONやProtocolBufferなど様々なシリアライザを調査してました。

 

まず、効率的な通信、これが驚異的でした。kazuhoさんのpicojsonを用いて、私たちが扱う予定のデータを幾つか試してみたところ、データサイズで約1/4、シリアライズにかかる時間で約1/18、デシリアライズに係る時間ではなんと1/100と、相当な速さ、という結果となりました。(なお、かなり昔のことですし、またデータ構造などで大きく変わりますのでこの結果は当時の私たちの評価ではこうなった、という結果でしかありません)

 

次に、アドオンとしてネットワーク機能を追加できる形、というものに対してもmsgpackはとても扱いやすいです。様々な機材の上で動作している、何らかのデータ構造を扱うソフトウェアでは、CやC++が利用されていました。

msgpack-cでは、シリアライズ・デシリアライズするオブジェクトは、MSGPACK_DEFINEするのみでよい、という仕組みが存在します。

つまり、いままでのコードをほぼ書き換えることなく、たったの1行defineを追加するのみでシリアライズされる、なんて素敵な仕様なんでしょう。

 

最後に、簡単・容易に利用できる一意なAPIとなりますが、ここだけ少し考える必要がありました。msgpackでシリアライズしデータ交換を行うことを考えた場合、後はネットワークプロトコルを決めるだけなのですが、私たちのシステムで扱う必要があったプラットフォームが、

  • PCアプリケーション(Win/Mac)
  • iOSAndroid端末
  • Webブラウザ

と、当初から複数種類あったためです。また、ターゲット市場は主に閉域網ではありますが、やはり昨今はネットワークセキュリティが求められつつあり、証明書による相手先検証やトランスポートの暗号化などといった要求も視野に入れる必要がありました。

すると、SSLWSSを利用する必要が発生しますが、各プラットフォーム、各プログラミング言語、そして各トランスポートを用いたmsgpackでのデータ交換が実現できる実装は存在しませんでした。

そのため、Node.jsの実装を参考にさせていただきつつ、下回りに用いられていたlibuvを利用させていただき(主にWinSockのため、でしたが)、アプリケーションサーバ・クライアント用ライブラリを作成する運びと相成りました。

 

様々な素晴らしいOSSの力をお借りしまして、今のlinear-rpcにおいては、

// 追加コード

class FooHandler : linear::Handler {

  public:

    void OnConnect(...) { ... }

    void OnDisconnect(...) { ... }

    void OnMessage(...) { ... }

};

linear::TCPServer tcp_server(FooHandler()); // TCP Server

tcp_server.Start("0.0.0.0", 9000);

linear::WSServer ws_server(FooHandler()); // WebSocket Server

ws_server.Start("0.0.0.0", 9001);

// 既存コード

while (true) {

  ... // 様々な機能のメイン処理ループ

} 

と、ネットワークイベントハンドラを追加するのみで、様々なネットワークからの操作を同じようにハンドルすることが可能になっています。

 

また、送受信するmsgpackデータをもう少しだけ取り扱いやすくするため、linear::type::anyという型などの追加や、m_mizutaniさんのQiitaに記述してあるchunked dataの取り扱いを気にしないで良い仕組み、サーバサイドから任意のタイミングでメッセージを送信可能な仕組み(Push通知/Notification通知といった類)なども入れてあります。

ただ、これら扱いやすくするための代償、そして私たちの力不足によって、少しだけ速度は犠牲になっていますので、この辺りは今後継続して改善していこうと思っています。

 

なお、私たちの置かれている環境では、「様々な製品が、様々な時期にリリースされる」ため、過去互換性を慎重に検証する必要性があります。

そのため、より速いと言われているmsgpack-liteなども追っかけてはいますが、IE8はまだもう少しサポートしなくてはいけない(少なくともJSONPへのフォールバックをサポートしなくてはいけない)であるとか、旧製品のアプリケーション開発環境に適合させなくてはならず、古いgccなどでのmakeも通さなくてはならないであるとか、まだまだ組み込み機器においてはスクリプト言語は常用できない(この観点では、golangやmrubyなどがとても魅力的ですが、残念ながら内部に開発者が少なく利用できない)、など幾つかの制約が存在します。(勿論、まだまだCのI/FやLibraryが欲しい、と言われることも良くあります)

 

そのため、C++14であるとかES6であるとかそういった新しい言語、仕様への追随が難しい状況にありますが、そのあたりも環境が追いつき次第、追随していきたいな、と思ってます。

 

私が知っている範囲の話でしかないですが、このような形でソフトウェア開発を行っています。gitlabやgerrit、Jenkinsなどを運用しているところもありますし、travis CIやcircle CIを利用しているところもあります。勿論、静的・動的解析ツールを使うこともあり、利用言語や仕様に関して一部制約がある以外は、一般的なソフトウェア開発とあまり変わらないと思っていただくと良いかな、と思います。

昔のコンパイラや仕様と戦いながらコードを記述し、ハードウェアと連携、製品として世の中に送り出されるのを見届けるのもなかなか面白いですよ。

 

さて、今回まで実効コードが全くなかったので、最後に、C++のエコーサーバとJavaScriptのエコークライアントを以下に記述して今回の〆としたいと思います。

C++エコーサーバ

gist.github.com

 

JavaScriptエコークライアント

gist.github.com

動かし方

C++側は、linear-cppのsampleフォルダにある、ws_server_sample.cppを置き換えてmake、実行

$ cp foo.cpp /path/to/linear-cpp/sample/ws_server_sample.cpp

$ cd /path/to/linear-cpp

$ ./bootstrap && ./configure --with-sample && make

$ cd sample; ./ws_server_sample

JavaScript側は、C++側のサーバを起動したのち、jquery.min.jsとlinear.debug.jsをhtmlと同じフォルダに置いてブラウザで開く(file://)だけで動作しますので、是非試してみてくださいませ。

では、また。