flash中通过XMLSocket监控生产系统(1)
来源/作者:http://www.manbu.net 未知 2006-06-21 [评论(0条)]
一、背景
Flash现在已经成为制作高压缩、基于矢量的网络内容的行业标准,据统计有98.3%的网站用户使用 Macromedia Flash Player,也就是说全球有超过四亿一千四百万的网络用户通过Macromedia Flash Player无缝观看用Macromedia Flash制作的内容。
然而,Flash的发展没有停止,随着Flash新版本(Flash MX和Flash Player 6.0)的发布,Flash的应用领域得到了极大的扩展,使用 Flash 设计的内容可以在任意浏览器、系统平台和支持 Macromedia Flash Player 的设备上使用。它是可以在Internet上分发的最广泛的丰富客户机技术。Macromedia公司正在和广大的硬件制造商、操作系统开发商和浏览器提供商协作,确保其Macromedia Flash Player 可以跨越主流桌面系统和设备平台。
在软件设计中,软件体系结构最基本地可以分为C/S(客户端/服务器)与B/S(浏览器/服务器)两种。由于后者有着许多显著的优点,近几年B/S体系结构在软件设计中,得到了广泛的应用。使用B/S体系结构,充分利用了Web技术,客户端已经统一为Web浏览程序的单一平台。应用程序处理的结果,一律通过Web浏览程序显示出来。作为最终用户,只要操纵Web浏览程序,各种各样的处理任务都可以通过Web浏览程序调用系统资源来完成。
B/S与C/S体系结构相比,它大大简化了客户端,只要装上操作系统、网络协议软件以及浏览器即可,这时的客户机成为瘦客户机,而服务器则集中了所有的应用逻辑。开发、维护等几乎所有工作也都集中在服务器端。同时当企业对网络应用进行升级时,只需更新服务器端的软件,而不必更换客户端软件,减轻了系统维护与升级的成本与工作量,使用户的总体拥有成本(TCO)大大降低。
可是B/S体系结构也不是万能的,由于其工作机制的限制,在某些领域,以前采用B/S体系结构就很难实现要求功能。典型情况下, Web页面和大多数Web应用使用了一种称为请求/响应的工作机制:
o 使用者点击一个超级链接,或是提交一个表单等。
o 浏览器向服务器发送请求。
o 服务器启动一个会话。
o 服务器经过处理后,把数据发送给浏览器。
o 服务器关闭会话,浏览器显示数据。
我们可以看出,使用B/S体系结构设计软件时,无法回避两个限制:被动性与滞后性。首先,使用者必须通过浏览器请求,服务端才能进行响应,在某些领域,如及时监控中, 需要服务端在特定情况下,主动发送数据给客户端,而使用传统B/S体系结构技术,实现该目的就很困难。其次,服务端每次请求都必须启动一个会话,加上服务端处理与网络传送所消耗的时间,这自然就形成了客户端的滞后性。
也许有些读者,会提出异议,聊天室就是采用B/S体系结构的,为什么服务端能够每几秒钟,就把聊天室里各人的发言发送过来呢?其实,聊天室还是采用B/S体系结构的请求/响应机制,只不过客户端每过几秒就发送一次请求而已,好奇的读者可以看一下聊天室网页的源码,可以在源码的头部找到这样一条语句:
它的作用就是每2秒钟发送一次请求。
在设计软件时,往往要对工业生产系统进行及时监控,随时了解生产系统的运行情况,可是在以前,如果采用Web方式就很难保证及时性,同时,每几秒就给服务端发送请求,既浪费网络资源,又给系统带来额外负担。如果硬要采用B/S体系结构,当然也有方法,采用Applet,由于Java安全的限制,必须对每台客户端进行安全配置,可这样与C/S体系结构又有什么不同了呢,已经丧失了B/S体系结构的“瘦客户”的优点。
可自从Flash 5发布后,其中的XML与XMLSocket技术很好地解决了B/S体系结构被动性与滞后性的限制,同时没有了Java的安全限制,新的Flash MX中对此又有了很大的增强。本文就将介绍利用XML与XMLSocket技术对生产系统进行及时监控,同时为了测试方便,介绍了一个模拟服务端的程序。(本文所有介绍,都将依据Falsh的最新版本Flash MX。)
二、技术介绍
1ML 对象
通过使用该对象实例的方法与属性,可以加载、解析、发送、构造、和维护XML文档树。
令人激动的是,在Flash的最新版本Flash MX中,XML 对象已经成为一个本地对象,即在FlashPlayer 6中内置对其的直接支持(Flash Player 5只有300多KB,而Flash Player 6却有800 KB,内置许多对象是相当大的原因。),因此,动态性能得到了极大的提高。
在使用XML 对象之前,必须通过其构造函数,生成一个对象实例,然后通过使用其方法和属性,对XML文档树进行操作。XML 对象有两种构造函数:
1) new XML(src)
其中参数src 必须是格式正确的XML文本,以下语句先通过传进来的XML文本生成一个XML对象实例,将把两个非静态文字(TextField对象)实例nameTextField和passwordTextField的内容分别设为“Morgan”和“loveme”如:
myXML = new XML(””);
nameTextField.text = myLogin.attributes.username ;
passwordTextField.text = myLogin.attributes.password ;
2) new XML()
生成一个空的XML对象实例,然后调用其相应的方法如load()、createElement()、 createTextNode()、cloneNode()、appendChild()等方法构造或加载XML文挡树,具体的用法可以参考Flash在线帮助,由于本文的重点在于使用Flash的socket(套接字)连接,所以关于XML对象的基于HTTP协议的send()、load()、sendAndLoad()等方法就不做介绍,对于如何构造XML文挡树请参见下文。
二、技术介绍
2、XMLSocket 对象
实现了客户端socket(套接字),允许包含Flash应用的浏览器与服务端建立socket连接,之后Flash应用与服务端就可以相互发送XML数据,而且在一个socket连接建立之后,在该连接上传送的数据量是没有限制的,直到socket连接关闭。
使用XMLSocket 对象时,必须要注意两点:
o 在socket连接上发送的XML数据,每条数据以一个0字节隔开,详见下面服务端的建立。
o Flash 应用所连接的主机必须是与相应Web服务器在同一IP地址或是同一子域。所谓同一子域是指在同一域名空间中,例如包含Flash应用的网页是从mail.real-ok.com下载的,则name.mail.real-ok.com就是子域,就允许建立连接,而real-ok.com不是子域,Flash的安全规则就不允许建立连接。
使用XMLSocket 对象的流程为:
1) 建立一个XMLSocket 对象
mySocket = new XMLSocket();
2) 对生成的XMLSocket 对象进行设置
mySocket.onConnect = myOnConnect;
mySocket.onData = myOnData;
mySocket.onXML = myOnXML;
mySocket.onClose = myOnClose;
以上四条语句,分别设置了mySocket的四个事件处理函数,其中,myOnConnect、myOnData、myOnXML分别是带有一个参数的函数,myOnClose不带参数,当发生相应的事件时,就调用相应的处理函数。
3) 使用XMLSocket 对象的connect方法,建立与服务端的连接mySocket.connect(null, 6666);
其中,connect方法有两个参数,第一个参数表示要连接的主机,可以是全限定的域名和者IP地址,需要注意一点:当使用IP地址时,如 127.0.0.1 需要把它当作字符串来处理,即要用引号把IP地址括起来。如果为null,则连接Web服务器(从该Web服务器下载了包含当前Flash应用的网页)所在的IP地址。
第二个参数表示要连接的端口,由于低于1024的端口被通用程序所占,Flash的安全规则不允许在低于1024的端口建立连接。connect方法返回布尔型变量true或false,表示连接是否成功。如:
if (!mySocket.connect(null, 2000)) {
myTextField.text = “连接失败!”;
}
以上语句中,如果连接失败,connect方法返回flase,则把myTextField(为一非静态文字TextField对象的实例)的内容设为“连接失败!”。
XMLSocket对象与服务端进行连接将触发onConnect事件,则相应的事件处理函数(见上面流程步骤2中的设置)myOnConnect,其中的参数与connect方法的返回值意义相同,详见下面的客户端实例。
4) 当连接建立成功之后,客户端与服务端就可以相互发送XML数据了。使用XMLSocket 对象的send方法向服务端发送数据:
mySocket.send(myXML);
其中,myXML是一个包含XML数据的XML对象,send方法先把myXML转化为字符串,然后将该字符串发送到服务端,并在字符串发送后,追加发送一个0字节。send方法没有返回值。
以下示例中,先生成一个空的XML对象myXML,然后在myXML中添加了一个元素节点myLogin,该节点包含两个属性username和password,send方法把myXML转化成字符串 后,发送给服务端,当然还要追加发送一个0字节,以表示一条XML数据的完成:
var myXML = new XML();
var myLogin = myXML.createElement("login");
myLogin.attributes.username = "morgan";
myLogin.attributes.password = "loveme";
myXML.appendChild(myLogin);
mySocket.send(myXML);
当有数据到达(收到一条以0字节为结尾的字符串)时,首先将触发onData事件,相应的事件处理函数myOnData带有一个参数,表示当时到达的数据字符串,但不包含0字节,如下例中,服务端传来一个字符串 “I am Morgan Yang !”,加上一个0字节,以下语句将把myTextField(为一非静态文字TextField对象的实例)的内容设为“I am Morgan Yang !”,我们注意到这里发送的数据,可以是包括XML格式在内的任何形式:
function myOnData(src) {
myTextField.text = src ;
}
对于onData事件,如果没有相应的事件处理函数,默认将触发onXML事件,形式如下:
XMLSocket.prototype.onData = function (src) {
this.onXML(new XML(src));
}
即在onXML事件中,使用onData事件中得到的数据生成一个XML对象,并把该对象作为参数传给onXML事件的处理函数,所以如果要自定义onXML事件的处理函数,服务端发送来的数据就必须是XML格式,否则就会发生意想不到的错误。如果设置了onData事件的处理函数,当数据到达时,将不再调用onXML事件的处理函数,除非在显式地调用,所以在某种意义上,两种事件是互斥的。
在没设置onData事件处理函数,又设置了onXML事件处理函数情况下,当有XML数据 到达时,以下语句将把两个非静态文字实例nameTextField和passwordTextField的内容分别设为“Morgan”和“loveme”:
function myOnXML(doc) {
var e = doc.firstChild;
if (e != null && e.nodeName == " login ") {
nameTextField.text = e.attributes.username ;
passwordTextField.text = e.attributes.password ;
}
}
5) 最后,在程序结束的时侯,使用XMLSocket 对象的close方法,关闭Socket连接,如下:
mySocket.close();
需要注意的是,使用XMLSocket 对象的close方法,来关闭Socket连接不触发XMLSocket对象的onClose事件,只有当Socket连接被服务端关闭时,才在Flash应用客户端触发该事件,默认情况下,XMLSocket 对象的onClose事件处理函数不执行任何动作,可以自定义该事件处理函数,以达到特定目的。如以下语句,在onClose事件发生时,把myTextField(为一非静态文字实例)的内容设为 “Socket Closed By Server !”
function myOnClose() {
myTextField .text = "Socket Closed By Server !" ;
}
二、技术介绍
3extField文本域对象
在介绍TextField对象之前,首先我们须明白,在Flash中,有三种文本:
1) 静态文本(Static Text)
内容和样式都在创作是决定,在Flash MX中,可以有竖直的静态文本,在Flash属性检查器中设置(关于静态文本的详细阐述,请参见作者的另一篇文章“Flash Text”)。
2) 动态文本(Dynamic Text)
是TextField对象的一个实例,可以在Flash属性检查器中为其设置唯一的实例名,然后就可以使用该实例名来使用相应的方法与属性以改变动态文本的内容与样式。
本文所介绍的使用XMLSocket以Web方式对生产系统进行及时监控的应用案例中,就是使用动态文本以不同的颜色来显示服务端发送过来的不同信息:
function myOnXML(doc) {
var e = doc.firstChild;
Selection.setFocus("_root.txt");
Selection.setSelection(0,0);
if (e != null && e.nodeName == "MESSAGE") {
if(e.attributes.type == "error"){
myTextFormat.color = "0xff0000";
txt.setNewTextFormat(myTextFormat);
txt.replaceSel("Error -- " + e.attributes.text + " -- "
+ e.attributes.time + newline);
}else if (e.attributes.type == "normal"){
myTextFormat.color = "0x0000ff";
txt.setNewTextFormat(myTextFormat);
txt.replaceSel("Normal-- " + e.attributes.text + " -- "
+ e.attributes.time + newline);
}
}
}
以上语句是XMLSocket对象的onXML事件处理函数的部分,函数中第一条语句得到XML文档的第一个节点,假设服务端发送来的数据为
接着的一个嵌套if语句,判断变量 e 所代表的节点的类型,如果是"error"类型,就通过myTextFormat(TextFormat文本样式对象的一个实例),把该条信息显示成红色;如果是"error"类型,就通过myTextFormat,把该条信息显示成蓝色;在实际场合下,可以根据需要,定义任意条信息类型。
其中的变量 txt 就表示一个动态文本对象,每次收到数据时,就调用TextField对象的 replaceSel() 方法把相应的数据插入到文本框的最顶行。为什么是最顶行呢?这就是 Selection.setSelection(0,0) 语句的作用,它把光标位置定位到第一行的第一个位置。其中,newline是Flash中的常量,表示换行。
3) 输入域文本(Input Text)
和动态文本一样,它也是TextField对象的一个实例,可以在Flash属性检查器中为其设置唯一的实例名,然后就可以使用该实例名来使用相应的方法与属性以改变动态文本的内容与样式,与动态文本的是,它允许用户输入,并且具有剪切、复制、粘贴、全选等编辑功能。
本文所介绍的应用案例中,就是使用输入框文本来让用户输入建立Socket 所要连接的主机与端口:
function btnConnect_OnClick() {
if(txtPort.text < 1024 || txtPort.text > 65536){
txtPort.text = "";
Selection.setFocus("_root.txtPort");
txtPort.backgroundColor = "0xFF0000";
}else{
if(txtHost.text == “Default Host”){
socket.connect(null, txtPort.text);
}else{
socket.connect(txtHost.text, txtPort.text);
}
}
}
以上语句是btnConnect按钮(应用程序开发组件PushButton的一个实例)的单击事件处理函数,其中函数名设为btnConnect_OnClick,只是为了便于理解,只要在属性检查器中设置btnConnect的Click Handler(单击事件处理器)到相应的函数。txtHost和txtPort 分别让用户输入连接主机与端口的输入域(TextField)对象。
当用户单击btnConnect按钮时,首先判断用户输入的端口数是否在1024与65565之间,如果不在,把端口输入框txtPort的内容请空,背景变成红色,并用以下语句得到输入焦点 Selection.setFocus("_root.txtPort") ,让用户重新输入。由于txtPort端口输入框的内容必须是数字,所以可以设定用户只能输入数字,同时,当用户再次输入时,把端口输入框的背景变成原来的颜色:
txtPort.restrict = "0-9";
txtPort.onChanged = txtPort_onChanged;
在生成输入框文本时,默认txtHost的内容是”Default Host” ,如果用户在连接前没有改变,则连接Web服务器所在的IP地址,如果用户输入了主机,得保证所连接的主机与Web服务器在同一IP地址或同一子域。
三、实战
1、服务端
首先,我们来构造服务端。考虑到本文的重点只是用XMLSocket 进行及时监控,而服务端知识来模拟生产系统,为了节省篇幅,我们对服务端的功能进行了简化,只要求其能够做到(使用Java语言完成服务端的编写)监听指定的端口,当有客户端请求时,启动一个新的线程与之建立连接。
try{
ServerSocket server = new ServerSocket(Integer.parseInt (args[0]));
while(true) {
System.out.println(" Start Listenning the TCP port " + Integer.parseInt(args[0]));
System.out.println("-------------------------------------------");
Socket client = server.accept();
System.out.println("Remote Host is "+ client.getInetAddress());
new ServerThread (client).start();
}
}catch (IOException ex){
ex.printStackTrace();
}
以上语句中,先是通过启动Java程序时传进来的端口参数,建立一个服务端套接字(ServerSocket)对象server,在随后的死循环中,调用其accept()方法监听指定的端口,如果有客户端连接请求时,生成一个套接字对象client,并用其作为参数生成并启动一个新的线程与客户端交互。
三、实战
2、Flash客户端
在了解了服务端向客户端发送的数据后,我们来构造本文的核心内容——用Flash客户端来连接服务端,并以不同的颜色及时显示不同类型的数据。
1) 启动Flash MX,通过 File -> New 菜单创建一个新的Flash 文档。
2) 通过 Insert -> Layer 菜单,添加一个层,并把现有的两个层名字分别修改为UI 和 Action ,如图一。其中,Action层是我们统一放程序代码的地方,Flash是一个编程极其灵活的开发工具,可以放置代码的地方非常多,如MovieClip内部、各个祯、按钮等内部都可以放置程序代码(关于代码放置的详细阐述,请参见作者的另一篇文章“Flash 编程规范”),假如MovieClip嵌套过深的话,这将增加以后维护程序的难度。
所以作者推荐尽可能把程序代码放置在一个统一的位置。
3) 选中 Action层,打开Action面板,切换到专家模式,加入以下注释,如图二:
//Flash创作时对象的初始化
//Flash运行时对象的初始化
//自定义函数
为了以后程序维护的方便,建议尽可能把所有的程序写在一处,象本例中,所有程序都写在Action层中,为了程序的结构清晰,建议用以上注释把整个程序分成几个部分。
不断向客户端发送不同类型的有意义的XML数据,模拟生产系统的不同状态。考虑到功能的简便性,服务端只是每2秒向客户端发送一条随机类型的数据,而没有包括模拟生产系统及时的状态,如在某一时刻发生错误时,发送一条表示错误类型的数据,在实际设计编程中,基于本文中的例子,可以轻松扩展实现所需功能。
public void run() {
try {
while (true) {
System.out.println(this.getName() + "------" + i++);
d = new Date();
if(new Random().nextInt()*100 < 50){
str = "< MESSAGE TYPE=/"error/" TEXT=/"Hello, my name is Morgan!/" TIME=/"" + d.toString() + "/"/>";
} else {
str = "";
}
byte[] s= str.getBytes();
out.write(s);
out.write(0);
out.flush();
this.sleep(2000);
}
} catch (Exception ex){
ex.printStackTrace();
}
}
以上语句是自定义线程类ServerThread的run()方法,首先利用随机数条件if语句,随机产生不同类型的XML数据,接着用OutputStream输出流对象,输出XML数据的字节数组数据,为了表示该条数据已发送完成,再发送一个0字节,随后调用输出流的flush()方法清空缓存,让输出流立即发送数据。最后使线程暂停2秒钟,等线程再次执行时,循环以上步骤。
这样,在客户端就可以每2秒钟收到一条随机类型的XML数据。详见本文附带的程序源码。
在服务端,为了简便,只是随机向两种类型(normal类型和error类型)的数据:
< MESSAGE TYPE="error" TEXT=" 数据内容" TIME=" + 系统时刻+ / >"
< MESSAGE TYPE="normal" TEXT=" 数据内容" TIME=" + 系统时刻+ / >"
三、实战
2、Flash客户端
在了解了服务端向客户端发送的数据后,我们来构造本文的核心内容——用Flash客户端来连接服务端,并以不同的颜色及时显示不同类型的数据。
1) 启动Flash MX,通过 File -> New 菜单创建一个新的Flash 文档。
2) 通过 Insert -> Layer 菜单,添加一个层,并把现有的两个层名字分别修改为UI 和 Action ,如图一。其中,Action层是我们统一放程序代码的地方,Flash是一个编程极其灵活的开发工具,可以放置代码的地方非常多,如MovieClip内部、各个祯、按钮等内部都可以放置程序代码(关于代码放置的详细阐述,请参见作者的另一篇文章“Flash 编程规范”),假如MovieClip嵌套过深的话,这将增加以后维护程序的难度。
所以作者推荐尽可能把程序代码放置在一个统一的位置。
3) 选中 Action层,打开Action面板,切换到专家模式,加入以下注释,如图二:
//Flash创作时对象的初始化
//Flash运行时对象的初始化
//自定义函数
为了以后程序维护的方便,建议尽可能把所有的程序写在一处,象本例中,所有程序都写在Action层中,为了程序的结构清晰,建议用以上注释把整个程序分成几个部分。
4) 选中 UI层,在工具箱选中文本工具,并在属性检查器中设置属性Show Border Around Text为真、文本类型为Dynamic Text(动态文本)、文本为Multiline(多行),在Stage (舞台)上半部分画一个大的文本区域,把其实例名设为 txt。如图三,现在注意到,txt文本域具有了边框和白色背景,我个人不太喜欢白色,能不能改变文本域的背景色呢?
当然能,可以这么说,在Flash MX中,能够看到的都有方法来改变,我们就把txt文本域的背景变成 #BBBBCC颜色吧,请在程序的 [Flash创作时对象的初始化]部分加入下面语句:
txt.backgroundColor = "0xbbbbcc";
通过 Control -> Test Movie 菜单(或 Ctrl + Enter快捷键)预览,您会看到txt文本域的背景已经变成所期望的颜色。
5) 通过 Window -> Componets 菜单,打开Componets(组件)面板,选择ScrollBar组件拖到舞台上的txt文本域上,当释放鼠标时,两者会自动结合,如图五。到属性检查器中,ScrollBar组件的Target TextField属性为 txt ,同时给ScrollBar组件起一个唯一的实例名,如scrollbar ,由于当信息显示满一屏时,需要通过滚动条查看以往的信息,所以需要在程序一开始对scrollbar进行设置以激活它,在程序的 [Flash创作时对象的初始化]部分加入下面语句:
scrollbar.setScrollProperties(10, 10, 20);
6) 从工具箱中选择文本工具,在属性检查器中设置属性Show Border Around Text为真、文本类型为Input Text(输入框文本)、文本为Single Line(单行),在Stage(舞台)上txt文本区域的下面靠左画两个文本域,把其实例名分别设为txtHost和txtPort,如图三:
另外,在属性检查器中,设置txtPort的Maximum Characters(最大长度)为5(因为端口数最大为65535),设置txtPort只能输入数字,我们可以在属性检查器中的Character按钮设置限制,我们这里用ActionScript在运行时设置(也可以在属性检查器中设置),在程序的 [Flash创作时对象的初始化]部分加入下面语句:
txtPort.restrict = "0-9";
txtPort.onChanged = txtPort_onChanged;
并且设置txtHost的内容为 ”Default Host”, txtPort的内容为 ”6666”。同时, 由于用户在txtPort中键入错误的端口数时,txtPort的背景将变成红色(详见前面输入框 文本(Input Text)的介绍),当用户再次键入时,背景颜色将变回原来的白色,这 txtPort的onChanged事件中得以实现。 7) 从Componets(组件)面板上,选择两个PushButton组件拖到舞台上的txt文本域的下部靠右的位置,水平与前面两个输入框文本对起。
第一个PushButton按钮的实例名设为 btnConnect ,Label(标签)设为 Connect, Click Handler(单击事件处理函数)设为btnConnect_onClick 。第一个PushButton按钮 的实例名设为 btnClear ,Label(标签)设为 Clear,单击事件处理函数设为 btnClear_onClick ,如图四:
通过完成以上的步骤,我们已经建立了Flash客户端的用户界面。现在我们来在程序的第二部分[Flash运行时对象的初始化]中,创建并初始化相应的对象:
myTextFormat = new TextFormat();
socket = new XMLSocket();
socket.onConnect = myOnConnect;
socket.onXML = myOnXML;
socket.onClose = myOnClose;
以上语句中,第一条语句生成一个TextFormat(文本样式对象)实例myTextFormat,以达到以不同颜色显示不同类型数据的目的(详见前面动态文本(Dynamic Text)的介绍 )。接着生成一个XMLSocket对象实例socket,并分别设置其onConnect、onXML、onClose事件的处理函数。
以下我们最后只剩下编写各种对象的事件处理函数了,由于在本文的第二部分里,就有关的技术已经做了相当详细的介绍,并且您可以同时参照本文附带的程序源码,为了节省篇幅,这部分将不再做进一步的介绍了,如果您在实际使用中,有问题您可以给我发 E_mail: [email protected]。
现在程序已经完成了,在您的Web服务器上启动服务端模拟程序,然后通过浏览器连接到相应包含Flash客户端的页面,单击Connect按钮,如果没有其他的意外,您就会看到客户端正常地运行了。如果为了测试的方便,您可以在本地机启动服务端模拟程序,然后通过单独的Flash Player 6来打开,在txtHost中键入“127.0.0.1”,这样您就省了架设Web服务器的麻烦。祝您顺利!