一步一步学Flash Media Server

从今天起,我们来学习一下 Flash Media Server,简称FMS,从名字上我们可以看出这是一个媒体服务器,其它简单点理解就是一个交互服务器,而且可以实现媒体方面的交互,我们可以利用它来完成一些网络版的FLASH程序,比如聊天室,网络版的不是很复杂的游戏,等等。

大家可以到如下地址下载最新的3.0版本:
http://www.adobe.com/products/flashmediainteractive/

当然这个版本是开发版,开发版和正式版的区别在于开发版只支持10个连接数,不过对于我们学习已经是足够了.

大家安装完之后,我们会在安装目录里看到applications和logs这两个目录,这个是我们用的最多的两个目录,applications是我们放服务端程序的地方,而logs是放日志文件的地方,日志文件对我们调试程序很有帮助.

大家也可以修改这两个文件的路径,当然要更改配置文件,在conf目录里放的都是配置文件,我们打开fms.ini这个文件我们会找到

VHOST.APPSDIR =
LOGGER.LOGDIR =
这两项就是更改applications和logs路径的地方,如比我们改成:

VHOST.APPSDIR = e: \fms\applications
LOGGER.LOGDIR = e:\fms\logs

那么我们就在e盘建立相应的目录把程序入在里面就可以了.

确定FMS的服务打开了,就可以运行了,FMS有两个服务,在安装完后在开始菜单我们可以找到Start Flash Media Administration Server 3 和 Start Adobe Flash Media Server 3 这两个打开服务的菜单,要打开服务点这两个就可以了,前一个是FMS管理服务,后一个是FMS服务,Stop Flash Media Administration Server 3和Stop Adobe Flash Media Server 3就是关闭这个服务的,默认状态下,在电脑刚启动里,这两个服务就会启动,当然也可以在windows的服务里面来启动和关闭服务.

Flash Media Administration Console这个菜单项是打开管理后台,管理后台对我们调试程序是很有作用的,打开这个之后是一个登录界面,要求输入用户名密码和服务器地址,用户名和密码是我们在安装FMS就输入的,服务器地址就是我们要连的装有FMS的地址,我们连接本机就用localhost或者127.0.0.1就可以了,如果我们在安装的时候改了它的默认端口(1935)的话,我们在地址上还要加端口,如果没有改端口这里就不用加了,输入用户名密码和服务器地址,我们就可以进入管理界面了,具体如何操作,我们在后面会讲到.

整个教程,我会用一个聊天室的例子来讲解如何使用FMS,当然我只能讲出我所知道的知识,不能完全讲出FMS的强大的功能.

下节我们接触FMS基本的结构,下节继续.

今天我们来看一下FMS里面最重要的一个类—Application类.

Application类包含了有关一个Flash Media Server应用程序实例的信息,这些信息会一直维持直至这个应
用程序实例被卸载.

先看一下在Application类中常用的方法:

Application.acceptConnection()      接受一个来自客户机的至一个应用程序的连接。
Application.disconnect()                  从服务器断开一个客户机的连接。
Application.rejectConnection()       拒绝至一个应用程序的连接。
Application.shutdown()                    卸载应用程序实例。

再看一下在Application类中常用的属性:

Application.clients      一个对象,该对象包含了当前连接到这个应用程序的所有客户的一个列表。
Application.name       一个应用程序实例的名字。

最后看一下在Application类中常用的事件:

Application.onAppStart              当这个应用程序被服务器装载时调用。
Application.onAppStop              当这个应用程序被服务器卸载时调用。
Application.onConnect              当一个客户机连接到这个应用程序时调用。
Application.onDisconnect        当一个客户机从这个应用程序断开连接时调用。

(这些是比较常用的,还有其它一些,大家可以看FMS自带的文档)

我们来描述一下客户机连接的流程:

1.当第一个用户连接的时候,会启动服务端应该程序实例,这样就会触发Application.onAppStart事件,通常我们会里这个事件里做一些初如化的事情.

2.用户连接的时候会触发Application.onConnect事件,在这个事件里我们可以用Application.acceptConnection()方法来接受这个用户的连接,也可以用Application.rejectConnection()方法拒绝这个用户.

3.服务器想把一个用户断开的时候,可以用Application.disconnect()方法,当一个用户断开,不管是服务器把它断开,还是客户端自己断开,都会触发Application.onDisconnect事件.

4.使用Application.shutdown()方法可以卸载应用程序实例,当这个应该程序长时间(大概半个小时左右)没有客户端连接的时候,应该程序也会被卸载,当应该程序卸载的时候会触发Application.onAppStop事件.
我们写FMS程序时,大多数都是这样的流程.

今天我们来看一下用AS 3连接FMS3的代码(这些代码其它对FMS2也是适用的).

这个例子我们不会去写FMS的代码,但我们需要建一个FMS应该程序,其实就是建一个目录,在FMS放应该程序的目录(applications)里建一个文件夹,我们后面要做聊天室的例子,所以我们就建一个名叫chat的目录.
接下来就是客户端的代码了,我们建一个chat.fla的FLASH文件,再建一个文档类Chat.as:

