スライドしてアンロック風ボタンを作ってみた。

f:id:growthfield:20121123101150j:plain

iOSのUI回りの勉強のためカスタムUIControlを書いてみました。NNGradientControls
iPhoneのロック画面に出てくるアレです。特許になっちゃってるみたいですが。
アップル訴訟「スライド式ロック解除」とは?

まあ特許やらロック解除やらは置いておいてこの手のスライド式ボタンはちょっとした誤タッチで動作してしいまう通常のボタンに比べてユーザのより明確な意志の元に動作するので重宝する気がします。
確認画面を挟むまでも無いけどある程度誤動作を抑制したいようなケースとか。
でも審査は通らないんだろうな〜。

NNGradientSlideButton

NNGradientSlideButtonはUIControlから派生しており、ボタンが一番右まで移動されるとUIControlEventValueChangedイベントを発火します。

Storyboardから利用される事を想定していて、素のViewコンポーネントを配置しコンポーネントのCustom ClassにNNGradientSlideButtonを指定する事で利用できるようになります。
ボタンの色はViewコンポーネントの背景色が使用されます。

また以下のプロパティが指定可能になっています。

プロパティ 説明
leftPadding スライド表示領域左端側の空白領域サイズを指定します。
rightPadding スライド表示領域右端側の空白領域サイズを指定します。
font グラデーションするテキストのフォントを指定します。
text グラデーションするテキストの文字列を指定します。
textColor グラデーションするテキストの色を指定します。

font以外はStoryboardのUser defined runtime attributesから指定可能ですが、
fontはUser defined runtime attributesで利用可能な型ではないのでコード上から指定する必要がありますが、欧文フォントしか利用できないという制限があります。

iPhoneの位置情報をsocket.ioでリアルタイムにGoogle Mapに表示する

前エントリ iPhoneとsocket.ioサーバを常時接続。その時バッテリは? からの続きです。
今回はiPhone側で取得した位置情報をsocket.io経由でnodejs側へ流してgoogle mapで表示というサンプルアプリを作ってみました。

実現するために必要な主要機能は以下となります。

  • iPhone側で位置情報を取得してsocket.ioに流す
  • node側(socket.io)側では受信した位置情報をDBに格納しつつブロードキャスト
  • node側(web UI)側では受信した位置情報をgoogle mapに表示する

f:id:growthfield:20120226074449p:plain

構成要素

サーバ

Z Clould上のnodejsMVCフレームワークであるMatadorとリアルタイムな双方向通信のためにsocket.ioを利用します。
DBにはMongoDBを使用し、node側からはMongooseを利用してアクセスします。

iPhoneクライアント

iPhone4端末にネイティブのクライアントアプリにNNSocketIOを組み込んで利用します。
アプリはvoipかつlocation型として登録しバックグラウンドでも位置を測定しつつsocket.ioでサーバへ送信できるようにします。

WEB UI

BootstrapjQueryGoogle Maps JavaScript APIを使用します。

機能

マップ表示

Google Map上にiPhoneと現在位置と過去6時間の移動経路を表示します。
これらの情報はリアルタイムに反映します。
現在位置はマーカー、移動経路は青い線によって示されます。
f:id:growthfield:20120226111501p:plain赤いマーカーはiPhone側がオンライン状態
f:id:growthfield:20120226111610p:plain灰色のマーカーはオフライン状態
を表します。

f:id:growthfield:20120226094421p:plain

iPhone側では標準位置情報サービスを使用して現在位置を要求精度「kCLLocationAccuracyBestForNavigation」で取得し、位置が20メートル以上変化したらサーバへ送信するようにしています。なので上図のように小道では移動経路が若干カクカクになります。
閾値を5メートル程度にすると移動経路はいい感じになりますが、その分送信データが増えるためバッテリの消耗が激しくなってしまいます。

