package org.rubyforge.dango { /** * Dangoのクライアントフレームワーク本体のクラス * */ import flash.net.*; import flash.events.*; import flash.text.*; import flash.utils.*; import flash.system.*; import flash.display.*; import mx.utils.ObjectUtil; import com.adobe.serialization.json.JSON; import flash.events.IEventDispatcher; import flash.events.EventDispatcher; import flash.events.Event; import org.rubyforge.dango.DangoUtil; import org.rubyforge.dango.DangoErrorCode; public class DangoClientFramework implements IEventDispatcher { private var socket:Socket; //ソケット private var dispatcher:EventDispatcher; // Event送出用 private var is_debug:Boolean; // Debugモードかどうかのフラグ private var is_connect:Boolean = false; // 接続完了しているかどうか private var receve_count:uint = 0; // データ受信回数 private var frame_rate:uint = 24; // デフォルトのフレームレート(想定値) private var receive_cache:Array = []; // 受信データの一時保管用のキャッシュ private var recv_not_yet_size:uint = 0; // データ受信時のキャッシュサイズ private var recv_not_yet_str:String = ""; // データ受信時のキャッシュデータ private var polling_timer:Timer; // ポーリング(ハートビート)用タイマーの設定 private var polling_timer_msec:uint = 5000; // ポーリング(ハートビート)用タイマーのミリ秒 private var delay_send_timer:Timer; // 遅延送信用のタイマーの設定 private var delay_send_timer_msec:uint = 1500; // 遅延送信用のタイマーのミリ秒 private var delay_send_cache:Array = []; // 遅延送信用のキャッシュ private var recv_timer_msec:uint; // 受信用タイマーの実行間隔 private var recv_timer:Timer; // 受信用タイマーの追加 private var recv_last_date:Date = new Date(); // 受信用処理落ちチェック用 public var server_time:String = ""; // サーバーの時計 // private var server_host:String = "172.31.1.74"; // private var server_host:String = "localhost"; // private var server_port:int = 15000; private var server_host:String; private var server_port:int; public var sid:int; /** * コンストラクタ * */ public function DangoClientFramework(config:*, disp_obj:DisplayObject = null){ // is_debug = d; // Debugモードかどうかのフラグ if(disp_obj){ frame_rate = disp_obj.stage.frameRate; // フレームレート } // 設定ファイルの読み込み server_host = config.server_host; server_port = config.server_port; is_debug = config.debug; // is_debug = true; var policy_file_protocol:String = config.policy_file_protocol; // var policy_file_port:uint = config.policy_file_port; var policy_file_path:String = config.policy_file_path; // Event送出用 dispatcher = new EventDispatcher(this); // Security.sandboxType trace("Security.sandboxType:" + Security.sandboxType); // policy_file // if(!policy_file_protocol){ policy_file_protocol = "http" }; if(!policy_file_path) { policy_file_path = "/crossdomain.xml" }; // ソケットの生成 socket = new Socket(); // ソケットのイベントリスナーの追加 socket.addEventListener(Event.CONNECT, connectHandler, false); socket.addEventListener(Event.CONNECT, connectHandler, true); socket.addEventListener(Event.CLOSE, closeHandler, false); socket.addEventListener(Event.CLOSE, closeHandler, true); socket.addEventListener(ProgressEvent.SOCKET_DATA, socketDataHandler, false); socket.addEventListener(ProgressEvent.SOCKET_DATA, socketDataHandler, true); socket.addEventListener(SecurityErrorEvent.SECURITY_ERROR, securityErrorHandler, false); socket.addEventListener(SecurityErrorEvent.SECURITY_ERROR, securityErrorHandler, true); socket.addEventListener(IOErrorEvent.IO_ERROR, ioErrorHandler, false); socket.addEventListener(IOErrorEvent.IO_ERROR, ioErrorHandler, true); trace("registered event handling."); // ポリシーファイルの読み込み if(policy_file_protocol){ var url_load_policy_file:String; // url_load_policy_file = "xmlsocket://" + server_host + ":" + server_port; // url_load_policy_file = "http://aiarebaba.hi-fi-net.com/crossdomain.xml"; url_load_policy_file = "http://" + server_host + policy_file_path; trace("url_load_policy_file=" + url_load_policy_file); Security.loadPolicyFile(url_load_policy_file); } // 接続 try{ trace("connectiong... host=" + server_host + " port=" + server_port); socket.connect(server_host, server_port); trace("connected host=" + server_host + " port=" + server_port); } catch(err:Error){ trace("connect error err=" + err + " name=" + err.name + " message=" + err.message); return(void); } // polling用タイマーの設定 var polling_timer:Timer = new Timer(polling_timer_msec, 0); // タイマーの追加 polling_timer.addEventListener(TimerEvent.TIMER, polling_callback); // イベントリスナーの発行 polling_timer.start(); // タイマーの作動開始 // 遅延送信用タイマーの設定 var delay_send_timer:Timer = new Timer(delay_send_timer_msec, 0); // タイマーの追加 delay_send_timer.addEventListener(TimerEvent.TIMER, delay_send_callback); // イベントリスナーの発行 delay_send_timer.start(); // タイマーの作動開始 // fps用タイマーの設定(3フレームごとに動かすよう変更) recv_timer_msec = uint((1000 * 3) / frame_rate); if(is_debug){ trace("DangoClientFramework:recv_timer_msec:" + recv_timer_msec); } recv_timer = new Timer(recv_timer_msec, 0); // タイマーの追加 recv_timer.addEventListener(TimerEvent.TIMER, recv_callback); // イベントリスナーの発行 recv_timer.start(); // タイマーの作動開始 // 接続完了のときに接続完了をサーバーに通知するためのハートビート送信 var hb_id:String = make_heartbeat(); this.send_action("_notice_heart_beat", { "_hb_id": hb_id}, true); // ハートビート送信 } //接続イベントの処理 private function connectHandler(evt:Event):void { is_connect = true; var msg:String = "DangoClientFramework:connectHandler:" + DangoUtil.now2str(); if(is_debug){ trace(msg); } } //切断イベントの処理 private function closeHandler(evt:Event):void { // タイマーが動いていれば止める if(polling_timer != null && polling_timer.running){ polling_timer.stop(); } is_connect = false; var msg:String = "DangoClientFramework:closeHandler:" + DangoUtil.now2str(); if(is_debug){ trace(msg); } this.dispatchEvent(new DangoErrorEvent("DangoError", DangoErrorCode.CloseError, msg)); } //セキュリティエラーイベントの処理 private function securityErrorHandler(evt:SecurityErrorEvent):void { // タイマーが動いていれば止める if(polling_timer != null && polling_timer.running){ polling_timer.stop(); } is_connect = false; var msg:String = "DangoClientFramework:securityErrorHandler:text=" + evt.text + ":" + DangoUtil.now2str(); if(is_debug){ trace(msg); } this.dispatchEvent(new DangoErrorEvent("DangoError", DangoErrorCode.SecurityError, msg)); } //IOエラーイベントの処理 private function ioErrorHandler(evt:IOErrorEvent):void { // タイマーが動いていれば止める if(polling_timer != null && polling_timer.running){ polling_timer.stop(); } is_connect = false; var msg:String = "DangoClientFramework:ioErrorHandler:" + DangoUtil.now2str(); if(is_debug){ trace(msg); } this.dispatchEvent(new DangoErrorEvent("DangoError", DangoErrorCode.IOError, msg)); } /* //プログレスイベントの処理(呼び出し用イベントのディスパッチ) private function socketDataHandler(evt:ProgressEvent):void { receve_count ++; if(is_debug){ trace("DangoClientFramework:socketDataHandler:" + receve_count); } var receive_data:Object = this.receive_notice(); if(recv_not_yet_size == 0){ // 未受信データが無ければ if(receive_data != {}){ // データが空なら無視する var notice_name:String = receive_data["notice"]; if(notice_name == "_notice_sid"){ // 接続直後のsid通知なら this.sid = receive_data["_sid"]; // if(is_debug){ trace("DangoClientFramework:this.sid=" + this.sid + ":" + DangoUtil.now2str()); } if(is_debug){ trace("DangoClientFramework:this.sid=" + this.sid + ":" + receve_count); } } else if(notice_name == "_heart_beat"){ // heart beat通知なら if(is_debug){ trace("DangoClientFramework:_heart_beat:" + receve_count); } } else { // 通常のデータならイベント発生 if(is_debug){ trace("DangoClientFramework:dispatchEvent:dango_" + notice_name + ":" + receve_count); } this.dispatchEvent(new DangoReceiveEvent("dango_" + notice_name, receive_data)); } } else { // データが空なら // if(is_debug){ trace("DangoClientFramework:receive_data is empty." + DangoUtil.now2str()); } if(is_debug){ trace("DangoClientFramework:receive_data is empty."); } } } } */ // プログレスイベントの処理(呼び出し用イベントのディスパッチ) // とにかくキャッシュに入れるだけ private function socketDataHandler(evt:ProgressEvent):void { receve_count ++; if(is_debug){ trace("DangoClientFramework:socketDataHandler:" + receve_count + ":start:" + DangoUtil.now2str()); } var byte_array:ByteArray = new ByteArray; socket.readBytes(byte_array, 0, socket.bytesAvailable); receive_cache.push([byte_array, receve_count]); } // Event送出用 public function addEventListener(type:String, listener:Function, useCapture:Boolean = false, priority:int = 0, useWeakReference:Boolean = false):void{ dispatcher.addEventListener(type, listener, useCapture, priority); } public function dispatchEvent(evt:Event):Boolean{ return dispatcher.dispatchEvent(evt); } public function hasEventListener(type:String):Boolean{ return dispatcher.hasEventListener(type); } public function removeEventListener(type:String, listener:Function, useCapture:Boolean = false):void{ dispatcher.removeEventListener(type, listener, useCapture); } public function willTrigger(type:String):Boolean { return dispatcher.willTrigger(type); } /** * recv_callback * フレームレート単位で動く処理(キャッシュにデータがあればデータ受信) * * @param evt:TimerEvent * @return void */ public function recv_callback(evt:TimerEvent):void { // if(is_debug){ trace("DangoClientFramework:recv_callback:" + DangoUtil.now2str()); } // 前回から時間がかかりすぎている(処理落ちしかけている場合は)スキップ var start_date:Date = new Date(); // if(is_debug){ trace("DangoClientFramework:recv_last_date.time=" + recv_last_date.time + " start_date.time=" + start_date.time); } if(recv_last_date.time > start_date.time - (recv_timer_msec * 1.5)){ if(receive_cache.length > 0){ // while(receive_cache.length > 0){ if(is_debug){ trace("DangoClientFramework:recv_callback:receive_cache.length=" + receive_cache.length + ":" + DangoUtil.now2str()); } var arr:Array = receive_cache.shift(); // Queueとして取り出し var byte_array:ByteArray = arr[0]; var recv_c:uint = arr[1]; if(is_debug){ trace("DangoClientFramework:recv_c=" + recv_c); } var receive_data:Array = this.receive_notice(byte_array); if(recv_not_yet_size == 0){ // 未受信データが無ければ、ひとつのデータ受信が完了とみなす if(receive_data != []){ // データが空じゃなければ処理開始 for(var i:uint = 0; i < receive_data.length; i++){ var notice_name:String = receive_data[i]["notice"]; server_time = receive_data[i]["server_time"]; if(notice_name == "_notice_sid"){ // 接続直後のsid通知なら this.sid = receive_data[i]["_sid"]; if(is_debug){ trace("DangoClientFramework:this.sid=" + this.sid + " recv_c:" + recv_c + " server_time:" + server_time); } } else { // 通常のデータならイベント発生 if(is_debug){ trace("DangoClientFramework:dispatchEvent:dango_" + notice_name + ": recv_c:" + recv_c + ":" + i + " server_time:" + server_time); } this.dispatchEvent(new DangoReceiveEvent("dango_" + notice_name, receive_data[i])); } } } else { // データが空なら // if(is_debug){ trace("DangoClientFramework:receive_data is empty." + DangoUtil.now2str()); } if(is_debug){ trace("DangoClientFramework:receive_data is empty."); } } } /* // 処理に時間がかかりすぎている場合は、スキップさせる(while用) if(is_debug){ trace("DangoClientFramework:recv_callback:end_date:" + DangoUtil.now2str()); } var end_date:Date = new Date(); if(start_date.time < end_date.time - recv_timer_msec){ if(is_debug){ trace("DangoClientFramework:break"); } break; } */ } } recv_last_date = new Date(); } /** * polling_callback * ハートビート用タイマーコールバック * * @param evt:TimerEvent * @return void */ public function polling_callback(evt:TimerEvent):void { if(is_debug){ trace("DangoClientFramework:polling_callback:" + DangoUtil.now2str() ); } if(socket.connected){ // var send_obj:Object = null; // this.send_data_to_server(send_obj); var hb_id:String = make_heartbeat(); // if(is_debug){ trace("DangoClientFramework:send _notice_heart_beat:" + hb_id + ":" + DangoUtil.now2str()); } this.send_action("_notice_heart_beat", { "_hb_id": hb_id}); // ハートビート送信 } } /** * polling_callback * ハートビートの作成 * * @return String */ public function make_heartbeat():String { return(String((new Date()).time) + String(this.sid)); } /** * delay_send_callback * 遅延送信用タイマーコールバック * * @param socket:Socket * @param send_obj:Object * @return void */ public function delay_send_callback(evt:TimerEvent):void { // if(is_debug){ trace("DangoClientFramework:delay_send_callback:evt:" + evt); } if(!is_connect){ return(void); } if(socket.connected){ var send_obj_dup:Array; var i:uint; for (i = 0; i < 5; i++) { if(delay_send_cache.length == 0) { break; } send_obj_dup = delay_send_cache.pop(); // データをすぐ送信 this.send_data_to_server(send_obj_dup); if(is_debug){ trace("DangoClientFramework:delay_send_callback:sent:" + DangoUtil.now2str()); } } } } /** * send data to server. * クライアント側から使うサーバーへのデータ送信メソッド * * @param socket:Socket * @param send_obj:Object * @return void */ public function send_action(action_name:String, send_obj:Object, delay:Boolean=false):void { if(is_debug){ trace("DangoClientFramework:send_action:start:" + action_name + ":" + DangoUtil.now2str()); } // if(!is_connect){ throw new DangoError("error:not connect" , 29); } // 接続されていない場合はエラー // 送信データの作成 var send_obj_dup:Object = ObjectUtil.copy(send_obj); send_obj_dup["action"] = action_name; send_obj_dup["_return_id"] = (new Date()).time; // delayフラグがあったり、接続がまだなら、遅延送信用のキャッシュにデータを入れる if(delay || !is_connect){ delay_send_cache.push([send_obj_dup]); if(is_debug){ trace("DangoClientFramework:send_action:delay_pull:" + action_name + ":" + DangoUtil.now2str()); } return(void); } // データをすぐ送信 this.send_data_to_server([send_obj_dup]); if(is_debug){ trace("DangoClientFramework:send_action:end:" + action_name + ":" + DangoUtil.now2str()); } } /** * send data to server. * フレームワーク側のデータ送信の一般処理 * * @param socket:Socket * @param send_obj:Object * @return void */ public function send_data_to_server( send_obj:Array ):void { // if(is_debug){ trace("DangoClientFramework:send_data_to_server:send_obj=" + ObjectUtil.toString(send_obj)); } var type:int = 0; // データが空ならJSONencodeしない var send_obj_str:String; if(send_obj == null){ send_obj_str = "\n"; } else { send_obj_str = JSON.encode(send_obj) + "\n"; } var send_obj_size:int = DangoUtil.string_byte_length(send_obj_str); // if(is_debug){ trace("DangoClientFramework:send:" + type + ":" + send_obj_size + ":" + send_obj_str); } // 長さ送信 var byte_array:ByteArray = new ByteArray; byte_array.writeByte(type); byte_array.writeUnsignedInt(send_obj_size); socket.writeBytes(byte_array, 0, 5); socket.writeUTFBytes("\n"); socket.flush(); // データ送信 socket.writeUTFBytes(send_obj_str); socket.flush(); // if(is_debug){ trace("DangoClientFramework:send_obj_str:" + send_obj_str + ":" + DangoUtil.now2str()); } } /** * receive data from server. * フレームワーク側のデータ受信の一般処理 * * @return Object */ public function receive_notice(byte_array:ByteArray):Array { if(is_debug){ trace("DangoClientFramework:receive_notice:" + DangoUtil.now2str()); } // 変数定義 var recv_data_orig:String = ""; // まず読めるデータをすべてByteArrayに入れる // var byte_array:ByteArray = new ByteArray; // socket.readBytes(byte_array, 0, socket.bytesAvailable); // 読んだデータの長さ取得 var byte_read_size:uint = byte_array.length; if(recv_not_yet_size == 0){ // 未受信データが無ければ // if(is_debug){ trace("DangoClientFramework:recv_not_yet_size==0"); } if(byte_read_size < 6){ // きちんとデータが届いていなければ this.dispatchEvent(new DangoErrorEvent("DangoError", DangoErrorCode.IOError, "byte_read_size is too short.")); return([]); } // 長さを取得 var byte_array_size:ByteArray = new ByteArray; try{ byte_array.readBytes(byte_array_size, 0, 5); } catch(err:Error){ this.dispatchEvent(new DangoErrorEvent("DangoError", DangoErrorCode.IOError, "failed to byte_array.readBytes.")); return([]); } var type:int = byte_array_size.readByte(); recv_not_yet_size = byte_array_size.readUnsignedInt(); var crlf:String = byte_array.readUTFBytes(1); byte_read_size -= 6; // if(is_debug){ trace("DangoClientFramework:recv_not_yet_size=" + recv_not_yet_size); } } // 読めているだけ読んで、その分recv_not_yet_sizeを減らす if(recv_not_yet_size > byte_read_size){ // if(is_debug){ trace("DangoClientFramework:byte_read_size=" + byte_read_size); } recv_data_orig = byte_array.readUTFBytes(byte_read_size); recv_not_yet_size -= byte_read_size; }else{ // if(is_debug){ trace("DangoClientFramework:recv_not_yet_size=" + recv_not_yet_size); } recv_data_orig = byte_array.readUTFBytes(recv_not_yet_size); recv_not_yet_size = 0; } // if(is_debug){ trace("DangoClientFramework:recv_data_orig:" + recv_data_orig); } // 全データを受信したかどうかを確認 if(recv_not_yet_size == 0){ // 受信完了ならJSONパース var recv_data:String = recv_not_yet_str + recv_data_orig; recv_not_yet_str = ""; // if(is_debug){ trace("DangoClientFramework:recieve:data=" + ObjectUtil.toString(recv_data)); } var ret_obj_data:Array; if(recv_data && recv_data != "" && recv_data != "\n"){ // データが空じゃないならdecode ret_obj_data = JSON.decode(recv_data) as Array; } else { // データが空なら空データを作ってreturn return([]); } // ret_obj_dataがObjectでnoticeが存在しているかのチェック if(!(ret_obj_data is Array) || !(ret_obj_data[0]["notice"] is String)){ // if(!is_connect){ throw new DangoError("error:recieve data is invalid." , 29); } throw new DangoError("error:recieve data is invalid." , 29); } /* if(ret_obj_data["notice"] == "_notice_sid"){ if(is_debug){ trace("DangoClientFramework:recieve:_notice_sid:no response:" + receve_count + ":" + DangoUtil.now2str()); } }else{ var response_notice_name:String; response_notice_name = "_response"; if(is_debug){ trace("DangoClientFramework:sending:response:notice_name=" + ret_obj_data["notice"] + ":" + ret_obj_data["_id"] + ":" + receve_count + ":" + DangoUtil.now2str()); } this.send_action(response_notice_name, {"_id":ret_obj_data["_id"]}); // 受信完了確認の為に空データを送る } */ return(ret_obj_data); } else { // まだデータが残っているなら空を返す recv_not_yet_str += recv_data_orig; return([]); } } } }