2011

10/17

comment
tweetback
tags

AS3 Socket.IO node.js

tukutta

GFS.I.OAS3 - Socket.IO と AS3 で簡単に WebSocket 通信できるライブラリを作った

前回の記事で話にあげていた Socket.IO と AS3 で簡単に WebSocket 通信できるライブラリについて、
何か物を作るのに必要そうな機能を一通り実装できたので github で公開しました。


ライブラリは以下 github からリポジトリをクローンするか、zip をダウンロードして入手できます。


GFS.I.OAS3 - GitHub https://github.com/glassesfactory/GFS.I.OAS3


github リポジトリで、ごく簡単ではありますがサンプルの紹介と使い方の説明をしています。
既に色々と手馴れているかたはリポジトリに書かれている使い方と ASDoc を読めば何となく使えるかと思います。

この記事では、前回 Socket.IO サーバー周りなど色々と省きまくってしまったので、そこも含め解説して行きたいと思います。

作った経緯

前回の記事でも触れましたが、もう少し詳細に。
少し前から記事を書いていたように、このところ趣味や仕事のため、node.js を触っていました。
とりあえず Flash を普段いじっているということもあり、まずはお作法的に Flash Socket Server を立ててみて
あまりの簡単さに感動するなど。
以下前回と同じコードですが、とてもシンプルなコードで Flash Socket Server が立てることができました。

server.js:

var net = require('net');
var server = net.createServer(function(socket) {
    socket.setEncoding('utf8');
   
    socket.on('connect', function() {
       console.log('ほむほむ');
    });
});
  
server.listen(3000);
console.log('Server runninng at:3000');

SocketTest.as:

public function SocketTest(){
     var socket:Socket = new Socket( '127.0.0.1', 3000 );
     socket.addEventListener( Event.CONNECT, _connectedHandler );
     socket.connect();
}
 
private function _connectedHander( event:Event ):void {
     trace("魔法少女なれや");
}

もちろんこのまま進めてもよかったのですが、いろいろと調べて行くと
そもそも node.js でリアルタイムな通信をするのにはどうやら Socket.IO というものを使うのがよさそうだという話。
ざっと調べたところ

  • WebSocket, flash.net.Socket, AJAX long poling, JSONP poling など多彩なプロトコルに対応。
  • ブラウザの後方互換、モバイルサポート
  • 簡単に JSON データでやりとりできる
  • ネームスペース機能によって簡単にチャットルーム的な機能を実装できる

といったメリットが挙げられるようです。
ブラウザの後方互換サポートについては古いブラウザをサポートするつもりなら
最初から Flash でやろうぜという発想だったので正直どうでも良かったのですが
JSON データのやり取りやチャットルーム的な機能を簡単に実装できるのは確かにありがたいですね。
というわけで Socket.IO を採用し、とりあえず Flash から同じように Socket 接続しようと試みたわけです。


思わぬ落とし穴

とりあえず net モジュールと同じ要領で書けばおk と思ってザクザクザクと以下のようなコードを書きました。

var io = require('socket.io').listen(3000);
io.sockets.on('connection', function(socket) {
   //コンソールすら出ない
   console.log('connectiooooon');
});

// イベント名を connection ではなく connect にしても反応なし…
/*
io.sockets.on( 'connect', function(socket) {
   console.log('connectttttt');
});
*/
console.log('SocketServer running port:3000');


しかし、なぜだか Socket.IO サーバーでクライアントからの接続を検知出来ない…
Flash 側では Event.CONNECT がディスパッチされてきているものの、
サーバー側で connection イベントを取れていないという事態。


調べたところ、デフォルトでは flash.net.Socket は有効になっていないとのこと。
それならばと、listen のあとに以下を追加して、flash.net.Socket を有効化してみました。

io.configure(function(){
    io.set('transports', ['websocket', 'flashsocket']);
});

connection イベントが発生しない…
さてこれは困りました。どうにかすれば検知できるのかもしれませんが
あわよくば iPhone や js からも同じサーバーに接続して処理させたいと考えている自分にとってはあまり都合がよくありません。
なんとかならないかと Socket.IO のリポジトリを漁っていたら以下のようなライブラリを発見。


FlashSocket.IO - GitHub https://github.com/simb/FlashSocket.IO


さてどういう風に使うのかなと README やコードを読み始める。
ごめん、使いづらい。
依存してる外部ライブラリが多すぎる上に、web_sokcet.js なるものを自身で HTML に埋め込む必要があるなどとにかく使いづらい。
というか結局 ExternalInterface で js をラップして WebSocket 通信させてるなら自分で作るよ
という流れで今回のライブラリを作り始めたわけです。
大分前置きが長くなってしまいましたが、以下から使い方の解説をしていきたいと思います。



基本的な使い方

github にも書きましたが基本的な使い方から。
自分で js を埋め込んだり、他のライブラリにパスを通す必要はありません。
ビルトインパッケージの API や他のライブラリと同様、インポートしてインスタンス化するだけです。


node.js のテスト環境構築のやり方は以前の記事を参考にしてください。


ひとまずこんなサーバーを用意しておきます。

var io = require('socket.io').listen(3000);
io.sockets.on('connection', funtion(socket){
     socket.on('user add', function(data){
          console.log(data);
          socket.emit('$walpurgis', { 'night':'魔女にはなりたくない' });
    });
});
 

とりあえず繋ぐ:

public function SioTest(){
     _socket = new GFSIOAS3( '127.0.0.1', 3000 );
     _socket.addEventListener( GFSIOAS3Event.CONNECTED, _connectedHandler );
     _socket.connect();
}

private function _connectedHandler( event:GFSIOAS3Event ):void{
     trace("エントロピーを凌駕した");
}