package net.smilecn.chat{
   
    import flash.display.Sprite;
   
    import flash.net.NetConnection;
   
    import flash.events.NetStatusEvent;
   
    public class Chat extends Sprite{
       
        private var nc:NetConnection;
        private var rtmpUrl:String = “rtmp://localhost/chat”;
   
        public function Chat():void{
            nc=new NetConnection();
            nc.addEventListener (NetStatusEvent.NET_STATUS,netStatusHandler);
            nc.connect (rtmpUrl);
        }
       
        private function netStatusHandler(event:NetStatusEvent):void{
            trace(event.info.code);
        }
    }
   
}
这段代码里我们导入了一个NetConnection,这个类是FLASH里用于跟网络连接相关的操作,像我们连接FMS,remoting(我前面的一步一步学ActionScript 3[十六]里面有相关介绍).

NetStatusEvent是一个检测状态的事件

rtmpUrl是一个连接FMS的字符串,rtmp是FMS用的一个网络协议,localhost是服务器的IP,这里我们是本机,所以是 localhost,如果放在网上,应该是你网上的IP,chat是应用程序名,就是之前我们建立的chat目录.这里我们完整的地址就 是:rtmp://localhost/chat,其实如果是localhost,地址可以这样写:rtmpe:/localhost.

这个程序动行后会trace出NetConnection.Connect.Success,这个信息表示的是我们连接FMS成功了.

这是一个连接状态,event.info.code就是这个状态,相关的状态还有:

NetConnection.Connect.Closed  成功关闭连接。
NetConnection.Connect.Failed  连接尝试失败。
NetConnection.Connect.Rejected 连接尝试没有访问应用程序的权限。

这几个状态是我们用的比较多的,当然还有一些状态,在帮助里可以查到.

这是连接FMS最基本的代码,以后我们都会用到这些代码.下节继续.

今天我们讲一个非常简单的多人聊天功能,同样我们也没有服务端代码,今天要用到一个新东西—-SharedObject

先看一段代码,更改一下上一节中的代码:
package net.smilecn.chat{
   
    import flash.display.Sprite;
    import flash.net.NetConnection;
    import flash.net.SharedObject;
    import flash.events.NetStatusEvent;
    import flash.events.SyncEvent;
    import flash.events.MouseEvent;
    import fl.controls.TextArea;
    import fl.controls.Button;
    import fl.controls.TextInput;
 
   
    public class Chat extends Sprite{
       
        private var nc:NetConnection;
        private var rtmpUrl:String = “rtmp://localhost/chat”;
        private var button:Button;
        private var textArea:TextArea;
        private var textInput:TextInput;
        private var chatMsg_so:SharedObject;
        private var userName:String = “user001″;
   
        public function Chat():void{
            textArea=new TextArea();
            textArea.setSize (200,300);
            textArea.move (20,20);
            addChild (textArea);
 
            textInput=new TextInput();
            textInput.width = 140;
            textInput.move (20,330);
            addChild (textInput);
 
            button=new Button();
            button.width=50;
            button.label=”发送”;
            button.move (170,330);
            addChild(button);
            button.addEventListener (MouseEvent.CLICK,sendMsg);
           
           
            nc=new NetConnection();
            nc.addEventListener (NetStatusEvent.NET_STATUS,netStatusHandler);
            nc.connect (rtmpUrl);
        }
       
        private function netStatusHandler(event:NetStatusEvent):void{
            if(event.info.code == “NetConnection.Connect.Success”){
                chatMsg_so=SharedObject.getRemote(“chatMsg”,nc.uri,false);
                chatMsg_so.connect (nc);
                chatMsg_so.addEventListener (SyncEvent.SYNC,checkSO);
            }
        }
       
        private function checkSO (event:SyncEvent):void{
             for (var i:uint; i<event.changeList.length; i++)
             {
                    switch (event.changeList[i].code)
                    {
                        case “clear” :
                            trace(“clear”);
                            break;
                        case “success” :
                             trace (chatMsg_so.data.msg);
                             break;
 
                        case “change” :
                             textArea.appendText (chatMsg_so.data.msg + ” “);
                             break;
                   }
             }
        }
       
        private function sendMsg (e:MouseEvent):void{
             chatMsg_so.setProperty (“msg”,userName + “:” + textInput.text);
             textArea.appendText (userName + “:” + textInput.text + ” “);
             textInput.text = “”;
        }
 
 
    }
   
}
要看到效果,我们先了布一份EXE的文件,相当一个客户端,然后把userName改一个名字,前一个是user001,这个我们就改成user002,将那个EXE打开,再将FLASH里的这个发布,试着打上文字点发布,看看是不是达到了聊天的效果.

接下来来讲解一下代码:

在连接成功之后,有这样一句代码:chatMsg_so=SharedObject.getRemote(”chatMsg”,nc.uri,false);

chatMsg_so是我们定义的一个SharedObject实例,SharedObject–共享对象,官方的解释是这样的:

SharedObject 类用于在用户计算机或服务器上读取和存储有限的数据量。 使用共享对象,可在永久贮存在本地计算机或远程服务器上的多个客户端 SWF 文件和对象之间实现实时数据共享。 本地共享对象类似于浏览器 Cookie,远程共享对象类似于实时数据传输设备。

