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

kkAyatakaのメモ帳。

誰かの役に立つかもしれない備忘録。

AIR.簡易Messengerを作る その3

メッセージ送受信部分を作って完成。

メッセージの受信

メッセージの受信は、

  • ServerSocket待ち受け
  • ServerSocketのConnectイベントで通信用Socket取得
  • 受け取ったSocketにSocketData取得用のListener登録
  • メッセージ受信

といった流れ。


initializeで待ち受け開始して、

private function initializeLogic():void
{
	....
	serverSocket = new ServerSocket();
	serverSocket.addEventListener(ServerSocketConnectEvent.CONNECT, serverSocket_connectHandler);
	serverSocket.bind( PORT_TALK );
	serverSocket.listen();
	...
}


接続があったらSocketにListenerを登録して、

private function serverSocket_connectHandler(event:ServerSocketConnectEvent):void
{
	event.socket.addEventListener(ProgressEvent.SOCKET_DATA, socket_socketDataHandler);
}


メッセージを受信したら、テキストエリアに表示する。

private function socket_socketDataHandler(event:ProgressEvent):void
{
	textArea.text = event.target.readUTF();
}


socketを保持する必要があるのかよく分かってないけど、
まあ、普通はこんな使い方はせんなぁ。
readUTFはreadUTFBytesの方が安全だけど、
とりあえずMessenger同士の通信には問題ないのでOK。

メッセージ送信

メッセージの送信は

  • ListからIP取得
  • Socketを接続
  • 接続が完了したら、テキストエリアの文字列をSend

といった流れ。


ボタン押下をトリガーにIPを取得し、Socketをつなげて、

private function sendBtn_clickLogic():void
{
	if( nodeList.selectedItem ) {
		var address:String = nodeList.selectedItem as String;
		
		var socket:Socket = new Socket(address, PORT_TALK);
		socket.addEventListener(Event.CONNECT, socket_connectHandler);
		socket.addEventListener(IOErrorEvent.IO_ERROR, socket_ioErrorHandler);
	}
}


つながったら、メッセージを送信。

private function socket_connectHandler(event:Event):void
{
	event.target.writeUTF( textArea.text );
	event.target.flush();
	event.target.close();
}

書き込んだらもういらないのでcloseする。

動作テスト

とりあえずこれで完成。
実際にメッセージのやりとりをしてみると、なかなか気持ちよく動いてくれる。


どうせなので、Win、Mac、Linuxの3台体制でテストしてみた。
TCP/IPのメッセージ送受信に関してはどれも問題なくできた。


ただ、Mac(10.5.8)ではLinuxと同じくbroadcastができなかった。
公開されているbeta2より進んだリリースならできるのかねぇ。


一応C言語でbroadcastしてみたら問題なかったので、
AIR 2.0 SDK beta2の問題だとおもふ。


あと、今回始めてDatagramSocketのbroadcastを試してみたけど、
思った以上に取りこぼしがある印象を受けた。


失敗するといっても低確率だと思っていたので、この動作は結構意外。
利用する場合は設計段階から留意しないと、はまるな。

簡易Messenger全ソース