以上のように、GFSIOAS3 をインスタンス化し、addEventListenr で、CONNECT を監視、connect(); を実行することで接続できます。

Socket.IO サーバーと通信:

先程の _connectedHandler を以下のように書き換えつつ、イベントハンドラを追加します。

private function _connectedHandler( event:GFSIOAS3Event ):void{
    _socket.removeEventListener( GFSIOAS3Event.CONNECTED, _connectedHandler );
    _socket.addEvent('$walpurgis');
    _socket.addEventListener( GFSIOAS3Event.PROGRESS, _progressHandler );
    _socket.emit( 'user add', {homu:'homu'});
}

private function _progressHandler( event:GFSIOAS3Event ):void{
    trace(event.eventName, event.data );
}

_socket.addEvent でサーバー側で発生するイベントを登録、
サーバーからイベントが通知されると GFS.I.OAS3 から PROGRESS がディスパッチされるので
progress ハンドラ内でイベントを仕分けてください。
_socket.emit でサーバーにメッセージを送信できます。
以上のサンプルを実行すると、魔女にはなりたくないと出力されるはずです。
残念ですね。

ネームスペース機能

Socket.IO の素敵な機能として、ネームスペースというものがあります。
Socket.IO インスタンスに名前をつけることで、簡単に複数チャットルームを実装できるアレです。
もちろん GFS.I.OAS3 でもこの機能をサポートしています。


まず、以下のようなネームスペースを使ったサーバーを用意します。

ns-test.js:

var io = require('socket.io').listen(3000);

var homu = io.of('/homu').on('connection',function(socket){
    console.log('ほむほむ');
    socket.emit('$msgmsg',{homu:'homu'});
});

var mami = io.of('/mami').on('connection',function(socket){
   console.log('まみさん');
   socket.emit('$msgmsg',{tiro:'finale'});
   socket.on('disconnect',function(data){
       console.log('まみられた');
    });
});


Flash 側は以下のような実装をします。

public function NsTest(){
     _homu = new GFSIOAS3( '127.0.0.1', 3000, '/homu');
     _homu.addEventListener( GFSIOAS3Event.CONNECTED, _homuConnectedHandler );
              
     _mami = new GFSIOAS3( '127.0.0.1', 3000, '/mami' );
     _mami.addEventListener( GFSIOAS3Event.CONNECTED, _mamiConnectedHandler );
              
     _homu.connect();
     _mami.connect();
}

private function _homuConnectedHandler( event:GFSIOAS3Event ):void{
     _homu.removeEventListener( GFSIOAS3Event.CONNECTED, _mamiConnectedHandler );
     _homu.addEventListener( GFSIOAS3Event.PROGRESS, _homuProgressHandler );
     _humu.addEvent('$msgmsg');
}

private function _mamiConnectedHandler( event:GFSIOAS3Event ):void{
     _mami.removeEventListener( GFSIOAS3Event.CONNECTED, _mamiConnectedHandler );
     _mami.addEventListener( GFSIOAS3Event.PROGRESS, _mamiProgressHandler );
     _mami.addEvent('$msgmsg');
}

private function _homuProgressHandler( event:GFSIOAS3Event ):void{
     switch( event.eventName ){
          case '$msgmsg':
               trace("homu: ",event.data);
               break;
          default:
               break;
     }
}
         
private function _newsProgressHandler( event:GFSIOAS3Event ):void{
     switch( event.eventName ){
          case '$msgmsg':
               trace("mami: ",event.data);
               _mami.disconnect();
               break;
          default:
               break;
     }
}


GFSIOAS3 コンストラクタの第3引数にネームスペースを指定してあげることで
Socket.IO のネームスペース機能をサポートすることが出来ます。
あとは普通に接続するの同様、イベントを登録、PROGRESS イベントを受信
接続としてあげれば通信できます。
このサンプルを実行すると Flash 側でマミさんがティロったあと、 Socket.IO 側でマミられてしまうはずです。
disoconnect() メソッドで、サーバーとの接続を切断できます。

ちょっとしたオプション

結局の所、このライブラリは Socket.IO-client を一から実装しているわけではなく、
Socket.IO-client.js をラップしているだけです。
そのため、swf を貼りつけている HTML と同一上に /socket.io/socket.io.js が存在している必要があります。


GFS.I.OAS3 では、そういった手間を省くため、AS から js を生成して貼りつけていますが
URL の変更、そもそも AS から生成させないなどオプションで指定することが出来ます。

例えば、開発しているときわざわざローカルにサーバーを立てるのがめんどくさいとき、
既に稼動しているサーバー上の socket.io.js の場所を指定してあげることで手間を省けます。

GFSIOAS3.socketIOClientURL = 'http://node.hageyama.info/socket.io/socket.io.js';

また、node.js、PHP、Python など他 Web 言語によって動的に生成させたい場合、
以下のオプションを設定することで AS から生成しないように出来ます。

GFSIOAS3.useManualClient = true;

加えて、あまりおすすめしませんが socket.io.js <-> AS3 の仲立ちになっている GFS.I.OAS3.js も
以下のオプションで AS からの生成ではなく、他の手段によって貼り付けることが出来ます。

GFSIOAS3.useLibOnHTML = true;


API 一覧とか

ASDoc を以下に用意しています。
API 一覧は以下でご確認ください。


API Documentation http://glasses-factory.net/lab/gfsioas3/asdoc/


まだ色々とサポートしきれていない部分などはありますが、Socket.IO のリリースを追いかけつつ
ぼつぼつサンプルなど追加していきたいと思います。
質問、要望、バグ報告などありましたら twitter などでお気軽にどうぞ。

blog comments powered by Disqus