ちなみに自分が試した限り屋外では概ね誤差+-20メートル以下の精度で位置情報が得られました。また建物内や地下鉄などでは誤差が大きくなりました。(+-150メートル以上)
標準位置情報サービスはGPSだけではなくWiFiスポット、携帯基地局なども併用して位置を求めるためか、地下鉄でも駅構内ならばそれなりに位置を測定できます。

ストリートビュー表示

マップ表示は地味でいまいちリアルタイム性を体感できなかったので、iPhoneの磁力センサーから取得した方位角をストリートビューにリアルタイム反映させてみました。

f:id:growthfield:20120226102827p:plain

スクリーンショットでは全く分からないですがiPhoneの向きに応じてストリートビューが360度ぐりぐりと動きます。

DEMO

作成したサンプルアプリケーションは数日間以下に公開します。
chromesafariで動作確認しています。
http://node.growthfield.jp:8080

デモを公開しておいてなんですが、最近自分の行動範囲は自宅と会社の往復でかつ地下鉄多用なので退屈なデータしか表示されないと思います。
デモ終了しました。

ソースコードは以下にあります。

課題的なもの

取得した位置情報の精査

iPhone側では測定した位置を使用するかどうかのフィルタ処理が重要になります。
精度が高いデータしか使用しないようにすると地下鉄などでは位置が記録されなくなってしまいますし、逆に精度の低いデータを使用するとマップ上の移動経路が拡散してしまいます。
そこで今回は誤差が+-20メートル以下の場合は即利用し、この精度のデータが取得できない場合は15秒間隔で取得したデータの内もっとも精度が高いものを利用するようにしてみました。なにもしないよりは多少マシですが、もっと良い精査アルゴリズムを適用したいところです。

また取得したデータ上高精度となっていても実際には誤差以上かけ離れた場所を指し、測定位置が転々とする場合もあるので、一カ所に留まっていてもマップ上ではあたりを徘徊しているように表示されることがあります。

バッテリ

分かっていた事ではありますが、socket.io常時接続とGPS併用という二重苦で一時間あたり7〜8%を消費します。移動している時間帯はある程度消費するのは仕方ないのですが、職場や自宅など一定箇所に長く留まる際にはこの消費はキツイですね。
実際に試してはいないのですが、もし標準位置情報サービスより領域観測サービスの方が低電力で動作するのであれば移動量が極めて小さく一定箇所に留まっている状態を検出したらその領域に領域観測サービスをかけて標準位置情報サービスをオフ、領域外に出たら再度標準位置情報サービスをオンにするなんて対策もあるかもしれません。
またEnergy diagnosticsをかけつつ丁寧に最適化を試みるというのも面白そうではありますが、低電力動作の追求は今回の勉強の趣旨から外れてしまうのでまたの機会に。

iPhoneとsocket.ioサーバを常時接続。その時バッテリは?

前エントリ iPhoneとsocket.ioサーバを常時接続させる3 からの続きです。
今回はバックグラウンドでもRunLoopを動かし続ける版のAppでsocket.ioサーバとiPhoneと常時接続で動かし続け、バッテリがどの程度消費するのか確認してみます。
前回バッテリは気にしないと言いましたが、とはいえどのぐらいの目安で減っていくのか興味があったので精密には計測せずさらっと計ります。

でさらっと計ってサクっと終わらせるつもりだったのですが、やってみるとheartbeat間隔を120秒以上にあげようとすると接続断が発生します。

var io = require('socket.io').listen(8080);                                                                                                                                                                                                                                                                                                 
io.configure(function() {                                                                                                                                                
    io.set('heartbeat interval', 180);                                                                                                                                   
});   

上記のようにheartbeat間隔を120秒以上にすると接続後120秒でsocket.ioサーバ側が接続クローズしてしまう現象に見舞われました。
socket.ioではwebsocketトランスポートのコード内でHTTPのUpgradeメソッドを受け取った後、net.socket#setTimeout(0)をしてタイムアウトしないようにしているのですがどうもこれが効いていないようです。