<?xml version="1.0" encoding="utf-8"?>
<mx:WindowedApplication xmlns:mx="http://www.adobe.com/2006/mxml"
	layout="vertical" horizontalAlign="left" showStatusBar="false" fontSize="20"
	width="450" height="400"
	initialize="initializeLogic()" close="closeLogic()">
	
	<!-- logic -->
	<mx:Script>
		<![CDATA[
			import mx.collections.ArrayCollection;
			
			//--------------------------------------------------------------------------------------
			// constants
			//--------------------------------------------------------------------------------------
			/** node検出のbroadcastに使うポート */
			static private const PORT_BROADCAST:int = 50000;
			/** メッセージ送受信に使うポート */
			static private const PORT_TALK:int = 50001;
			
			/** node検出用にbroadcastするメッセージ */
			static private const MSG_SEARCH_NODE:String = "message search node";
			/** node検出メッセージに対して、返信するメッセージ */
			static private const MSG_NODE_RES:String = "message node res";
			
			//--------------------------------------------------------------------------------------
			// logics
			//--------------------------------------------------------------------------------------
			/** application initialize */
			private function initializeLogic():void
			{
				try {
					// broadcastの受信開始
					datagramSocket = new DatagramSocket();
					datagramSocket.addEventListener(DatagramSocketDataEvent.DATA, datagramSocket_dataHandler);
					datagramSocket.bind( PORT_BROADCAST );
					datagramSocket.receive();
					
					// talkメッセージの待ち受け開始
					serverSocket = new ServerSocket();
					serverSocket.addEventListener(ServerSocketConnectEvent.CONNECT, serverSocket_connectHandler);
					serverSocket.bind( PORT_TALK );
					serverSocket.listen();
				}
				catch( e:Error ) {
					trace( e.message );
				}
			}
			
			/** application close */
			private function closeLogic():void 
			{
				// リソース開放
				datagramSocket.close();
				serverSocket.close();
			}
			
			/** search button click */
			private function searchBtn_clickLogic():void
			{
				try {
					// node情報をクリアする
					nodes.removeAll();
					
					// node検出用メッセージをbroadcast
					var ba:ByteArray = new ByteArray();
					ba.writeUTF( MSG_SEARCH_NODE );
					datagramSocket.send(ba, 0, 0, "192.168.0.255", PORT_BROADCAST);
				}
				catch( e:Error ) {
					trace( e.message ); // Unixはsendで例外 AIR 2.0 beta2
				}
			}
			
			/** send button click */
			private function sendBtn_clickLogic():void
			{
				// nodeの選択状態を確認
				if( nodeList.selectedItem ) {
					// 選択されているなら、IPアドレス確保
					var address:String = nodeList.selectedItem as String;
					
					// 選択したnodeにsocketをつなげる
					var socket:Socket = new Socket(address, PORT_TALK);
					socket.addEventListener(Event.CONNECT, socket_connectHandler);
					socket.addEventListener(IOErrorEvent.IO_ERROR, socket_ioErrorHandler);
				}
			}
			
			//--------------------------------------------------------------------------------------
			// handlers
			//--------------------------------------------------------------------------------------
			/** datagram socket data (broadcastメッセージの受け取り) */
			private function datagramSocket_dataHandler(event:DatagramSocketDataEvent):void
			{
				try {
					// メッセージを確保
					var msg:String = event.data.readUTF();
					
					// broadcastメッセージか判定
					if( msg == MSG_SEARCH_NODE ) {
						// node検出用のbroadcastメッセージなので、返信する
						// 送信データ作成
						var ba:ByteArray = new ByteArray();
						ba.writeUTF( MSG_NODE_RES );
						// 送信
						event.target.send(ba, 0, 0, event.srcAddress, PORT_BROADCAST);
					}
					else {
						// broadcastメッセージの返信なので、
						// 送信元のIPアドレスをnode一覧に追加する
						nodes.addItem( event.srcAddress );
					}
				}
				catch( e:Error ) {
					trace( e.message );	// UTF文字列じゃないと例外
				}
			}
			
			/** server socket connect (Talkメッセージ受信用ServerSocketへの接続) */
			private function serverSocket_connectHandler(event:ServerSocketConnectEvent):void
			{
				// socketを取り出して、メッセージ受け取り用Listenerを設定
				event.socket.addEventListener(ProgressEvent.SOCKET_DATA, socket_socketDataHandler);
			}
			
			/** socket socket data (Talkメッセージの受信) */
			private function socket_socketDataHandler(event:ProgressEvent):void
			{
				// メッセージを受け取ったら、メッセージをテキストエリアに表示
				textArea.text = event.target.readUTF();
			}
			
			/** sokcet connect (Talkメッセージ送信用Socketの接続) */
			private function socket_connectHandler(event:Event):void
			{
				// テキストエリアの文字列を、送信
				event.target.writeUTF( textArea.text );
				event.target.flush();
				event.target.close();
			}
			
			/** socket io error (Talkメッセージ送信用Socketのエラー処理) */
			private function socket_ioErrorHandler(event:IOErrorEvent):void
			{
				trace( "socket io error" );
			}
			
			//--------------------------------------------------------------------------------------
			// private members
			//--------------------------------------------------------------------------------------
			/** broadcast用 */
			private var datagramSocket:DatagramSocket;
			/** メッセージ受信用 */
			private var serverSocket:ServerSocket;
			
			/** ローカルネット内のPC */
			private const nodes:ArrayCollection = new ArrayCollection( ["192.168.0.10"] );
		]]>
	</mx:Script>
	
	<!-- components -->
	<mx:Button label="Search" width="140" click="searchBtn_clickLogic()"/>
	<mx:List id="nodeList" width="100%" height="60%" dataProvider="{nodes}"/>
	<mx:TextArea id="textArea" width="100%" height="40%"/>
	<mx:Button label="Send" width="100%" click="sendBtn_clickLogic()"/>

</mx:WindowedApplication>