从今天起,我们来学习一下 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,到时会将代码处理得更好些.