nodeのissuesを見てみるとこれが該当っぽいです。
socket.setTimeout is ignored after HTTP upgrade

ということでnodeを最新の0.6.10に上げて解決しました。

で結果は?

以下の前提で試験した結果、

  • 実機はiPhone4(iOS 5.0.1) バッテリ100%状態
  • 実機側アプリはバックグラウンドでもRunLoopを動かし続ける版
  • サーバ側はZ Cloud上のnodejs 0.6.10とsocket.io 0.8.7
  • 3G回線を使用
  • 実機、サーバ間の送受信データはheartbeatパケットのみ。
  • 1時間後バッテリが100%からどのぐらい消費されたか確認する

こうなりました。

heartbeat間隔(秒) バッテリ使用量
20 7%
60 4%
180 2%

結構食いますね。サーバからiPhoneへ低頻度の即時通知処理とかだと実用上いけなくもなさそうですが、iPhone側から高頻度で情報を送ってるとモリモリとバッテリが消費していきそうです。

次回はそのモリモリケースのサンプルとして位置情報、加速度センサー、ジャイロセンサー等のiPhone上で知覚できる各種情報をリアルタイムにnode側へ流すアプリを作ってみたいと思います。

iPhoneとsocket.ioサーバを常時接続させる3

前エントリ iPhoneとsocket.ioサーバを常時接続させる2 からの続きです。
バックグラウンドで接続断が起きた場合の再接続処理を10分に1回実行されるsetKeepAliveTimeoutのタイミングではなく、即時(またはReachabilityが回復次第)に出来ないか調べてみます。

バックグラウンドでもRunLoopを回せるチャンスは?

まずバックグラウンドにおいてRunLoopが回るタイミングを整理します。

  1. voip接続が確立されている場合
  2. setKeepAliveTimeoutが10分1回呼ばれるのでそのタイミングで10秒動く
  3. beginBackgroundTaskWithExpirationHandlerを呼ぶと10分だけRunLoopが動く

1についてはデータの受信をトリガとしてRunLoopが回りますが接続断時は当然回らなくなります。
2はvoip接続を回復できる唯一無二のタイミングとなります。
3はバックグラウンドに回った最初の10分だけの話なので再接続処理としては当てにできません。

Reachabilityが回復したイベントを基点に再接続できないか?

まず考えたのがコレです。
setKeepAliveTimeoutを基点に再接続処理が行われるように、Reachabilityのイベントを基点に再接続処理を実施できないか調べてみました。
ReachabilityのイベントはRunLoopによって通知されるためRunLoopが回っていないとイベント自体が発生しません。つまりバックグラウンド時はReachabilityを失ったというイベントは受信できますが、回復したというイベントは受信できません。

バックグラウンドでも常時RunLoopが回るようにできないか?

次に考えたのがコレです。
なにかのイベントを基点するのではなく単純にバックグラウンドでもRunLoopが回るようにできないか調べてみました。ちなみにバッテリへの影響とかアプリとしてのお行儀とかは一旦忘れて仕組み的に可能かどうかだけに着目します。

beginBackgroundTaskWithExpirationHandlerを呼ぶとバックグラウンドに入った最初の10分間はRunLoopが動きます。
10分間という制限付きですがこの間はフォアグラウンドの時と同じように接続断からの再接続やReachabilityの変化に伴うイベント受信等なんら制約なく動くことができます。
この10分間を増やすというか無制限にできないでしょうか?

beginBackgroundTaskWithExpirationHandlerを呼び直してみる

10分に1度呼ばれるsetKeepAliveTimeoutでbeginBackgroundTaskWithExpirationHandlerを呼び、バックグラウンドタスクの使用を再宣言してみます。