SharedObject可以用来存储数据,这里我们用到它是存储我们的聊天数据.

getRemote方法是得到一个远程共享对象,就是FMS上的一个共享对象,名字叫chatMsg,地址是nc.uri,最后一个参数是是否以文 件的形式保存下来,false表示不保存,当服务器上有这样一个名为chatMsg的共享对象时,这个方法就会得到这个共享对象,如果没有这相共享对象, 就会创建一个名为chatMsg的共享对象.

我们的代码中,第一个客户连进去后会创建一个共享对象,其它用户再进去,就是得到这个共享对象.

chatMsg_so.addEventListener (SyncEvent.SYNC,checkSO);

这句代码是侦听共享对象的状态

event.changeList[i].code就是状态,这里我们用了三个状态—-clear,success,change

clear是清除数据时(我们的代码中第一个人进去时会触发)
success是成功(我们的代码中自己发消息时会触发)
change是改变(我们的代码中别人发消息时会触发)

我们实现这个聊天功能实际是每个人去改变这个共享对象,然后共享对象改变了就会通知所有的客户端,其它人就会收到这个消息(注意自己不会收到change消息).

这个聊天在实际中用处不大,这里只是介绍共享对象的一个使用方式,而且这种聊天还有很多功能都没有实现.(本人不喜欢用共享对象)

在后面我们会讲解怎么写服务端代码,这两节都没有写服务端代码.下节继续.

今天我们的讲解的是在昨天代码的功能上加上在线列表的功能,同时会去掉共享对象,用另一种方法向客户端发消息.

先看看服务端代码我们更改些什么代码:
application.onAppStart = function() {
    this.chatMsgArray = new Array();
    this.userListArray = new Array();
}
 
application.onConnect = function(client, userName) {
    if(checkOnline(userName)){
        this.rejectConnection(client);
        return;
    }
    this.acceptConnection(client);
    client.userName = userName;
    this.userListArray.push(userName);
    sendUserList();
    //客户端调用方法
    client.getMsg = function(){
        return application.chatMsgArray;
    }
    client.sendMsg = function(msg){
        var chatInfo = this.userName + ” : ” + msg;
        application.chatMsgArray.push(chatInfo);
        sendMsgToClient(chatInfo);
    }
}
 
application.onDisconnect = function(client) {
    trace(“用户:”+client.userName+” 离开”);
    var len = this.userListArray.length;
    for(var i=0;i<len;i++){
        if(this.userListArray[i] == client.userName){
            this.userListArray.splice(i,1);
            sendUserList();
        }
    }
}
 
 
application.onAppStop = function() {
    delete this.chatMsg_so;
}
 
function checkOnline(userName){
    var len = application.userListArray.length;
    for(var i=0;i<len;i++){
        if(application.userListArray[i] == userName){
            return true;
        }
    }
    return false;
}
 
function sendMsgToClient(chatInfo){
    var len = application.clients.length;
    for(var i=0;i<len;i++){
        application.clients[i].call(“getMsgInfo”,null,chatInfo);
    }
}
 
function sendUserList(){
    var len = application.clients.length;
    for(var i=0;i<len;i++){
        application.clients[i].call(“getUserList”,null,application.userListArray);
    }
}
this.chatMsgArray = new Array();
this.userListArray = new Array();

这两个定义的数组是在开始定义的,chatMsgArray是存储所有的聊天信息,userListArray是一个存储在线列表的数组

当用户连接进来的时候,我们用了一个函数checkOnline来检查用户是不是在在线列表中,如果在就用this.rejectConnection(client);拒绝这个连接,如果不在就接受这个连接并加到在线列表中

sendUserList函数是向所以客户端发送在线列表信息,application.clients是一个存储所以客户端连接的信息,application.clients.call就是调用客户端函数,使用方法跟客户端调服务器端是一样的.

然后我们增加了1个供客户端调用的函数

client.getMsg = function(){
      return application.chatMsgArray;
}
client.getMsg是返回所有的聊天信息,当用户第一次连接时,得到别人的聊天记录

在用户断开连接的时候增加了将断线用户从在线列表中清除,并且发送在线列表.

再来看看客户端代码:

