読者です 読者をやめる 読者になる 読者になる

pblog

pplog.net を作っている @ppworks こと越川直人(Koshikawa Naoto)のブログ。esa LLCで働いてます(\\( ⁰⊖⁰)/)

red5ではFMSのproxied SharedObjectは出来ない?

proxied SharedObjectとは

そもそもproxied SharedObjectとはO'reillyのProgramming Flash Communication ServerP315に載っているテクニックのことです。(マニュアルにも載っているのでテクニックというほどでもないですが)
あるFMSアプリケーションから別のFMSアプリケーションのSharedObjectをあたかも自分のSharedObjectかのように扱えるようにするのがproxied SharedObjectの素敵なところです。
以下の図でいうと、chat/_definst_のSharedObjectをproxyして各room1〜15でも同じSharedObjectを共有できるイメージです。ロビーからルームを作成するようなアプリケーションにおいて、ロビーからルームに居るユーザ一覧や、ルームからロビー並びに他のルームにいるユーザ一覧を参照するよ、みたいなシチュエーションで使えそうです。
親側では普通にSharedObjectを作成。子側は以下のようにSharedObject作成。SharedObject.getメソッドの第三引数へ親applicationへのNetConnectionを渡している点がポイント。

var nc = new NetConnection();
nc.connect("親applicationのURI");
var so = SharedObject.get("親のapplicationで作成したSharedObject名", isPersistent, nc);

f:id:naoto5959:20081219140355p:image

red5におけるSharedObjectの扱い

red5ではscopeごとにSharedObjectを作成・参照・更新出来るので、サーバ上で別scopeのSharedObjectを参照・更新することが可能です。ただし、クライアントから現在接続しているscope以外のscopeのSharedObjectへ接続することが出来ないようです。接続する方法を知っている方がいらっしゃったら教えていただきたいです。切実です。
また、FMSのようなConnectionを指定したproxied SharedObjectはサポートされていません。しかし、前述したような親アプリケーションと子アプリケーションでSharedObjectをShare(変な表現ですね)したい場合、どうしたら良いのでしょう。

考えてみたこと

  • 親のscopeで作成したSharedObjectが更新されたら各子のscopeのSharedObjectを更新する
  • 各子のscopeで作成したSharedObejctが更新されたら親のscopeのSharedObjectを更新する

これで、似たようなことが出来るような気がします。

やってみた

appStartでapplication scopeのSharedObjectを作成する。Listenerとして、changeやdeleteイベントが発生したときに各room scopeのSharedObjectへ値を通知するAppScopeListenerを登録する。
roomStartではroom scopeのSharedObjectを作成する。Listenerとして、コンストラクタでapplication scopeのSharedObjectを読み込む処理、changeやdeleteイベント発生時にapplication scopeのSharedObjectへ値を通知するRoomScopeListenerを登録する。
以下、例。とりあえずこれで動きましたが、色々と抜けている点があるので今後メンテナンスします。

Applicationクラスの抜粋
public class Application extends ApplicationAdapter {
    public static final String SO_NAME = "chat";
    
    private static IScope _appScope;
    
    private ISharedObject _soApp;
    
    public boolean appStart(IScope app) {
        _appScope = app;
        createSharedObject(_appScope, "roomList", false);
        _soAppListener = new AppScopeSOListener(this);
        _soApp = getSharedObject(_appScope, "roomList", false);
        _soApp.addSharedObjectListener(new AppScopeSOListener(this));
    }

    public boolean roomStart(IScope room) {
        createSharedObject(room, "roomList", false);
        ISharedObject soLobby = getSharedObject(room, "roomList");
        soLobby.addSharedObjectListener(new RoomScopeSOListener(soLobby, _soApp));
        return super.roomStart(room);
    }
}
AppScopeSOListenerの抜粋
public class AppScopeSOListener
        implements ISharedObjectListener {
    private ApplicationAdapter _app;
    
    public AppScopeSOListener(ApplicationAdapter app) {
        _app = app;
    }
    
    public void onSharedObjectUpdate(ISharedObjectBase so, String key, Object newValue) { 
        Iterator itr = _app.getChildScopeNames();
        while (itr.hasNext()) {
            String scopeName = (String)itr.next();
            scopeName = scopeName.replace(":", "");//bughack!
            IScope scope = _app.getChildScope(scopeName);
            ISharedObject childSO = _app.getSharedObject(scope, Application.SO_NAME, false);
            if (childSO.getAttribute(key) != newValue) {
                childSO.beginUpdate();
                childSO.setAttribute(key, newValue);
                childSO.endUpdate();
            }
        }
    }
    
    public void onSharedObjectDelete(ISharedObjectBase so, String key) { 
        Iterator itr = _app.getChildScopeNames();
        while (itr.hasNext()) {
            String scopeName = (String)itr.next();
            scopeName = scopeName.replace(":", "");//bughack!
            IScope scope = _app.getChildScope(scopeName);
            ISharedObject childSO = _app.getSharedObject(scope, Application.SO_NAME, false);
            if (childSO.hasAttribute(key)) {
                childSO.beginUpdate();
                childSO.removeAttribute(key);
                childSO.endUpdate();
            }
        }
    }
}
RoomScopeSOListenerの抜粋
public class RoomScopeSOListener
        implements ISharedObjectListener {
    private ISharedObject _appSO;
    
    public RoomScopeSOListener(ISharedObject roomSO, ISharedObject appSO) {
        _appSO = appSO;
        Set <String> soKeySet = _appSO.getAttributeNames();
        Iterator itr = soKeySet.iterator();
        while (itr.hasNext()) {
            String soKey = (String)itr.next();
            roomSO.setAttribute(soKey, appSO.getAttribute(soKey));
        }
    }

    public void onSharedObjectUpdate(ISharedObjectBase so, String key, Object newValue) { 
        if (_appSO.getAttribute(key) != newValue) {//[bug]hack!
            _appSO.beginUpdate();
            _appSO.setAttribute(key, newValue);
            _appSO.endUpdate();
        }
    }

    public void onSharedObjectDelete(ISharedObjectBase so, String key) { 
        if (_appSO.hasAttribute(key)) {
            _appSO.removeAttribute(key);
        }    
    }
}