- (void)applicationDidEnterBackground:(UIApplication *)application
{
    void (^handler)(void)  = ^{
        UIApplication* app = [UIApplication sharedApplication];
        [app endBackgroundTask:bgTask];
        bgTask = UIBackgroundTaskInvalid;
    };
    bgTask = [application beginBackgroundTaskWithExpirationHandler:handler];
    [application setKeepAliveTimeout:600 handler:^{
        if (!bgTask || bgTask == UIBackgroundTaskInvalid) {
            UIApplication* app = [UIApplication sharedApplication];
            bgTask = [app beginBackgroundTaskWithExpirationHandler:handler];                
        }
    }];
}

上記のコードを組み込んだ場合の挙動は以下のようになりました。

1. アプリがバックグラウンドに回る

2. beginBackgroundTaskWithExpirationHandlerで10分延命

3. setKeepAliveTimeoutで10分毎に再度beginBackgroundTaskWithExpirationHandlerを呼ぶようにする

4. 10分経過して2で登録したハンドラが実行。バックグラウンドタスクがexpire。

5. 同時にsetKeepAliveTimeoutのハンドラが実行。2に戻る。

バックグランドタスク宣言による10分間の延命要求が10分毎に発行されるので結果的にRunLoopは永続的に有効になります。
これによってフォアグラウンド時と同様に接続断から即時再接続が行えます。またReachabilityの回復時にも即時イベントが通知されるようになります。setKeepAliveTimeoutの10分後を待つ必要はなくなりました。

自分のイメージではbeginBackgroundTaskWithExpirationHandlerはバックグラウンドに一回入る度に一回だけ登録できる代物だと思っていたのですがexpireさえしていれば再度登録できるようです。

うーん、しかしこれはexploitなコードでしょうか。それともiOSのvoipアプリ開発者にとっては定石なんでしょうか。

次回はiPhoneをsocket.ioサーバと常時接続させた際のバッテリの消耗について調べてみたいと思います。

iPhoneとsocket.ioサーバを常時接続させる2

前エントリ iPhoneとsocket.ioサーバを常時接続させる1 からの続きです。
フォアグラウンド/バックグラウンド時における接続断と回復方法について調べて、

  • 圏外などで一度接続を失った場合でも電波受信状況が回復したら自動で再接続する

というのが出来るようにしてみます。

フォアグラウンド

フォアグラウンドでアプリが表示されている間はなんら制約もないので特に困った事はありません。
NNSocketIOでは接続失敗時の試行と接続断時の再接続がデフォルトで有効になっているため接続が切れてもすぐに再接続されます。
以下のNNSocketIOOptionsのプロパティとして調整可能ですが、一旦すべてデフォルトのままでいきます。

プロパティ名 意味 デフォルト値
retry 接続失敗時に試行をするかどうか YES
retryDelay 試行するまでの待ち時間初期値 3(sec) ※実施の待ち時間は試行毎に2倍づつ増える
retryDelayLimit 試行待ち時間の最大 1800(sec)
retryMaxAttempts 最大試行回数 -1 ※マイナスは無限大
connectionRecovery 接続断時に再接続するかどうか YES

またiPhoneが圏外となっている場合の試行は無駄なので圏外である場合は自動的に試行を中断、ネットワークが利用可能になってから再度試行を開始できるようにNNReachabilityを使用します。

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    // socket.ioクライアントを作成
    NSURL* url = [NSURL URLWithString:@"http://ホスト名:ポート番号"];
    NNSocketIOOptions* opts = [NNSocketIOOptions options];
    opts.enableBackgroundingOnSocket = YES;
    io = [NNSocketIO io];
    client = [io connect:url options:opts];
    [client on:@"connect" listener:^(NNArgs* args) {
        NSLog(@"接続しました");
    }];
    [client on:@"disconnect" listener:^(NNArgs* args) {
        NSLog(@"切断しました");        
    }];
    // ネットワークの状態をチェック
    reachability = [NNReachability reachabilityForInternetConnection];
    [reachability start];
    return YES;
}