package net.smilecn.chat{
   
    import flash.display.Sprite;
    import flash.net.NetConnection;
    import flash.net.Responder;
    import flash.events.NetStatusEvent;
    import flash.events.SyncEvent;
    import flash.events.MouseEvent;
    import fl.controls.TextArea;
    import fl.controls.Button;
    import fl.controls.TextInput;
    import fl.controls.Label;
    import fl.controls.List;
    import fl.data.DataProvider;
 
   
    public class Chat extends Sprite{
       
        private var nc:NetConnection;
        private var rtmpUrl:String = “rtmp://localhost/chat”;
        private var msg:Label;
        private var userNameInput:TextInput;
        private var enterBtn:Button;
        private var button:Button;
        private var textArea:TextArea;
        private var textInput:TextInput;
        private var userList:List;
       
        private var userName:String = “user002″;
   
        public function Chat():void{
            userNameInput = new TextInput();
            userNameInput.move(100,200);
            addChild(userNameInput);
            enterBtn = new Button();
            enterBtn.move(220,200);
            enterBtn.label = “进入”;
            addChild(enterBtn);
            enterBtn.addEventListener(MouseEvent.CLICK,enterBtnClickHandler);
            msg = new Label();
            msg.move(100,230);
            msg.text = “”;
            addChild(msg);
        }
       
        private function enterBtnClickHandler(event:MouseEvent):void{
            if(userNameInput.text!=”"){
                userName = userNameInput.text;
                removeChild(userNameInput);
                removeChild(enterBtn);
                removeChild(msg);
           
                textArea=new TextArea();
                textArea.setSize (200,300);
                textArea.move (20,20);
                addChild (textArea);
 
                textInput=new TextInput();
                textInput.width = 140;
                textInput.move (20,330);
                addChild (textInput);
 
                button=new Button();
                button.width=50;
                button.label=”发送”;
                button.move (170,330);
                addChild(button);
                button.addEventListener (MouseEvent.CLICK,sendMsg);
               
                userList = new List();
                userList.move(250,20);
                userList.setSize(100,300);
                addChild(userList);
           
                nc=new NetConnection();
                nc.addEventListener (NetStatusEvent.NET_STATUS,netStatusHandler);
                nc.connect (rtmpUrl,userName);
                nc.client = this;
            }else{
                msg.text = “请输入用户名”;
            }
           
        }
       
        private function netStatusHandler(event:NetStatusEvent):void{
            trace(“event.info.code:”,event.info.code);
            if(event.info.code == “NetConnection.Connect.Success”){
                nc.call(“getMsg”,new Responder(getMsgResult,getMsgFault))
            }
        }
       
        private function getMsgResult(re:Array):void{
            var s:String=”";
            var len:Number = re.length;
            for(var i=0;i<len;i++){
                s += re[i]+” “;
            }
            textArea.text = s;
        }
       
        private function getMsgFault(fe:*):void{
            trace(fe);
        }
       
        private function sendMsg (e:MouseEvent):void{
            nc.call(“sendMsg”,null,textInput.text);
            textInput.text = “”;
        }
       
        public function getMsgInfo(msg:String):void{
            textArea.appendText(msg+” “);
        }
       
        public function getUserList(list:Array):void{
            userList.dataProvider = new DataProvider(list);
        }
    }
   
}
客户端首先增加了一个用户输入名字,这是些简单的代码.

然后共享对象的那部份去掉了,加了句这样的代码:nc.client = this;

这句话的意思是指定当前对象为服务器回调方法的对象

public function getMsgInfo(msg:String):void{
       textArea.appendText(msg+”\n”);
}
 
public function getUserList(list:Array):void{
       userList.dataProvider = new DataProvider(list);
}
这两个方法就是服务器调用的方法.

在nc连接成功的地方加了句nc.call(”getMsg”,new Responder(getMsgResult,getMsgFault)),这句是在刚连接成功的时候得到以前用户的聊天记录.

顺便说一句,客户端代码用到了组件,所以要把这些组件放到库中才能运行,上一节也是这样的.

下节继续.

当我们要加的功能越来越多时,就会发现程序会越写越大,这样我们就需要更好的组织我们的程序,用类是最好方法,但FMS用的是AS1.0的语法,没有真正意义的类,但也能完成类的简

单功能,不管怎么样,总比没有类好,今天我们就来看一下如果在AS1.0里使用类,当然这不是真正的类.

现在我们将上一节中用到的用户列表做一下修改,写成一个类,上一节中我们用了userListArray这样一个数组来存储用户列表,现在我们把用户列表写成一个类:
先建一个UserList.asc文件,这就是我们要用的类的文件名,当然UserList也是类名(其实并不需要文件名和类名相同,因为这不是真正意义上的类)

function UserList(){
    this.listArray = [];//也可以用new Array(),不过听说[]效率更高;
}
 
UserList.prototype.addUser = function(userName){
    this.listArray.push(userName);
}
 
UserList.prototype.delUser = function(userName){
    var len = this.listArray.length;
    for(var i=0;i<len;i++){
        if(this.listArray[i] == userName){
            this.listArray.splice(i,1);
            break;
        }
    }
}
 
UserList.prototype.checkOnline = function(userName){
    var len = this.listArray.length;
    for(var i=0;i<len;i++){
        if(this.listArray[i] == userName){
            return true;
        }
    }
    return false;
}
 
UserList.prototype.getUserList = function(){
    return this.listArray;
}
function function UserList(){} 这个相当于类里面的构造函数

UserList.prototype. 这种相当于给UserList这个类加入方法,用过as1.0或者as2.0的朋友应该都知道prototype的用法

在这里我们加了四个方法

addUser 将用户加入到列表中
delUser 将用户从列表中删除
checkOnline 检查用户是否在列表中
getUserList 得到用户列表数组

这些代码应该很容易懂

再来看修改后的main.asc:

load(“UserList.asc”);
application.onAppStart = function() {
    this.chatMsgArray = new Array();
    this.userList = new UserList();
}
 
