ServerSocketで遊んでみる

AdobeからAdobe AIR 2の正式版がリリースされましたね。
Adobe AIR 2ではネイティブプロセスAPIのサポートやパフォーマンスの向上など数多くの新機能が盛り込まれていますが、今回はその新機能の一つ、ServerSocketで遊んでみました。


Adobeが公開しているサンプルソース集、Tour de Flexも参考になります。

下準備

まず、Adobe AIR 2で遊ぶ下準備として、AIR 2 SDKをダウンロードしてFlex 4.0 SDKとマージしておく必要があります。詳しくは下記ページを参照の事。

[FLEX AIR2 Mac] Interacting with a native process のサンプルを動かす メモ

処理の流れ

ServerSocketの使い方は、

  1. ServerSocketのインスタンスを作る
  2. イベントハンドラを登録
  3. ポートをバインド
  4. コネクションの待ち受けを開始
  5. (クライアントがコネクションを張ってくる)
  6. 通信路にデータを出力 or 通信路からデータを読み出す
  7. 通信路をクローズ

のように進んでいきます。

インスタンスを作る

インスタンスの作り方は簡単。

var serverSocket:ServerSocket = new ServerSocket();

で、ServerSocketのインスタンスが作れます。

イベントハンドラを登録

ServerSocketにイベントハンドラを登録します。

//通信路が閉じられたときに呼ぶハンドラを登録
serverSocket.addEventListener(Event.CLOSE, closeEventHandler);
//コネクションを確立したときに呼ぶハンドラを登録
serverSocket.addEventListener(ServerSocketConnectEvent.CONNECT, connectEventHandler);
ポートのバインド/コネクション待ち受け

次のようにポートをバインドし、コネクションの確立を待ち受けます。

var port:int = 22222;
serverSocket.bind(port);
serverSocket.listen();

このlisten()を実行した後、クライアントからのコネクション確立要求が受け付けられるようになります。

コネクションの確立/クライアントとの通信

クライアントからのコネクション確立要求が行われ、コネクションが確立されると、ServerSocketConnectEvent.CONNECTをタイプに持つイベントが発行され、今回登録したconnectEventHandlerが呼ばれます。

function connectEventHandler(event:ServerSocketConnectEvent):void{
  var socket:Socket = event.socket;
  
  //socketに対して値の書き出し(wirte~)、読み込み(read~)ができる。
  
}
通信路をクローズ

通信が終わったら、ServerSocketをクローズしましょう。

try{
  serverSocket.close();
}catch(error:Error){
  trace(error.getStackTrace());
}

サンプルソース

そんなわけで、上記ServerSocketを使ったサーバー側のクラス TcpServer.asおよび、サーバー側にアクセスしにいく TcpClient.asです。
実際は上記以外にいろいろ余分な事をしています。(TCPの伝送遅延を測るのが目的のソースなもので。)