NNReachabilityを開始しておくと圏外やフライトモードなどiPhoneのネットワーク状態が変化した際、NNReachabilityChangedNotificationという種類のイベントをNSNotificationCenterへ通知します。
NNSocketIOは同イベントのオブサーバとなっておりネットワークが使用できない間は試行や再接続を中断、使えるようになった時に再開するようになります。

以上でフォアグラウンド時においてはsocket.ioの接続が極力維持されるようになりました。
続いてバックグラウンドです。

バックグラウンド

フォアグラウンドの時と同じように接続が回復されると思いきやそうではありませんでした。
例えばバックグラウンドで接続中にsocket.ioサーバのnodeプロセスを終了させます。
想定ではiPhone側は再度socket.ioサーバに接続を試みて接続が成功するまで試行するはずでしたが、試行を2回繰り返した後でアプリは停止してしまいました。
試しにアプリをフォアグラウンドに戻すとまた試行を再開し始めました。

なぜアプリが停止したか?

バックグラウンドにおけるアプリとスレッドの関係、スレッドとRunLoopの関係を理解できていないのでここからは完全に自分の推測です。(良い書籍、ドキュメント等あったら教えてください)

まずアプリが停止ってどういう状態なのかですが、NNSocketIOでは接続断を検出するとGCDのdispatch_afterを使用してmain queueに再接続処理を呼び出すBlockを投入しています。
フォアグラウンドではmain queueに投入したBlockは以降のループで実行されるわけですがバックグラウンドでは実行されなくなっています。
つまり接続断に伴い試行を2回ほど繰り返した後RunLoopが停止したように見えます。
ちなみにバックグラウンドで動けない通常アプリ(beginBackgroundTaskWithExpirationHandlerを呼ばず10分の延命権利をもらっていない場合)はバックグラウンドに回ると約5秒程度でRunLoopが停止します。
今回の試行2回というのも約5秒程度です。
この事からvoipタイプのアプリもバックグラウンドでvoip用の接続が切れてしまうとそれ以降の寿命は一般アプリに準ずるのではないかと考えました。
voip接続を維持している間はiOSにvoipの魔法をかけられて魔法が切れると一般アプリに逆戻り。シンデレラ状態です。

上記を検証するためにbeginBackgroundTaskWithExpirationHandlerを呼ぶようにします。
仮定が正しければバックグラウンドに入った後の10分間は接続断が起きても問題なく再接続でき、10分以降はRunLoopが停止して再接続処理は実行されるなくなるはずです。

UIBackgroundTaskIdentifier bgTask;

- (void)applicationDidEnterBackground:(UIApplication *)application
{
    bgTask = [application beginBackgroundTaskWithExpirationHandler:^{
        [application endBackgroundTask:bgTask];
        bgTask = UIBackgroundTaskInvalid;
    }];
}

上記コードを組み込んで再度アプリを実行。一旦接続完了後にアプリをバックグラウンドにした後、nodeのプロセスを落としてみます。

予想通りバックグラウンドに回った後の10分間は再接続を試行し続けました。そして10分過ぎると試行は停止しました。

voipタイプのアプリはどうやって再接続すれば良いのか?

ではvoipタイプのアプリはバックグラウンドにおいてどうやって接続を回復したら良いのでしょうか?どこかにガラスの靴があるはずです。

実はvoipタイプのアプリにはiOSから以下二つの特権を与えられます。

  1. voip宣言した接続はバックグラウンドでも維持できる
  2. 10分に一回10秒だけ能動的に動くことができる

1については今までその恩恵を受けてきました。
そして2です。これがバックグラウンドでも接続断を回復するための方法のようです。

以下のようにsetKeepAliveTimeoutで10分に1回実行したい処理が記述できます。
intervalが600(sec)としていますが600秒以下は指定できないようです。
ここで再接続の処理を実行すればバックグラウンドでも接続を回復できるというわけです。