application.onConnect = function(client, userName) {
    if(this.userList.checkOnline(userName)){
        this.rejectConnection(client);
        return;
    }
    this.acceptConnection(client);
    client.userName = userName;
    this.userList.addUser(userName);
    sendUserList();
    //客户端调用方法
    client.getMsg = function(){
        return application.chatMsgArray;
    }
    client.sendMsg = function(msg){
        var chatInfo = this.userName + ” : ” + msg;
        application.chatMsgArray.push(chatInfo);
        sendMsgToClient(chatInfo);
    }
}
 
application.onDisconnect = function(client) {
    trace(“用户:”+client.userName+” 离开”);
    this.userList.delUser(client.userName);
    sendUserList();
}
 
 
application.onAppStop = function() {
   
}
 
function sendMsgToClient(chatInfo){
    var len = application.clients.length;
    for(var i=0;i<len;i++){
        application.clients[i].call(“getMsgInfo”,null,chatInfo);
    }
}
 
function sendUserList(){
    var len = application.clients.length;
    for(var i=0;i<len;i++){
        application.clients[i].call(“getUserList”,null,application.userList.getUserList());
    }
}
跟上一节的代码相比,首先多了一个load(”UserList.asc”),load能够将其它的asc文件加入进来,相当于导入了,也可以理解为包括,就是两个文件成了一个文件

其他的修改应该很简单,很容易看懂,我就不多讲了.

从这个程序代码的多少上看,并没有减少多少,但这只是一个小程序,当程序越大时就能看出这样写的好处了.

客户端不用改,现在看一下效果,应该和上一节是一样的.

下节继续.

在上一节中,我们学会了在FMS中使用类,虽然不是正式意义上的类,但也会使我们的程序看起来更结构化,这一节我们继续用类的方式改造之前的代码,首先我们要加个用户类(User.asc):
function User(client,userName){
    this.client = client;
    this.userName = this.client.userName = userName;
}
很简单的一个类,只有构造,没有方法,其实是当用户的信息更多时,我们通常会将用户的所有信息都封装起来,这样便于我们使用,这里我们只是比前面多加了一个client对象,这个对象是代表连接进来的客户端,我们把它封装到User里面.

接着,UserList.asc也要做相应该的修改,并且把之前在main.asc里面的sendUserList和sendMsgToClient也封装到UserList这个类中.

function UserList(){
    this.listArray = [];//也可以用new Array(),不过听说[]效率更高;
}
 
UserList.prototype.addUser = function(user){
    this.listArray.push(user);
    this.sendUserList();
}
 
UserList.prototype.delUser = function(userName){
    var len = this.listArray.length;
    for(var i=0;i<len;i++){
        if(this.listArray[i].userName == userName){
            this.listArray.splice(i,1);
            break;
        }
    }
    this.sendUserList();
}
 
UserList.prototype.checkOnline = function(userName){
    var len = this.listArray.length;
    for(var i=0;i<len;i++){
        if(this.listArray[i].userName == userName){
            return true;
        }
    }
    return false;
}
 
UserList.prototype.getUserList = function(){
    var result = [];
    var len = this.listArray.length;
    for(var i= 0;i<len;i++){
        result.push(this.listArray[i].userName);
    }
    return result;
}
 
UserList.prototype.sendUserList = function(){
    var len = this.listArray.length;
    for(var i=0;i<len;i++){
        this.listArray[i].client.call(“getUserList”,null,this.getUserList());
    }
}
 
UserList.prototype.sendMsgToClient = function(chatInfo){
    var len = this.listArray.length;
    for(var i=0;i<len;i++){
        this.listArray[i].client.call(“getMsgInfo”,null,chatInfo);
    }
}
在这个类中我们主要做了以下修改:

1.在addUser里传进来的参数是一个User对象,我们的列表里存的是每个人的对象,而不是以前的只一个userName了.
2.在delUser里,我们做比较的是列表里对象的userName和传进来的参数
3.getUserList方法中我们要把每个人的名字筛选出来
4.将sendUserList和sendMsgToClient封装进来,因为我们的用户对象中有Client,所以直接用this.listArray[i].client就可以调用客户端方法了.
5.在addUser和delUser后通过this.sendUserList()来发送在线列表.

通过将一些方法封装进来,我们发现main.asc的代码减少了,这是好处之一,还有一个好处在后面的教程中会讲到.

最后来看一下main.asc文件:

load(“UserList.asc”);
load(“User.asc”);
application.onAppStart = function() {
    this.chatMsgArray = new Array();
    this.userList = new UserList();
}
 
application.onConnect = function(client, userName) {
    if(this.userList.checkOnline(userName)){
        this.rejectConnection(client,{msg:”error1″});
        return;
    }
    this.acceptConnection(client);
    var user = new User(client,userName);
    this.userList.addUser(user);
    //客户端调用方法
    client.getMsg = function(){
        return application.chatMsgArray;
    }
    client.sendMsg = function(msg){
        var chatInfo = this.userName + ” : ” + msg;
        application.chatMsgArray.push(chatInfo);
        application.userList.sendMsgToClient(chatInfo);
    }
}
 
application.onDisconnect = function(client) {
    trace(“用户:”+client.userName+” 离开”);
    this.userList.delUser(client.userName);
}
 
 
application.onAppStop = function() {
   
}
在main.asc中减少了两个方法,并且多了一个load(”User.asc”)将User.asc载入进来

在接受用户后,新建了一个User对象 — var user = new User(client,userName);

