自習室

こもります

ofxLibWebsockets で oF とウェブブラウザの間でお話しする

はじめに

相変わらず、ブラウザとopenFrameworks 間で共同作業させるシリーズです。

以前こんなものを書きました。

これらの記事では、ブラウザとoF間にNode.jsのプログラムを挟み、socket.io と OSC を相互に変換することで、データのやりとりをしていました。もともと以下の理由でそういう構成にしていました。

  • ブラウザとnode.js間はsocket.ioでの通信が楽
  • node.jsとoF間では、OSCでの通信が楽
  • それを単につなげればよいので楽

このときの構成は下図のようになります

f:id:AMANE:20150407143947p:plain

しかしこの結果

  • 機能上はほぼ意味の無い node.js の変換コードを書かなければいけない。実装コスト増大

という問題も発生しました。

  • oFでやろうとしていることを別のプログラムでシミュレートする
  • 複数のブラウザやoFのプログラムとやりとりをする

などのケースを想定すると多段構成にする意味もありますが、一対一で通信をするだけなら、ほぼ意味はありません。従って、ブラウザとoFで直接お話しをさせたいです。

いろいろ試した結果、以下の構成が可能であることがわかりました。今回はその方法についてまとめます。

f:id:AMANE:20150407143959p:plain

非バイナリデータのみ

WebSocketでバイナリのやりとりも出来る筈ですが、本記事では非バイナリデータ…つまり、数値、文字、bool とそれらをオブジェクト化した物などをやりとりします。いずれバイナリも調査します。

この記事を追うとできるもの

oFの画面でマウスを動かすと、その座標をJSONとしてラップし、WebSocketを介してブラウザに送ります。ブラウザではJSONを復元して、オブジェクトとしてコンソールに出力します。

f:id:AMANE:20150408215316j:plain

完成品はこちらにアップしています

github.com

ofxLibWebsockets の基本

addon単体としてのドキュメントがほとんど無いため、使いこなしに少々苦労しました。サンプルを読んで探していくスタイル。

プロジェクトへの追加

oFプロジェクトへの追加の方法は、ofxLibWebsockets のgithubにまとめられています。こちらを参考に追加もしくはoFプロジェクトの作成を行ってください。

自分はWindows(VS2012) で試しているのですが、配布されている ofxLibWebsockets のオプションに間違いを見つけました。以下修正してください。

プロジェクトプロパティ > リンカー > 全般 > 追加のライブラリディレクトリ で、構成が Debug の際に、

..\..\..\addons\ofxLibwebsockets\libs\libwebsockets\lib\win32\Release` となっているものを
..\..\..\addons\ofxLibwebsockets\libs\libwebsockets\lib\win32\Debug に修正。 

oF側をサーバとしてセットアップ

ofxLibWebsockets のサンプル "example_server_echo" を参考に。最低限の構成は以下、だと思います

void ofApp::setup(){
    ofxLibwebsockets::ServerOptions options = ofxLibwebsockets::defaultServerOptions();
    options.port = 9092;
    options.bUseSSL = false; // you'll have to manually accept this self-signed cert if 'true'!
    bSetup = server.setup( options );

    // this adds your app as a listener for the server
    server.addListener(this);
}

メッセージの受け取り

こちらも、ofxLibWebsockets のサンプル "example_server_echo" を参考に。ただ受け取ったメッセージを標準出力します。

void ofApp::onMessage( ofxLibwebsockets::Event& args ){
    cout << "got message " << args.message << endl;
}

試しにブラウザから送ってみる

先に "example_server_echo" を起動してから、Chromeで適当なページを開き、developer tool のコンソール上で以下の様に順に打ち込みます

ws = new WebSocket('ws://localhost:9092'); // 接続
ws.send('hoge'); // 文字列を送る

一行目をenterした時点で、oFのコンソールには "new connection open" と表示されます。
更に二行目をenterすると、oFのコンソールに "got message hoge" を表示されます。

JSON形式でやりとりしてみる

ブラウザ側から送ってくるデータは、それが元々オブジェクトであった場合も、message = JSON.stringify(data) したのちに送り出し、ofxLibWebsockets で受け取ると、jsonとして復元されます。

逆に、ブラウザ側でJSON形式で受け取りたい場合は、oF側でJSON形式でまとめ上げたものを文字列として送り出し、それをdata = JSON.parse(message) することで、jsonとして復元されます。

図にまとめると下のようになります。

f:id:AMANE:20150407162026p:plain

ofxLibWebsockets は内部でjsoncpp というライブラリを利用しているので、json <-> 文字列 の変換が出来ます。新しいjson形式のデータ作成も可能です。

ブラウザからobjectデータを送り出し、oFで受け取るケース

// 送出側 (js)
var ws = new WebSocket('ws://localhost:9092/');
var pos = {'x':0, 'y':100};
var message = JSON.stringify(pos);
ws.send(message);
// 受け取り側 (cpp)
void ofApp::onMessage( ofxLibwebsockets::Event& args ){
    // args.messageには、 js側で message = JSON.stringify(pos) として作られた文字列"message" が入っている
    cout<<"got message "<<args.message<<endl;
    
    // 送られてきた args.message がjsonにパースできる形式である場合、
    // 自動的にパースされ args.json に Json::Value 形式で収められる
    if ( !args.json.isNull() ){
        cout << "New message: " + args.json.toStyledString() + " from " + args.conn.getClientName();
    }
}

oFでJSON形式にラップしたものをブラウザで受け取るケース

マウスを動かすと、最新の座標をWebSocketで送り出す、というもの

// 送出側 (cpp)
void ofApp::mouseMoved(int x, int y ){
    Json::FastWriter    writer;
    Json::Value     value;
    value["x"] = x;
    value["y"] = y;
    std::string message =  writer.write(value);
    server.send(message);
    cout << "mouseMoved : " << message << endl;
}
// 受け取り側 (js)
var ws = new WebSocket('ws://localhost:9092/');
ws.onmessage = function(event) {
  var data = JSON.parse(event.data); //event.dataは文字列。これをJSON形式にパース
  console.log(data);
}

JSONCPP の使い方

以下のページなどを参考にしました

スピードを上げる

ofxLibWebsocket (libwebsocket)では、WebSocketの送り出し受け取りを、専用のスレッドを立てて行っています。一つの処理をコンテキストとしてまとめ、それをキューに積んでいって、前から順に処理していきます。その辺りはこちらのページで勉強させて頂きました。

ofxLibWebsocketでは、そのスレッド処理に、何故か標準で 50ms の待機時間が挿入されているため、毎秒 20 回までしか通信の処理が出来ません。マウスの動きを伝えるには遅すぎます。ここを変更します。

// addons/ofxLibwebsockets/ofxLibWebsockets/Server.cpp 19行目
    Server::Server(){
        context = NULL;
        waitMillis = 10; //★ここ 50 -> 10 など。
        reactors.push_back(this);
        
        defaultOptions = defaultServerOptions();
    }

これで、最大100回/秒 通信できるようになります。

waitMillis を変更するための public な関数をServer.cpp か 親クラスの Reactor.cpp に追加しても良いかもしれません。

さいごに

oFで画像を作ってブラウザ内で利用する、と言ったことも出来るかもしれないので、調査してみます。