ActionScript 3是Adobe公司开发的用于编写Flash的脚本语言。Adobe新推出的Adobe Flex的Rich Internet Application开发平台同样支持Action Script。ActionScript编写的Flex Data Service提供了丰富的数据处理功能,也包括实现了通过建立HTTPChannel的数据实时更新功能,例如聊天室,股市行情等。本文将使用ActionScript 3.0编写HTTPTunnel Client取代Flex Data Service的HTTPChannel, 用开源的Java HTTPTunnel作为Server,实现数据实时更新。
1 架构
Flash Web Browser |
JHTTPTunnel Server
|
3 HTTPHeader+Content Data |
1 Post 2 Get |
1. Flash客户端连接HTTP Server并向HTTP Server发送Post命令。
2. Flash客户端连接HTTP Server并向HTTP Server发送Get命令。
3. HTTP Server向Flash不断发送遵循HTTP协议的数据,直到Flash客户端发送Close命令关闭连接。
4.Flash客户端解析接受到的数据并更新界面。
2.实现
2.1 客户端
MXML-类似于XML语言,用于部署Flash界面,可被Flex SDK编译为Flash文件。
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" width="620" height="341"
creationComplete="Refresh()">
<mx:Script>
<![CDATA[
import org.colimas.http.*;
import mx.collections.*;
[Bindable]
public var initDG:ArrayCollection;
var host:String="localhost";
var hport:int=8888;
var jhtc:HttpTunnelClient=new HttpTunnelClient(host, hport);
private var DGArray:Array = [
{ corp:'IBM', last:79.99},
{ corp:'MS', last:30.99}];
private function Refresh():void {
trace("start...");
Data1.text="Started";
Data2.text="Yes!";
jhtc.setInBound(new InTunnelSocket());
jhtc.setOutBound(new OutTunnelSocket());
initDG=new ArrayCollection(DGArray);
jhtc.connect();
jhtc.Register(initDG);
jhtc.close();
var myTimer:Timer=new Timer(300);
myTimer.addEventListener("timer", timerHandler);
myTimer.start();
}
public function timerHandler(event:TimerEvent):void {
initDG.refresh();
}
]]>
</mx:Script>
<mx:Text x="41" y="38" text="Text1" width="62" height="28" id="Data1"/>
<mx:Text x="124" y="38" text="Text2" width="62" height="28" id="Data2"/>
<mx:DataGrid x="39" y="86" width="542" editable="false" id="Stock" dataProvider="{initDG}">
<mx:columns>
<mx:DataGridColumn headerText="Corp." dataField="corp"/>
<mx:DataGridColumn headerText="Last" dataField="last"/>
</mx:columns>
</mx:DataGrid>
</mx:Application>
界面显示如下:
Refresh()函数实现数据刷新。org.colimas.http.HttpTunnelClient类用ActionScript语言编写,实现HTTPTunnel客户端,完成连接HTTPTunnel并接受数据任务。
org.colimas.http.HttpTunnelClient实现:
package org.colimas.http
{
import flash.utils.ByteArray;
import flash.errors.IOError;
import mx.collections.ArrayCollection;
public class HttpTunnelClient extends HttpTunnel
{
static private var CONTENT_LENGTH:int=1024*10;
private var init:Boolean=false;
private var closed:Boolean=false;
private var dest_host:String=null;
private var dest_port:int=0;
private var proxy:Proxy=null;
private var ib:InTunnel=null;
private var ob:OutTunnel=null;
public function HttpTunnelClient( host:String, port:int){
this.dest_host=host;
this.dest_port=port;
}
/*传入用于界面显示的数据源*/
public function Register(DGArray:ArrayCollection):void{
this.ib.setData(DGArray);
}
public function setProxy( host:String, port:int):void{
this.proxy=new Proxy(host, port);
}
public function connect():void{
if(ib==null){
trace("InTunnel is not given");
return;
}
ib.setHost(dest_host);
ib.setPort(dest_port);
ib.setProxy(proxy);
if(ob==null){
trace("OutTunnel is not given");
return;
}
ob.setHost(dest_host);
ob.setPort(dest_port);
ob.setProxy(proxy);
ob.setContentLength(CONTENT_LENGTH);
getOutbound();
getInbound();
}
/*数据发送OutTunnel类连接服务器端*/
private function getOutbound():void{
if(closed){
trace("broken pipe");
return;
}
ob.connect();
}
/*数据接受InTunnel连接服务器端*/
private function getInbound():void{
ib.connect();
}
var buf_len:int=0;
public function close():void{
sendClose()
ib.close()
ob.close()
closed=true;
}
public function setInBound( ib:InTunnel):void { this.ib=ib; }
public function setOutBound( ob:OutTunnel):void{ this.ob=ob; }
}
}
数据发送OutTunnel类的实现
package org.colimas.http
{
import flash.net.Socket;
import flash.utils.ByteArray;
import flash.events.DataEvent;
import flash.events.Event;
import flash.events.ErrorEvent;
import flash.events.IOErrorEvent;
import flash.events.ProgressEvent;
public class OutTunnelSocket extends OutTunnel
{
static private var _rn:String="/r/n";
private var socket:Socket=null;
private var request:String="/index.html?crap=1 HTTP/1.1";
public function OutTunnelSocket(){
super();
}
private function errorHandler(event:ErrorEvent):void
{
trace("[" + event.type + "] " + event.toString());
}
private function ioErrorHandler(event:IOErrorEvent):void
{
trace("[" + event.type + "] " + event.toString());
}
/*连接后发送POST请求*/
private function connectHandler(event:Event):void {
trace("Out[" + event.type + "] " + event.toString());
socket.writeUTF(request);
socket.writeUTF(_rn);
socket.writeUTF("Content-Length: "+getContentLength());
socket.writeUTF(_rn);
socket.writeUTF("Connection: close");
socket.writeUTF(_rn);
socket.writeUTF("Host: "+getHost()+":"+getPort());
socket.writeUTF(_rn);
socket.writeUTF(_rn);
socket.flush();
sendCount=getContentLength();
socket.writeByte(HttpTunnel.TUNNEL_OPEN);
socket.writeByte(0);
socket.writeByte(1);
socket.writeByte(0);
}
public override function connect():void{
close();
var host:String=getHost();
var port:int=getPort();
var p:Proxy=getProxy();
if(p==null){
socket=new Socket(host, port);
request="POST "+request;
}
else{
var phost:String=p.getHost();
var pport:int=p.getPort();
socket=new Socket(phost, pport);
request="POST http://"+host+":"+port+request;
}
socket.addEventListener(Event.CONNECT, connectHandler);
socket.addEventListener(ErrorEvent.ERROR, errorHandler);
socket.addEventListener(IOErrorEvent.IO_ERROR, ioErrorHandler);
}
/*用于向Server发送命令*/
public override function sendData(foo:ByteArray, s:int, l:int, flush:Boolean):void{
if(l<=0) return;
if(sendCount<=0){
trace("1#");
connect();
}
var retry:int=2;
while(retry>0){
try{
socket.writeBytes(foo, s, l);
if(flush){ socket.flush(); }
sendCount-=l;
return;
}catch(e:Error){
trace(e.message);
connect();
retry--;
}
}
}
public override function close():void{
if(socket!=null && socket.connected){
socket.close();
socket=null;
}
}
}
}
数据发送InTunnel类的实现
package org.colimas.http
{
import flash.utils.ByteArray;
import flash.net.Socket;
import flash.events.DataEvent;
import flash.events.Event;
import flash.events.ErrorEvent;
import flash.events.IOErrorEvent;
import flash.events.ProgressEvent;
import mx.collections.ArrayCollection;
public class InTunnelSocket extends InTunnel
{
static private var _rn:String="/r/n";
private var socket:Socket=null;
private var request:String="/index.html?crap=1 HTTP/1.0";
private var receivedData:String;
private var ready:int;
private var DGArray:ArrayCollection;
public function InTunnelSocket(){
super();
}
public override function setData(DGArray:ArrayCollection){
this.DGArray=DGArray;
}
private function errorHandler(event:ErrorEvent):void
{
trace("[" + event.type + "] " + event.toString());
}
private function ioErrorHandler(event:IOErrorEvent):void
{
trace("[" + event.type + "] " + event.toString());
}
/*连接后发送数据接受请求*/
private function connectHandler(event:Event):void {
trace("In[" + event.type + "] " + event.toString());
ready=0;
socket.writeUTFBytes(request);
socket.writeUTFBytes(_rn);
socket.flush();
}
/*接受到数据后,解析数据*/
private function dataHandler(event:ProgressEvent):void {
try{
var tmp:Array=new Array();
receivedData=socket.readUTFBytes(socket.bytesAvailable);
if(receivedData==null)
return;
var lines:Array=receivedData.split("/r/n");
if(lines.length==0)
return;
var got:Boolean;
got=false;
for each (var i:String in lines){
if(got==true){
var stocks:Array=i.split(" ");
if(stocks.length!=2)
return;
tmp=[
{corp:'IBM',last:stocks[0]},
{corp:'MS',last:stocks[1]}
];
this.DGArray.source=tmp;
this.DGArray.refresh();
}
if(i=="Content-Type: text/html; charset=iso-8859-1")
got=true;
}
}catch(e:Error)
{
//var tmp:int=socket.readByte();
}
}
private function closeHandler(event:Event):void
{
trace("In[" + event.type + "] " + event.toString());
}
public override function connect():void{
close();
var host:String=getHost();
var port:int=getPort();
var p:Proxy=getProxy();
if(p==null){
socket=new Socket(host, port);
request="GET "+request;
}
else{
var phost:String=p.getHost();
var pport:int=p.getPort();
socket=new Socket(phost, pport);
request="GET http://"+getHost()+":"+getPort()+request;
}
socket.addEventListener(Event.CONNECT, connectHandler);
socket.addEventListener(ErrorEvent.ERROR, errorHandler);
socket.addEventListener(IOErrorEvent.IO_ERROR, ioErrorHandler);
socket.addEventListener(ProgressEvent.SOCKET_DATA, dataHandler);
socket.addEventListener(Event.CLOSE,closeHandler);
}
public override function receiveData():String{
return this.receivedData;
}
public override function close():void{
if(socket!=null && socket.connected==true){
socket.close();
socket=null;
}
}
}
}
其他
package org.colimas.http
{
public class HttpTunnel
{
public static var TUNNEL_OPEN:int=1;
public static var TUNNEL_DATA:int=2;
public static var TUNNEL_PADDING:int=3;
public static var TUNNEL_ERROR:int=4;
public static var TUNNEL_SIMPLE:int=0x40;
public static var TUNNEL_PAD1:int=5|TUNNEL_SIMPLE;
public static var TUNNEL_CLOSE:int=6|TUNNEL_SIMPLE;
public static var TUNNEL_DISCONNECT:int=7|TUNNEL_SIMPLE;
}
}
package org.colimas.http
{
import flash.utils.ByteArray;
import mx.collections.ArrayCollection;
public class InTunnel extends Tunnel
{
public function receiveData():String
{
return null;
}
public function setData(DGArray:ArrayCollection){
}
}
}
package org.colimas.http
{
import flash.utils.ByteArray;
public class OutTunnel extends Tunnel
{
static private var CONTENT_LENGTH:int=1024;
private var content_length:int=CONTENT_LENGTH;
var sendCount:int;
public function setContentLength( content_length:int):void{
this.content_length=content_length;
}
protected function getContentLength():int{return content_length;}
public function sendData(foo:ByteArray, s:int, l:int, flush:Boolean):void
{
};
}
}
package org.colimas.http
{
public class Tunnel
{
private var host:String=null;;
private var port:int=8888;
private var proxy:Proxy=null;
public function setHost(host:String):void
{
this.host=host;
}
public function setPort( port:int):void
{
this.port=port;
}
public function setProxy( proxy:Proxy):void
{
this.proxy=proxy;
}
protected function getHost():String
{
return host;
}
protected function getPort():int
{
return port;
}
protected function getProxy():Proxy
{
return proxy;
}
public function connect():void
{
} ;
public function close():void
{
} ;
}
}
package org.colimas.http
{
public class Proxy
{
var host:String;
var port:int;
var auth_name:String=null;
var auth_passwd:String=null;
function Proxy( host:String, port:int){
this.host=host;
this.port=port;
}
function getHost():String{ return host; }
function getPort():int{ return port; }
function setAuth( name:String, passwd:String):void{
this.auth_name=name;
this.auth_passwd=passwd;
}
function getAuthName():String{return auth_name;}
function getAuthPasswd():String{return auth_passwd;}
}
}
3. 服务器端
服务器端为JHTTPTunnel,可在网上下载。
我把部分代码修改,并加入数据生成程序。
void ok(MySocket mysocket, InputStream in, int l, String sid) throws IOException{
//死循环
for(;;){
try {
Thread.sleep(300);
mysocket.println("HTTP/1.1 200 OK");
mysocket.println("Last-Modified: Thu, 04 Oct 2001 14:09:23 GMT");
if(sid!=null){
mysocket.println("x-SESSIONID: "+sid);
}
mysocket.println("Content-Length: "+l);
mysocket.println("Connection: close");
mysocket.println("Content-Type: text/html; charset=iso-8859-1");
//数据生成 SimularData.stock[0]=SimularData.simulateChange(SimularData.stock[0]);
SimularData.stock[1]=SimularData.simulateChange(SimularData.stock[1]);
String tmp=String.valueOf(SimularData.stock[0])+" "+String.valueOf(SimularData.stock[1]);
//发送
mysocket.println(tmp);
mysocket.flush();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
mysocket.close();
return;
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
mysocket.close();
return;
}
}
4.运行结果
运行HTTPTunnel Server后,再打开Flash,可以看到界面的数据在不断更新。IBM和微软的股票在不断下降,哈哈。