- (void)applicationDidEnterBackground:(UIApplication *)application
{
    [application setKeepAliveTimeout:600 handler:^{
        ※接続状態が切断だった場合に再接続する処理をここで呼び出す。
    }];
}

setKeepAliveTimeoutでhandlerを登録しておくと10分に1回handlerのBlockが実行され、10秒間RunLoopが回ります。
NNSocketIOの場合、再接続の実施要求は接続断の検出時にmain queueに積まれており、あとはRunLoopが回り始めてくれれば良いだけの状態となっているためBlockに再接続のための処理を記述する必要はありません。

- (void)applicationDidEnterBackground:(UIApplication *)application
{
    [application setKeepAliveTimeout:600 handler:^{
        // 処理の記述は不要
    }];
}

以上でバックグラウンドにおいても接続が回復できるようになりました。
でもこの10秒で再接続ができなかった場合どうなるのでしょうか?
この場合は次の10分まで待たないといけません。つまり1時間で再接続できるチャンスは6回だけ。
もし運悪くたまたまその6回とも圏外に居た場合、1時間ずっと接続されていない状態となります。

まあバッテリの事を考えると10分に1回というのは妥当だと思いますが、もうすこしあがいて
次回はバックグラウンドでの再接続の機会を10分に1回の時間ベースではなくネットワークの状態、Reachabilityの回復タイミングで出来ないか調べてみます。

iPhoneとsocket.ioサーバを常時接続させる1

前エントリ Objective-Cでsocket.ioクライアントを書いてみたZ Cloudでnodejsを動かすiPhoneアプリとsocket.ioサーバを3G経由で接続させる準備が整いました。
次はiOSネイティブのsocket.ioクライアントの特性を活かしてiPhoneとsocket.ioサーバを常時接続に挑戦してみることにします。
実現したい事は以下の通りです。

  • 普通にiPhoneアプリから3G回線経由でクラウド上のsocket.ioサーバと接続できること
  • 接続を維持できること
    • Homeボタン等を押されてiPhoneアプリが終了(バックグラウンドに回る)しても接続を維持する
    • 圏外などで一度接続を失った場合でも電波受信状況が回復したら自動で再接続する

普通に接続してみる

それでまず普通にsocket.ioサーバに接続するようにNNSocketIOをアプリに組み込んでみます。

__strong NNSocketIO* io;
__weak id<NNSocketIOClient> client;

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    NSURL* url = [NSURL URLWithString:@"http://ホスト名:ポート番号"];
    io = [NNSocketIO io];
    client = [io connect:url];
    [client on:@"connect" listener:^(NNArgs* args) {
        NSLog(@"接続しました");
    }];
    [client on:@"disconnect" listener:^(NNArgs* args) {
        NSLog(@"切断しました");        
    }];
    return YES;
}

このアプリを実機で立ち上げてみるとsocket.ioサーバ側のログに接続完了とハートビートをやりとりしているログが表示されているのでひとまず接続は成功。
でもこの状態で別のアプリを使ったりiPhoneをロックさせたりすると接続は失われてしまいます。これは通常アプリがバックグラウンドで動くことをiOSに認められていないためです。

ちなみにUIApplicationのbeginBackgroundTaskWithExpirationHandlerを呼んで「さーせん!バックグラウンドでももうちょっと生きたいです!」とiOSに宣言すると最大10分の生存期間をもらうことができますが、今回の目標は接続を無制限に維持するという事なので違う方法を探すことにします。

iOSに自分はvoipタイプのアプリです!と宣言してみる

でも実際バックグラウンドで動いているっぽい既成のアプリとかあるんだけどこれはどうなってるの?
ということで調べてみると、iOSではある用途のアプリについては特別にバックグラウンドでも動かせてもらえるようです。用途は音声会話系(voip)、位置情報系(location)、音楽再生系(audio)などが定められておりバックグランドではその用途に応じた範囲で動作することができます。

