這類的應用通常 server 端都是以 socket server 實作,你可以找 open source 的 java server,或是 Adobe Flash Media Server (FMS) 皆可,然後就可以自己撰寫 server 端的商業邏輯、遊戲引擎。然而若是你不想多學一套程式語言 (通常是 Java 或 .NET),只想使用 ActionScript 的話,而 FMS 的 Server 開發使用 AS2 又令你覺得很不習慣的話,可以嘗試使用 Adobe AIR 2 自己寫一個 socket server。
以下是一個簡單的聊天室範例:
首先,server 使用 Adobe AIR 的專案開發;server 端的使用者介面,通常也不太需要甚麼介面,大多是管理工具或報表,這裡僅提供 "連線" 按鈕與 log 訊息畫面:
ChatServer.mxml
<?xml version="1.0" encoding="utf-8"?>
<s:WindowedApplication xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx"
xmlns:server="server.*"
width="400" height="400"
creationComplete="windowedapplication1_creationCompleteHandler(event)"
>
<fx:Script>
<![CDATA[
import mx.events.FlexEvent;
protected function windowedapplication1_creationCompleteHandler(event:FlexEvent):void
{
this.nativeWindow.addEventListener(Event.CLOSING, onClosing);
}
private function onClosing(e:Event):void{
dispatchEvent(new Event('WINDOW_CLOSE'))
}
]]>
</fx:Script>
<fx:Declarations>
<server:MyContext contextView="{this}" />
</fx:Declarations>
<s:layout>
<s:VerticalLayout />
</s:layout>
<s:HGroup>
<s:Button id="btnConnect" label="Connect"
click="dispatchEvent(new Event('CLICK_CONNECT'))" />
<s:Button id="btnClose" label="Close"
click="dispatchEvent(new Event('CLICK_CLOSE'))" />
</s:HGroup>
<s:TextArea id="txtMsg" width="100%" height="100%" />
</s:WindowedApplication>
我這個範例所使用的 framework 是 Robotlegs,雖然這麼單純的範例根本不用使用任何 framework 比較容易理解,不過,就當作是練習ㄅㄟ;在 Context 中註冊必要的 Service、Mediator:
MyContext
package server
{
import flash.display.DisplayObjectContainer;
import org.robotlegs.mvcs.Context;
import server.mediator.MyMediator;
import server.service.IMyService;
import server.service.MyService;
public class MyContext extends Context
{
public function MyContext(contextView:DisplayObjectContainer=null, autoStartup:Boolean=true)
{
super(contextView, autoStartup);
}
override public function startup():void{
//service
this.injector.mapSingletonOf(IMyService, MyService);
//mediator
this.mediatorMap.mapView(ChatServer, MyMediator);
super.startup();
}
}
}
Service 因為會換,所以通常都會設計 interface 來規範:
IMyService
package server.service
{
public interface IMyService
{
function connect(ip:String, port:int):void;
function close():void;
}
}
負責建立 ServerSocket、並存放一個 Socket clients 陣列 的管理物件,也負責接收、廣播訊息:
MyService
package server.service
{
import flash.events.ProgressEvent;
import flash.events.ServerSocketConnectEvent;
import flash.net.ServerSocket;
import flash.net.Socket;
import flash.utils.getTimer;
import org.robotlegs.mvcs.Actor;
import server.event.ServiceEvent;
public class MyService extends Actor implements IMyService
{
private var _server:ServerSocket;
private var _sockets:Vector.<Socket>;
public function connect(ip:String, port:int):void
{
this.dispatch(new ServiceEvent("start listen"));
_sockets = new Vector.<Socket>();
_server = new ServerSocket();
_server.addEventListener(ServerSocketConnectEvent.CONNECT, onConnect);
_server.bind(port, ip);
_server.listen();
}
public function close():void{
if(_sockets){
for(var i:int=0, len:int=_sockets.length; i<len; i++){
_sockets[i].close();
_sockets[i] = null;
}
_sockets = null;
}
if(_server){
_server.close();
_server = null;
}
this.dispatch(new ServiceEvent("closed"));
}
private function onConnect(e:ServerSocketConnectEvent):void{
this.dispatch(new ServiceEvent("client in"));
var socket:Socket = e.socket;
socket.addEventListener(ProgressEvent.SOCKET_DATA, onSocketData);
_sockets.push(socket);
}
private function onSocketData(e:ProgressEvent):void{
var socket:Socket = Socket(e.currentTarget);
var msg:String = socket.readUTF();
this.dispatch(new ServiceEvent("receive:" + msg));
broadcast(msg);
}
private function broadcast(msg:String):void{
for(var i:int=_sockets.length-1; i>=0; i--){
if(_sockets[i].connected){
this.dispatch(new ServiceEvent("broadcast to client:" + i));
_sockets[i].writeUTF("[SVR.TIME:" + getTimer() + "]" + msg);
_sockets[i].flush();
}else{
this.dispatch(new ServiceEvent("remove client"));
_sockets.splice(i, 1);
}
}
}
}
}
透過 Event 通知 Mediator 作 log:
ServiceEvent
package server.event
{
import flash.events.DataEvent;
public class ServiceEvent extends DataEvent
{
static public const SERVICE_MESSAGE:String = "serviceMessage";
public function ServiceEvent(data:String="")
{
super(SERVICE_MESSAGE, false, false, data);
}
}
}
使用者介面,監聽 view component 的連線事件,監聽 context 系統中當發生有 service message 要顯示時顯示到 view component 上:
MyMediator
package server.mediator
{
import flash.events.Event;
import org.robotlegs.mvcs.Mediator;
import server.event.ServiceEvent;
import server.service.IMyService;
public class MyMediator extends Mediator
{
[Inject]
public var viewComp:ChatServer;
[Inject]
public var service:IMyService;
override public function onRegister():void{
this.addViewListener("WINDOW_CLOSE", onWindowClose);
this.addViewListener("CLICK_CONNECT", onClickConnect);
this.addViewListener("CLICK_CLOSE", onClickClose);
this.addContextListener(ServiceEvent.SERVICE_MESSAGE, onServiceMessage);
}
private function onWindowClose(e:Event):void{
service.close();
}
private function onClickConnect(e:Event):void{
service.connect("127.0.0.1", 9999);
}
private function onClickClose(e:Event):void{
service.close();
}
private function onServiceMessage(e:ServiceEvent):void{
viewComp.txtMsg.appendText(e.data + "\r");
}
}
}
準備好 server 端之後,接下來是 client 端程式,使用一般的 flex web 專案開發,除了一些呈現訊息的文字欄位之外,另外多提供 input text 供使用者輸入對話訊息:
ChatClient.mxml
<?xml version="1.0" encoding="utf-8"?>
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx"
minWidth="400" minHeight="400" xmlns:client="client.*">
<fx:Declarations>
<client:MyContext contextView="{this}" />
</fx:Declarations>
<s:layout>
<s:VerticalLayout />
</s:layout>
<s:Button id="btnConnect" label="Connect"
click="dispatchEvent(new Event('CLICK_CONNECT'))" />
<s:TextArea id="txtMsg" width="100%" height="100%" />
<s:HGroup>
<s:TextInput id="txtInput" width="300" />
<s:Button id="btnSend" label="Send"
click="dispatchEvent(new Event('CLICK_SEND'))" />
</s:HGroup>
</s:Application>
註冊相關 service、mediator:
MyContext
package client
{
import client.mediator.MyMediator;
import client.service.IMyService;
import client.service.MyService;
import flash.display.DisplayObjectContainer;
import org.robotlegs.mvcs.Context;
public class MyContext extends Context
{
public function MyContext(contextView:DisplayObjectContainer=null, autoStartup:Boolean=true)
{
super(contextView, autoStartup);
}
override public function startup():void{
//service
this.injector.mapSingletonOf(IMyService, MyService);
//mediator
this.mediatorMap.mapView(ChatClient, MyMediator);
super.startup();
}
}
}
client 端用到的 service 會有 say() 方法:
IMyService
package client.service
{
public interface IMyService
{
function connect(ip:String, port:int):void;
function say(msg:String):void;
}
}
service 中主要做的事情,就是透過 Socket 物件,連向 server,並接收訊息:
MyService
package client.service
{
import client.event.ServiceEvent;
import flash.events.Event;
import flash.events.ProgressEvent;
import flash.net.Socket;
import org.robotlegs.mvcs.Actor;
public class MyService extends Actor implements IMyService
{
private var _socket:Socket;
private var _meNamy:String = "client-" + int(Math.random()*1000);
public function connect(ip:String, port:int):void
{
this.dispatch(new ServiceEvent("start connect"));
_socket = new Socket();
_socket.addEventListener(Event.CONNECT, onConnect);
_socket.addEventListener(Event.CLOSE, onClose);
_socket.addEventListener(ProgressEvent.SOCKET_DATA, onSocketData);
_socket.connect(ip, port);
}
private function onConnect(e:Event):void{
this.dispatch(new ServiceEvent("connect success"));
say("hello ev8d!!");
}
private function onClose(e:Event):void{
this.dispatch(new ServiceEvent("closed"));
}
private function onSocketData(e:ProgressEvent):void{
var msg:String = _socket.readUTF();
this.dispatch(new ServiceEvent("receive:" + msg));
}
public function say(msg:String):void{
//this.dispatch(new ServiceEvent("say:" + msg));
_socket.writeUTF(_meNamy + ":" + msg);
_socket.flush();
}
}
}
用來傳遞資料的事件:
ServiceEvent
package client.event
{
import flash.events.DataEvent;
public class ServiceEvent extends DataEvent
{
static public const SERVICE_MESSAGE:String = "serviceMessage";
public function ServiceEvent(data:String="")
{
super(SERVICE_MESSAGE, false, false, data);
}
}
}
使用者介面,監聽相關事件,將 service 與 view component 的互動串起來:
MyMediator
package client.mediator
{
import client.event.ServiceEvent;
import client.service.IMyService;
import flash.events.Event;
import org.robotlegs.mvcs.Mediator;
public class MyMediator extends Mediator
{
[Inject]
public var viewComp:ChatClient;
[Inject]
public var service:IMyService;
override public function onRegister():void{
this.addViewListener("CLICK_CONNECT", onClickConnect);
this.addViewListener("CLICK_SEND", onClickSend);
this.addContextListener(ServiceEvent.SERVICE_MESSAGE, onServiceMessage);
}
private function onClickConnect(e:Event):void{
service.connect("127.0.0.1", 9999);
}
private function onClickSend(e:Event):void{
var msg:String = viewComp.txtInput.text;
service.say(msg);
}
private function onServiceMessage(e:ServiceEvent):void{
viewComp.txtMsg.appendText(e.data + "\r");
}
}
}
測試畫面:
簡單的雛型,說明了如何用 Adobe AIR 寫 ServerSocket,可藉此延伸開發成多人連線遊戲。更完整的說明、教學,請參考 adobe 官網的文章:
Creating a socket server in Adobe AIR 2
http://www.adobe.com/devnet/air/flex/articles/creating_socket_server.html

0 意見:
張貼意見