还有一个改动就是:this.rejectConnection(client,{msg:”error1″});

我们在用户已经列表中拒绝用户加了一个参数,当你有不同的拒绝信息时,这个参数会起很大的作用

客户端代码也要做相应的修改:

package net.smilecn.chat{
   
    import flash.display.Sprite;
    import flash.net.NetConnection;
    import flash.net.Responder;
    import flash.events.NetStatusEvent;
    import flash.events.SyncEvent;
    import flash.events.MouseEvent;
    import fl.controls.TextArea;
      import fl.controls.Button;
      import fl.controls.TextInput;
    import fl.controls.Label;
    import fl.controls.List;
    import fl.data.DataProvider;
 
   
    public class Chat extends Sprite{
       
        private var nc:NetConnection;
        private var rtmpUrl:String = “rtmp://localhost/chat”;
        private var msg:Label;
        private var userNameInput:TextInput;
        private var enterBtn:Button;
        private var button:Button;
        private var textArea:TextArea;
        private var textInput:TextInput;
        private var userList:List;
       
        private var userName:String;
   
        public function Chat():void{
            userNameInput = new TextInput();
            userNameInput.move(100,200);
            addChild(userNameInput);
            enterBtn = new Button();
            enterBtn.move(220,200);
            enterBtn.label = “进入”;
            addChild(enterBtn);
            enterBtn.addEventListener(MouseEvent.CLICK,enterBtnClickHandler);
            msg = new Label();
            msg.move(100,230);
            msg.text = “”;
            addChild(msg);
        }
       
        private function enterBtnClickHandler(event:MouseEvent):void{
            if(userNameInput.text!=”"){
                userName = userNameInput.text;
                nc=new NetConnection();
                        nc.addEventListener (NetStatusEvent.NET_STATUS,netStatusHandler);
                nc.client = this;
                nc.connect (rtmpUrl,userName);
               
            }else{
                msg.text = “请输入用户名”;
            }
           
        }
       
        private function into():void{
            removeChild(userNameInput);
            removeChild(enterBtn);
            removeChild(msg);
           
            textArea=new TextArea();
            textArea.setSize (200,300);
            textArea.move (20,20);
            addChild (textArea);
 
            textInput=new TextInput();
            textInput.width = 140;
            textInput.move (20,330);
            addChild (textInput);
 
                   button=new Button();
            button.width=50;
                   button.label=”发送”;
                   button.move (170,330);
            addChild(button);
            button.addEventListener (MouseEvent.CLICK,sendMsg);
               
            userList = new List();
            userList.move(250,20);
            userList.setSize(100,300);
            addChild(userList);
        }
       
        private function netStatusHandler(event:NetStatusEvent):void{
            trace(event.info.code);
            switch (event.info.code) {
                case “NetConnection.Connect.Success” :
                    trace(“连接成功!”);
                    into();
                    nc.call(“getMsg”,new Responder(getMsgResult,getMsgFault))
                    break;
                case “NetConnection.Connect.Rejected” :
                    trace(“连接被拒绝!:”+event.info.application.msg);
                    if(event.info.application.msg == “error1″){
                        msg.text = “用户已在列表中”;
                    }
                    break;
                case “NetConnection.Connect.Failed” :
                    trace(“连接失败!”);
                    break;
                case “NetConnection.Connect.Closed” :
                    trace(“连接关闭!”);
                    break;
            }
        }
       
        private function getMsgResult(re:Array):void{
            var s:String=”";
            var len:Number = re.length;
            for(var i=0;i<len;i++){
                s += re[i]+”\n”;
            }
            textArea.text = s;
        }
       
        private function getMsgFault(fe:*):void{
            trace(fe);
        }
       
        private function sendMsg (e:MouseEvent):void{
            nc.call(“sendMsg”,null,textInput.text);
            textInput.text = “”;
        }
       
        public function getMsgInfo(msg:String):void{
            textArea.appendText(msg+”\n”);
        }
       
        public function getUserList(list:Array):void{
            userList.dataProvider = new DataProvider(list);
        }
       
        public function close():void{
           
        }
    }
   
}
代码比较简单,不用多讲解了,发布看一下效果,效果跟前几节还是一样,只是多了一个”用户已在列表中的提示”

下节继续.

在上一节中,我们将一些方法进一步封装到了UserList类中 了,这样做是有很多好处的,以前经常有朋友问我像聊天室怎么做成多房间,像斗地主怎么做成多个桌子在打,其实原理都是一样的,我们只需要对 UserList类做一些改造,就可以达到这个功能了,首先把类的名字改成ChatRoom,这样更好认一些,先看ChatRoom.asc,是由 UserList.asc改造而来的:
function ChatRoom(id){
    this.id = id;
    this.listArray = [];
    this.chatMsgArray = [];
}
 
ChatRoom.prototype.addUser = function(user){
    this.listArray.push(user);
    this.sendUserList();
}
 
ChatRoom.prototype.delUser = function(userName){
    var len = this.listArray.length;
    for(var i=0;i<len;i++){
        if(this.listArray[i].userName == userName){
            this.listArray.splice(i,1);
            break;
        }
    }
    this.sendUserList();
}
 