音声会話系のアプリは着呼や会話などで接続を維持しておく必要があり今回の目的を満たせそうなのでサンプルアプリをvoipタイプと宣言して動かしてみることにします。
アプリのInfo.plistにUIBackgroundModesキーでvoipを指定してvoipアプリであることを宣言。

<key>UIBackgroundModes</key>
    <array>
        <string>voip</string>
    </array>
</key>

ちなみに宣言は自由ですがAppStoreにvoipタイプのアプリとして認められることはまた別の問題です。
今回はもちろん開発用のプロビジョニングですのでAppStoreの制約は気にしないで進めます。

加えて先ほどの初期化コードを以下のように修正します。

    NSURL* url = [NSURL URLWithString:@"http://ホスト名:ポート番号"];
    NNSocketIOOptions* opts = [NNSocketIOOptions options];
    opts.enableBackgroundingOnSocket = YES;
    io = [NNSocketIO io];
    client = [io connect:url options:opts];
    [client on:@"connect" listener:^(NNArgs* args) {
        NSLog(@"接続しました");
    }];
    [client on:@"disconnect" listener:^(NNArgs* args) {
        NSLog(@"切断しました");        
    }];
    return YES;

違いはNNSocketIOOptionsを作成しenableBackgroundingOnSocketプロパティをYESに。最後にconnectメソッドの引数にオプションとして与えています。
こうすることでアプリ内でCFReadStreamRef、CFWriteStreamRefを作成する際にiOSに対して「これはvoip用の接続なんでバッググランドでも切らないでください」と宣言するようになります。

以上でアプリがバックグラウンドに回ってもsocket.ioの接続が切れなくなりました。
ちなみにiOSシミュレータではvoipタイプにしても接続が切れてしまいます。バックグラウンドの挙動については実機を使って確認するしかないようです。
次回はフォアグラウンド/バックグラウンド時における接続断とその回復方法についてメモりたいと思います。

Z Cloudでnodejsを動かす

前エントリ Objective-Cでsocket.ioクライアントを書いてみた で作成したsocket.ioクライアントをiPhoneアプリから3G経由でテストしたいのでZ CloudのSmartOSを借りてみました。

コースは一番安いSmall 1にしてみたんですが、どんな感じかというと

  1. OSはSmartOSはOpenSolarisベースのJoyent社によるカスタム
  2. CPUは1コア、状況に応じて2コアに拡張
  3. メモリは1G
  4. ディスクは30GでRAID5相当の冗長化構成
  5. root権限とグローバルIP
  6. ネットワーク転送量は月間20TBまで。超えたら応相談
  7. 料金はマシンの起動時間に応じて。1時間8.5円(税込み)

ということで複雑な料金計算もなく煙草を一本我慢すれば3時間動かせるというリーズナブルな感じです。
PaaSは他にもたくさんあるのですが、今回はwebsocketを使える事が大前提だったのでここにしてみました。

でオンラインから申し込んですぐに利用可能となりました。
さっそく管理画面から公開鍵を登録してsshでログイン。

インストール可能なパッケージにnodejsがあることを確認します。

# pkgin search nodejs
nodejs-0.6.6      Evented I/O for V8 javascript
nodejs-0.4.12    Evented I/O for V8 javascript

0.6.6をインストール。

# pkgin in nodejs-0.6.6

npmも一緒にインストールされて利用可能となりました。

nodejs用のサービスmanifestを確認してみます。

# vi /opt/local/share/smf/manifest/node.xml

自分はsolarisに疎いのですがnode用のサービスインスタンスexampleが定義済みで、/home/node/<インスタンス名>/server.jsを起動するようになっているようです。

ということで/home/node配下にexampleディレクトリを作成しその下にserver.jsを作成します。

exampleのserver.jsをサービスとして有効にします。

# svcadm enable node:example

無事server.jsが起動しました。