TcpServer.as
package org.mineap.AIRSocketTest.tcp
{
	import flash.errors.IOError;
	import flash.errors.IllegalOperationError;
	import flash.events.Event;
	import flash.events.IOErrorEvent;
	import flash.events.ProgressEvent;
	import flash.events.ServerSocketConnectEvent;
	import flash.filesystem.File;
	import flash.filesystem.FileMode;
	import flash.filesystem.FileStream;
	import flash.net.ServerSocket;
	import flash.net.Socket;
	import flash.utils.ByteArray;
	import flash.utils.getTimer;
	
	import org.mineap.AIRSocketTest.util.Logger;

	/**
	 * 
	 * @author shiraminekeisuke(MineAP)
	 * 
	 */
	public class TcpServer
	{
		
		private var _serverSocket:ServerSocket;
		
		private var _sendData:File;
		
		private var _bytes:ByteArray;
		
		private var _responseBytes:ByteArray;
		
		private var logger:Logger = Logger.instance;
		
		private var _progressCount:Number = 0;
		
		private var _startTime:Date;
		
		private var _connectComplete:Date;
		
		private var _firstTime:Boolean;
		
		private var _port:int;
		
		public static const START:String = "Start";
		
		public var START_BYTES:ByteArray = new ByteArray();
		
		/**
		 * コンストラクタです
		 * 
		 */
		public function TcpServer()
		{
			START_BYTES.writeMultiByte(START, "UTF-8");
		}
		
		/**
		 * 
		 * @param file
		 * 
		 */
		public function set sendData(file:File):void{
			this._bytes = null;
			this._sendData = file;
		}
		
		/**
		 * 指定されたポートで待ち受けを開始します。
		 * 
		 * @param port
		 * @return 
		 * 
		 */
		public function listen(port:int, firstTime:Boolean = true):Boolean{
			
			this._port = port;
			this._progressCount = 0;
			
			this._serverSocket = new ServerSocket();
			this._firstTime = firstTime;
			this._startTime = null;
			
			try{
				// リスナ追加
				this._serverSocket.addEventListener(
						Event.CLOSE, closeEventHandler);
				this._serverSocket.addEventListener(
						ServerSocketConnectEvent.CONNECT, connectEventHandler);
				
				logger.put("Portをバインド:" + port);
				// Portをバインド
				this._serverSocket.bind(port);
				
				logger.put("待ち受け開始");
				// 待ち受け開始
				this._serverSocket.listen();
			}catch(error:Error){
				logger.put(error.getStackTrace());
				return false;
			}
			
			return true;
			
		}
		
		/**
		 * ServerSocketを閉じます
		 * 
		 */
		public function close():void{
			
			this._progressCount = 0;
			
			this._responseBytes = null;
			this._startTime = null;
			
			try{
				if(this._serverSocket != null){
					this._serverSocket.close();
					
					this._serverSocket.removeEventListener(
							Event.CLOSE, closeEventHandler);
					this._serverSocket.removeEventListener(
							ServerSocketConnectEvent.CONNECT, connectEventHandler);
					
				}
				this._serverSocket = null;
			}catch(error:Error){
				logger.put(error.getStackTrace());
			}
			
			logger.put("通信をクローズ");
		}
		
		/**
		 * 
		 * @param event
		 * 
		 */
		protected function closeEventHandler(event:Event):void{
			logger.put(event);
		}
		
		/**
		 * 
		 * @param event
		 * 
		 */
		protected function socketCloseEventHandler(event:Event):void{
			if(this._firstTime){
				this._connectComplete;
				logger.put("START受信完了");
				
				var time:String = (new Date().getTime() 
						- this._connectComplete.getTime())/1000.0 + " sec.";
				logger.status = time + " (TCP)";
				
				logger.put("通信遅延(往復?):" + time);
				this._responseBytes = null;
				close();
				this.listen(_port+1, false);
				
			}
		}
		
		/**
		 * Serverが通信を受け取るたびに呼び出されるイベントリスナです。
		 * 
		 * @param event
		 * 
		 */
		protected function connectEventHandler(event:ServerSocketConnectEvent):void{
			// 通信を確立したsocketを受け取る
			var socket:Socket = event.socket;
			socket.addEventListener(ProgressEvent.SOCKET_DATA, socketDataEventHandler);
			socket.addEventListener(Event.CLOSE, socketCloseEventHandler);
			socket.addEventListener(IOErrorEvent.IO_ERROR, ioErrorHandler);
			
			if(this._firstTime){
			
				//初回はSTARTの受信待ち				
				try{
					logger.put("START受信開始");
					
					this._connectComplete = new Date();
					
					this._responseBytes = new ByteArray();
					socket.readBytes(this._responseBytes);
					
				}catch(error:Error){
					logger.put(error.getStackTrace());
				}
				return;
				
			}
			
			var bytes:ByteArray = new ByteArray();
			
			if(this._bytes == null){
				
				logger.put("ファイルの読み込み");
				
				// ファイルの読み込み
				var fileStream:FileStream = new FileStream();
				
				try{
					
					fileStream.open(this._sendData, FileMode.READ);
					fileStream.readBytes(bytes);
					
					logger.put("読み込み済み : " + bytes.length + " bytes");
					
				}catch(error:IOError){
					logger.put(error.getStackTrace());
					fileStream.close();
				}catch(error:SecurityError){
					logger.put(error.getStackTrace());
					fileStream.close();
				}
			
			}else{
				
				// 読み込み済みファイル使う
				logger.put("既にあるデータを使う");
				bytes = this._bytes;
				
			}
			
			if(bytes.length > 0){
				
				this._bytes = bytes;
				
				try{
					logger.put("データをバッファに書き出し");
					// データをバッファに書き出し
					socket.writeBytes(bytes);
					
					logger.put("データをフラッシュ");
					// データを転送
					
					this._startTime = new Date();
					socket.flush();
					
					logger.put("通信路を切断");
					
					socket.close();
					
				}catch(error:IOError){
					logger.put(error.getStackTrace());
					socket.close();
				}
			}else{
				try{
					logger.put("ファイルサイズがゼロ");
					
					logger.put("データをフラッシュ");
					socket.flush();
					
					logger.put("通信路を切断");
					socket.close();
				}catch(error:IOError){
					logger.put(error.getStackTrace());
					socket.close();
				}
			}
				
		}
		
		/**
		 * 
		 * @param event
		 * 
		 */
		protected function ioErrorHandler(event:IOErrorEvent):void{
			logger.put(event);
		}
		
		/**
		 * 
		 * @param event
		 * 
		 */
		protected function socketDataEventHandler(event:ProgressEvent):void{
			this._progressCount++;
			
			if(this._progressCount % 10 == 1){
				logger.put(event.bytesLoaded + " / " + event.bytesTotal);
			}
		}
		
	}
}
TcpClient.as
package org.mineap.AIRSocketTest.tcp
{
	import flash.errors.IOError;
	import flash.events.ErrorEvent;
	import flash.events.Event;
	import flash.events.IOErrorEvent;
	import flash.events.ProgressEvent;
	import flash.events.TimerEvent;
	import flash.net.Socket;
	import flash.sampler.startSampling;
	import flash.utils.ByteArray;
	import flash.utils.Timer;
	import flash.utils.getTimer;
	
	import org.mineap.AIRSocketTest.util.Logger;

	/**
	 * 
	 * @author shiraminekeisuke(MineAP)
	 * 
	 */
	public class TcpClient
	{
		
		private var _server:String;
		private var _port:int;
		private var _socket:Socket;
		private var _bytes:ByteArray;
		private var _firstTime:Boolean;
		
		private var _progressCount:Number = 0;
		private var _totalBytes:Number = 0;
		
		private var _startTime:Date;
		
		private var _connectionStart:Date;
		
		public static const START:String = "Start";
		
		public var START_BYTES:ByteArray = new ByteArray();
		
		private var logger:Logger = Logger.instance;
		
		/**
		 * 
		 * 
		 */
		public function TcpClient()
		{
			START_BYTES.writeMultiByte(START, "UTF-8");
		}
		
		/**
		 * 
		 * @param server
		 * @param port
		 * @return 
		 * 
		 */
		public function connect(server:String, port:int, firstTime:Boolean=true):Boolean{
			
			this._server = server;
			this._port = port;
			this._progressCount = 0;
			this._firstTime = firstTime;
			
			this._socket = new Socket();
			
			try{
				
				this._socket.addEventListener(Event.CONNECT, connectHandler);
				this._socket.addEventListener(Event.CLOSE, closeHandler);
				this._socket.addEventListener(ErrorEvent.ERROR, errorHandler);
				this._socket.addEventListener(IOErrorEvent.IO_ERROR, ioErrorHandler);
				this._socket.addEventListener(ProgressEvent.SOCKET_DATA, dataHandler);
				
				logger.put("接続開始:" + this._server + ", " + this._port);
				
				this._connectionStart = new Date();
				this._socket.connect(this._server, this._port);
				
				return true;
				
			}catch(error:Error){
				logger.put(error.getStackTrace());
				
				try{
					this._socket.close();
				}catch(error:Error){
					
				}
				
			}
			
			return false;
			
		}
		
		/**
		 * 
		 * 
		 */
		public function close():void{
			
			try{
				if(this._socket != null){
					this._socket.flush();
					this._socket.close();
					this._socket = null;
				}
			}catch(error:Error){
//				logger.put(error.getStackTrace());
				try{
					this._socket.close();
				}catch(error:Error){
				}
				
			}
			this._socket = null;
		}
		
		/**
		 * 
		 * @param event
		 * 
		 */
		protected function connectHandler(event:Event):void{
			
			logger.put("接続成功:" + (new Date().getTime() - _connectionStart.getTime())/1000.0 + " sec.");
			
			var socket:Socket = event.target as Socket;
			
			if(this._firstTime){
			
				// 一回目はSTARTを返信
				try{
					
					logger.put("START送信開始");
					
					socket.writeBytes(this.START_BYTES);
					socket.flush();
					socket.close();
					
					logger.put("START送信完了");
					
					logger.put("データ通信用ソケット接続待ち受け開始");
					
					var timer:Timer = new Timer(500, 1);
					timer.addEventListener(TimerEvent.TIMER_COMPLETE, function(event:Event):void{
						connect(_server, _port+1, false);
					});
					timer.start();
					
				}catch(error:Error){
					logger.put(error.getStackTrace());
				}
			
			}else{
				
				// 二回目はバイト列の読み込み
				this._bytes = new ByteArray();
				
				logger.put("バイト列を読み込み");
				
				this._startTime = new Date();
				
				socket.readBytes(this._bytes);
				
				this._startTime = new Date();
				
			}
			
		}
		
		/**
		 * 
		 * @param event
		 * 
		 */
		protected function closeHandler(event:Event):void{
			var endTime:Date = new Date();
			
//			logger.put("closeHandler呼び出し:" + endTime.getTime());
			
			var diff:Number = endTime.getTime() - this._startTime.getTime();
			
			logger.put("Total " + this._totalBytes + " bytes loaded. (" + diff/1000.0 + " sec.)");
			logger.put("通信路が閉じられました");
		}
		
		/**
		 * 
		 * @param event
		 * 
		 */
		protected function errorHandler(event:ErrorEvent):void{
			logger.put("通信時にエラーが発生:" + event);
		}
		
		/**
		 * 
		 * @param event
		 * 
		 */
		protected function ioErrorHandler(event:IOErrorEvent):void{
			logger.put("通信時にエラーが発生:" + event);
		}
		
		/**
		 * 
		 * @param event
		 * 
		 */
		protected function dataHandler(event:ProgressEvent):void{
			this._progressCount++;
			this._totalBytes += event.bytesLoaded;
			
			if(this._progressCount % 10 == 1){
				logger.put(this._totalBytes + " bytes loaded...");
			}
		}
	}
}

以上、ServerSocketを使って遊んでみる、でした。