ChatRoom.prototype.checkOnline = function(userName){
    var len = this.listArray.length;
    for(var i=0;i<len;i++){
        if(this.listArray[i].userName == userName){
            return true;
        }
    }
    return false;
}
 
ChatRoom.prototype.getUserList = function(){
    var result = [];
    var len = this.listArray.length;
    for(var i= 0;i<len;i++){
        result.push(this.listArray[i].userName);
    }
    return result;
}
 
ChatRoom.prototype.sendUserList = function(){
    sendAllRoomNum(this.id,this.listArray.length);
    var len = this.listArray.length;
    for(var i=0;i<len;i++){
        this.listArray[i].client.call(“getUserList”,null,this.getUserList());
    }
}
 
ChatRoom.prototype.sendMsgToClient = function(chatInfo){
    trace(“sendMsgToClient:”+chatInfo);
    this.chatMsgArray.push(chatInfo);
    var len = this.listArray.length;
    for(var i=0;i<len;i++){
        this.listArray[i].client.call(“getMsgInfo”,null,chatInfo);
    }
}
来看看ChatRoom类和UserList类有什么区别:
1.在构造中增加了this.id = id,这里的id表示每个房间的编号,这样可以区别当前是哪个房间.
2.this.chatMsgArray,存储聊天信息的数组,之前是放在main.asc里面的,因为之前只有一个房间,而现在有多个房间,所以每个房间都应该有一个自己的聊天信息列表.

main.asc文件也做了相应的修改:

load(“ChatRoom.asc”);
load(“User.asc”);
application.onAppStart = function() {
    this.roomArray = [];
    this.roomUserNum = [];
    var roomLen = 3;
    for(var i=0;i< roomLen ;i++){
        this.roomArray.push(new ChatRoom(i+1));
        this.roomUserNum.push(0);
    }
}
 
application.onConnect = function(client) {
    this.acceptConnection(client);
    //客户端调用方法
    client.addRoom = function(userName,roomid){
        trace(userName,roomid);
        var isOnline = application.roomArray[roomid - 1].checkOnline(userName);
        if(!isOnline){
            var user = new User(this,userName,roomid);
            application.roomArray[roomid - 1].addUser(user);
            return true;
        }else{
            return false;
        }
    }
    client.getMsg = function(roomid){
        return application.roomArray[roomid - 1].chatMsgArray;
    }
    client.sendMsg = function(roomid,msg){
        var chatInfo = this.userName + ” : ” + msg;
        application.roomArray[roomid - 1].sendMsgToClient(chatInfo);
    }
    client.getAllUserNum = function(){
        return application.roomUserNum;
    }
}
 
application.onDisconnect = function(client) {
    trace(“用户:”+client.userName+” 离开  “+client.roomid);
    this.roomArray[client.roomid - 1].delUser(client.userName);
}
 
 
application.onAppStop = function() {
   
}
 
function sendAllRoomNum(roomid,num){
    application.roomUserNum[roomid - 1] = num;
    var len = application.clients.length;
    for(var i= 0;i<len ;i++){
        application.clients[i].call(“getAllRoomNum”,null,application.roomUserNum);
    }
}
在这个类中我们主要做了以下修改:

1.this.roomArray,用来存储每个房间实例的数组;this.roomUserNum,所有房间当前在线人数的数组;在这里我定义了三个房间,这个可以任意设定.
2.在客户端连接进来时,直接接受客户端连接,不做判断,把判断改在addRoom完成.
3.在客户端发过来的消息中,多加了个参数,就是roomid,用于区别是哪个房间发来的消息,然后根据roomid来判断调用哪个房间实例的方法.
4.增加sendAllRoomNum方法,用于发送实时的所有房间人数,供客户端的大厅显示,正常大厅的可以实时看到人数变化.

客户端代码也要做相应的修改:

package net.smilecn.chat{
   
    import flash.display.Sprite;
    import flash.net.NetConnection;
    import flash.net.Responder;
    import flash.events.NetStatusEvent;
    import flash.events.SyncEvent;
    import flash.events.MouseEvent;
    import fl.controls.TextArea;
      import fl.controls.Button;
      import fl.controls.TextInput;
    import fl.controls.Label;
    import fl.controls.List;
    import fl.data.DataProvider;
 
   
    public class Chat extends Sprite{
       
        private var nc:NetConnection;
        private var rtmpUrl:String = “rtmp://localhost/chat/chatLobby”;
        private var msg:Label;
        private var userNameInput:TextInput;
        private var enterBtn:Button;
        private var button:Button;
        private var textArea:TextArea;
            private var textInput:TextInput;
        private var userList:List;
       
        private var userName:String;
       
        private var allRoomNum :Array = new Array();
        private var roomBtnArray:Array = new Array();
       
        private var onlineArray:Array = new Array();
       
        private var currentRoomid:Number;
   
        public function Chat():void{
            userNameInput = new TextInput();
            userNameInput.move(100,200);
            addChild(userNameInput);
            enterBtn = new Button();
            enterBtn.move(220,200);
            enterBtn.label = “进入”;
            addChild(enterBtn);
            enterBtn.addEventListener(MouseEvent.CLICK,enterBtnClickHandler);
            msg = new Label();
            msg.move(100,230);
            msg.text = “”;
            addChild(msg);
        }
       
        private function enterBtnClickHandler(event:MouseEvent):void{
            if(userNameInput.text!=”"){
                userName = userNameInput.text;
                nc=new NetConnection();
                        nc.addEventListener (NetStatusEvent.NET_STATUS,netStatusHandler);
                nc.client = this;
                nc.connect (rtmpUrl);
               
            }else{
                msg.text = “请输入用户名”;
            }
           
        }
       
        private function netStatusHandler(event:NetStatusEvent):void{
            trace(event.info.code);
            switch (event.info.code) {
                case “NetConnection.Connect.Success” :
                    trace(“连接成功!”);
                    nc.call(“getAllUserNum”,new Responder(getAllUserNumResult,getAllUserNumFault))
                    break;
                case “NetConnection.Connect.Rejected” :
                    trace(“连接被拒绝!”);
                    break;
                case “NetConnection.Connect.Failed” :
                    trace(“连接失败!”);
                    break;
                case “NetConnection.Connect.Closed” :
                    trace(“连接关闭!”);
                    break;
            }
        }
       
        private function getAllUserNumResult(re:Array):void{
            trace(“getAllUserNumResult:”+re);
            removeChild(userNameInput);
            removeChild(enterBtn);
            removeChild(msg);
            allRoomNum = re;
            var xpos = 100;
            var ypos = 200;
            var num = 150;
            var len = allRoomNum.length;
            for(var i=0 ;i<len;i++){
                var btn:Button = new Button();
                btn.x = xpos;
                btn.y = ypos;
                xpos += num;
                btn.label = (i+1) + “号房间(“+allRoomNum[i]+”/”+(i+1) * 10 + “)”;
                addChild(btn);
                btn.addEventListener(MouseEvent.CLICK,roomBtnHandler);
                roomBtnArray.push(btn);
            }
        }
       
       
        private function getAllUserNumFault(fe:*):void{
            trace(fe);
        }
       
        private function intoRoom():void{
            var len = roomBtnArray.length;
            for(var i=0;i<len;i++){
                removeChild(roomBtnArray[i]);
            }
            roomBtnArray = [];
            textArea=new TextArea();
            textArea.setSize (200,300);
                   textArea.move (20,20);
                    addChild (textArea);
 
                    textInput=new TextInput();
            textInput.width = 140;
                    textInput.move (20,330);
                    addChild (textInput);
 
                   button=new Button();
                    button.width=50;
                   button.label=”发送”;
                   button.move (170,330);
            addChild(button);
            button.addEventListener (MouseEvent.CLICK,sendMsg);
               
            userList = new List();
            userList.move(250,20);
            userList.setSize(100,300);
            addChild(userList);
            userList.dataProvider = new DataProvider(onlineArray);
            nc.call(“getMsg”,new Responder(getMsgResult,getMsgFault),currentRoomid);
        }
       
        private function getMsgResult(re:*):void{
            var msg:String = “”;
            var len:Number = re.length;
            for(var i=0;i<len;i++){
                msg += re[i]+”\n”;
            }
            textArea.text = msg;
        }
       
        private function getMsgFault(fe:*):void{
           
        }
       
        private function roomBtnHandler(event:MouseEvent):void{
            var roomid = Number(event.currentTarget.label.substr(0,1));
            if(allRoomNum[roomid - 1] == roomid * 10){
                event.currentTarget.label = “此房间已满”;
            }else{
                nc.call(“addRoom”,new Responder(addRoomResult,addRoomFault),userName,roomid);
                currentRoomid = roomid;
            }
        }
       
        private function addRoomResult(re:*):void{
            trace(re);
            if(re){
                intoRoom();
            }
        }
       
        private function addRoomFault(fe:*):void{
           
        }
       
        private function sendMsg (e:MouseEvent):void{
            nc.call(“sendMsg”,null,currentRoomid,textInput.text);
            textInput.text = “”;
        }
       
        public function getMsgInfo(msg:String):void{
            textArea.appendText(msg+”\n”);
        }
       
        public function getUserList(list:Array):void{
            trace(“getuserList:”+list);
            onlineArray = list;
            if(userList){
                userList.dataProvider = new DataProvider(onlineArray);
            }
        }
       
        public function getAllRoomNum(array:Array):void{
            allRoomNum = array;
            var len = roomBtnArray.length;
            if(len > 0){
                for(var i=0;i<len;i++){
                    roomBtnArray[i].label = (i+1) + “号房间(“+allRoomNum[i]+”/”+(i+1) * 10 + “)”;
                }
            }
        }
       
        public function close():void{
           
        }
    }
   
}
客户端增加一个大厅,然后再进房间,客户端的代码我就不详细讲解了,因为这不是一个讲AS3的教程,会一些AS3的朋友应该都看得懂,如果不懂,可 以先看一下我写的关于AS3的教程,这个客户端代码写得有点乱,只是完成了功能,FMS的教程写完后,我准备写一些关了MVC框架的使用教程,像 cairngorm和pureMVC,到时会将代码处理得更好些.

此条目发表在article分类目录,贴了, 标签。将固定链接加入收藏夹。