• 2009-05-15
  • LearningFMS3中文版第5章(3)
  • 文章来源:www.hcxmflash.cn

    5.5.一个更好的双向聊天应用程序

    虽然两个模块的聊天室应用程序是很容易就建立和良好的工作,其唯一目的是显示创建一个双向聊天需要最 少的代码。更好的选择是一个双向的聊天室有一个单一的模块,并且可以告诉哪种方式来设定流。最佳方法是将其写入服务器端脚本。一个FMS3应用程序的概念 是最好的赞赏,当你考虑一个脚本,所有用户使用相同的应用得到不同的信息。您熟悉变量在一个非FMS应用程序改变,但是使用非服务端数据,像数据库。但 是,如果别人使用相同的应用,你这样做不会影响其他人使用相同的应用。例如线上游戏,没有服务器端组件。你的分数将不会影响玩同样游戏的其他人的分数。换 个方式来考虑这个应用程序不是一个共享的。

    在第3章, “非持久客户端远程共享对像”,你学习了共享对象和看到几个用户如何通过一个共同的应用连接,可能会影响他人。没有服务器端脚本应用在这些应用程序中,因 为客户端脚本的沟通通过FMS而不需要一个脚本在服务器上。这一章向您介绍服务器端脚本追踪,不管一个或两个用户在一时间内都使用相同的应用程序。脚本与 在前面讨论的两个模块的应用程序部分有很大的共通点,接下来这个应用程序只使用一个。因为由两个模块所做的工作现在由服务器端脚本来操作了,您只需要一个 单一的模块。

    5.5.1. Keeping Track of Users跟踪用户

    在开始服务端脚本之前,你必须转变一下你的思想:不是使用ActionScript 3.0的,对Flash Media Server 3.0,基本上是您使用的ActionScript 1.0与少数的类。 (如果您不熟悉的ActionScript 1.0 ,把该脚本想像为一个略加修改的JavaScript ) 。这意味着数据没有类型(没有指定数据类型)和您可以使用prototype(原型)来创建类。

    无论如何,这第一个脚本是只会做一件事。使用一个数组与两个要素,它将使用array.pop() 方法来提供两个字符串中的一个,left和right。当客户端离开,使用array.push()来把元素放置回去 。了解这一切是如何工作的,您首先需要了解两个主要服务器端的类,Application和Client。

    服务端ActionScript有大概-15个类。其中三个类一个可以处理XML和两个处理 SOAP。实际上,你需要花大部分的时候在其它4个类上:Application, Client, SharedObject 和 Stream。在这个例子中,你只需要前面两个。

    5.5.1.1. Application类

    顾名思义,Application类处理整个应用程序。每个应用程序,不管有多少客户端连接, 都有一个单一的应用程序对象。是什么使这个类不寻常的是,这是自动实例作为应用。所以,当你写SSAS代码,您不必创建一个对象,它自动为您创建。因此, 使用这个类,您只需这样写:

    application.doSomething...

    这可能会混乱,想像这是少了一个步骤来实例化一个类的实例。

    剩下的章节会探究服务端代码,将会进入到更多的服务端类中,同样也会涉及到Application类的更多的属性,方法,和事件,

    Application 事件

    · onAppStart

    · onConnect

    · onDisconect

    Application 方法

    · rejectConnection

    · acceptConnection

    客户端只要连接到该应用程序就可以触发应用程序事件。第一个客户端打开应用程序触发onAppStart事件。随后使用者(客户)将不会影响这一事件,只要在应用程序运行时-其中最后客户离开了应用程序 20分钟后,-事件并不触发。

    每个连接连接到应用程序都会触发onConnect事件。每个新连接都是通过RTMP来连接的,这个连接可以用Application方法来接受或者拒绝,acceptConnection或者rejectConnection

    每当一个客户端断开,客户从客户数组中移除。您也可以使用Application.onDisconnect事件来查找哪个客户断开了。这出现在服务器端脚本中。

    5.5.1.2. Client类

    像Application类,Client类也自动建立一个它的实例。每一个在应用程序中的客户,都变成应用程序的客户数组中的一部分,每个客户是 一个数组元素。例如,假设Nancy, Pete and Juan都连接到相同的应用程序中,Nancy是第一个连接,Juan是最后一个连接。如下所示:

    Nancy = application.clients[0];

    Pete = application.clients[1];

    Juan= application.clients[2];

    注意Application的客户属性是带个s:clinets(您可以节省大量的调试时间,记住这一点。)像服务器端脚本显示,Client的实例名称是currentClient 。有点像下面这样:

    currentClient = new Client();

    然而,这并不是Client的实例名称如何生效。相反,客户端从它连接到服务器的事件函数得到其参考的名字。图5-4显示正确的命名程序。

    图5-4. Naming Client instance

    currentClient实例对所有客户都有用处,但是紧记,每个连接到应用 程序的用户都是Application.clients数组的一部分。像所有类一样,这个实例也可以使用内置的属性,方法和事件。可是,你也可以建立你自 已的属性和方法和其它任何类一样。在这章中的应用程序中,属性和方法是为application创建的。脚本中包含用户的方法和属性:

    用户的方法写成:

    currentClient.streamSelect = function();

    这个方法是由客户端调用的。看看客户端脚本是怎么办调用的

    添加到currentClient实例的属性叫cliNow,它指定为vidStreams数组的值

    currentClient.cliNow=vidStreams.pop();

    在脚本顶部中的数组,只有两个元素出现:”right”和”left”。 在streamSelect方法中返回给客户端currentClient.clinow属性的值。

    5.5.2. The Server-Side Script服务端脚本

    建立一个服务端脚本,产生一个只有两个参与的客户端名称和拒绝其它的客户端,跟随以下步骤:

    1. 建立一个deux.asc

    2. 输入以下代码

    Example 5-2. deux.asc

    Code View:

    //Two-element array
    vidStreams=["right","left"];
     
    //应用程序第一次启动
    application.onAppStart = function()
    {
      trace("The deux is out of the deck");
    };
     
    //A currentClient (user) attempts to connect
    application.onConnect = function(currentClient)
    {
    //如果数组是空的话就拒绝连接
    //(当前有两个用户正在使用应用程序)
      if (vidStreams.length <= 0)
    {
      application.rejectConnection(currentClient);
    }
     
    //Store array element in currentClient property
      currentClient.cliNow=vidStreams.pop();
     
      application.acceptConnection(currentClient);
      currentClient.streamSelect = function()
      {
            trace("Stream "+currentClient.cliNow+" used");
      //Sent the property to Client object
            return currentClient.cliNow;
      };
    };
    application.onDisconnect = function(currentClient)
    {
    //When currentClient leaves put the element back in array
    vidStreams.push(currentClient.cliNow);
    }

    3. 在Flash Media Server 3目录下的application目录下建立一个deux的目录

    4. 保存deux.asc在deux目录下。或者你也可以把它重命名为main.asc,根据你的喜好。(我比较喜欢命名为应用程序的名称,如果我所有都命名为main.asc,一个不注意被覆盖或者移动,就非常难找了)

    在开始写客户端脚本之前,你需要明白这个服务端脚本到底是在什么。无论何时,一个 客户端连接到应用程序时,它从数组中删除一个元素,然后通过客户端脚本返回它给客户端。该脚本发送字符串,以确定流出的流和进来的流用什么名称。一旦这两 个元素的数组是空的,它不会允许任何其他客户端连接。当一个客户端离开,它把元素放回数组中,然后现在不同的客户端可以加入聊天室了。如果双方当事人离 开,两个更多的客户可以使用它。除其他事项外,此脚本保证一个私人的线上影音聊天室。(当然,除非你自已对谈自己) 。

    5.5.3. The Client-Side Strategy客户端策略

    一旦你完成了服务端脚本,现在你可以来设置客户端脚本了。你可以对前面制作的两个模块的应用程序进行简单的修改,把它转换为一个模块的聊天室

    为了保证流声音和视频正确地输出,您只需要从服务器端脚本得到一个唯一的字符串。该服务器端脚本返回两个字符串中的一个,用来命名流的名称-left和right。AS3.0 的Responder类用来捕捉服务器端的信息,这样您可以使用它在客户端脚本中。

    5.5.3.1. Responder类

    Responder类有处理返回的数据工作的参数,同样也可以处理错误,这个应用程序焦点只在第一个参数上,所以这个例子没有使用处理错误(后面的例子中会使用到)。图5-5显示了呼叫服务端和回应者角色正在捕捉返回的数据:

    Figure 5-5. Responder instance and call to server

    在图5-5中,Responder实例指定了一个回调函数用来捕捉呼叫服务端所返回的。NetConnection.call() [nc.call("streamSelect",responder);] 方法包含了服务端的函数名称,前面的呼叫指向到了下面这行(这行在服务端脚本中):

    currentClient.streamSelect = function();
    5.5.3.2. Responder回调函数

    一旦发出呼叫和指定回应函数,回调函数要求一个用来捕捉从服务端来的数据的参数。图5-6显示了回调函数是怎么样设置的和每个部分做什么。

    图5-6. Responder instance and call to server

    在5-6中,回调函数的参数是用来捕捉从服务端发出的东西的关键,因为它包含返回字符串的值。现在,这脚本使用这个值作为唯一的流名称。回调函数包含下面的switch语句:

    switch (streamSelect)
    {
      case "left" :
            outStream="left";
            inStream="right";
            break;
      case "right" :
            outStream="right";
            inStream="left";
            break;
    }

    Switch语句使用返回的值(在streamSelect中)来决定如何命名流。如果返回的值是”left”,流出的流就命名为”left”,进来的流就命名为”rigth”。反之亦然。

    这个程序可能似乎相当复杂,只是在命名一对流名称上。但使用这个脚本请记住,,两个来自全球各地的用户可以成功地协调沟通。此外,这种协调可以安全的只使用一个单一的模块。

    5.5.4.客户端应用程序

    客户端文件包含一个与应用程序相同名称的logo。这个logo是一个影片,放置在库中,按以下步骤来建立fla文件和as文件:

    1. 建立一个Deux.fla

    2. 把logo放置在左上角(可选)

    3. 在舞台中央建立一个文本内容为:Deux,坐标为:x=250, y=30.

    4. 在文档类中输入Deux

    5. 建立一个Deux.as与Deux.fla同一目录

    6. 在Deux.as中输入以下代码:

    Example 5-3. Deux.as

    Code View:

    package
    {
      import flash.display.Sprite;
      import flash.display.MovieClip;
      import flash.events.NetStatusEvent;
      import flash.net.NetConnection;
      import flash.net.NetStream;
      import flash.media.Camera;
      import flash.media.Microphone;
      import flash.media.Video;
      import flash.net.Responder;
     
      public class Deux extends Sprite
      {
            private var nc:NetConnection;
            private var good:Boolean;
            private var netOut:NetStream;
            private var netIn:NetStream;
            private var cam:Camera;
            private var mic:Microphone;
            private var responder:Responder;
            private var vidOut:Video;
            private var vidIn:Video;
            private var outStream:String;
            private var inStream:String;
     
            public function Deux ()
            {
                 var rtmpNow:String="rtmp://192.168.0.11/deux";
                 nc=new NetConnection;
                 nc.connect (rtmpNow);
                 nc.addEventListener (NetStatusEvent.NET_STATUS,getStream);
            }
     
            private function getStream (e:NetStatusEvent):void
            {
                 good=e.info.code == "NetConnection.Connect.Success";
                 if (good)
                 {
                       responder=new Responder(streamNow);
                       nc.call ("streamSelect",responder);
                 }
            }
     
            private function streamNow (streamSelect:String):void
            {
                 setCam ();
                 setMic ();
                 setVid ();
     
                 switch (streamSelect)
                 {
                       case "left" :
                            outStream="left";
                            inStream="right";
                            break;
                       case "right" :
                            outStream="right";
                            inStream="left";
                            break;
                 }
     
                 //Publish local video
                 netOut=new NetStream(nc);
                 netOut.attachAudio (mic);
                 netOut.attachCamera (cam);
                 vidOut.attachCamera (cam);
                 netOut.publish (outStream, "live");
     
                 //Play streamed video
                 netIn=new NetStream(nc);
                 vidIn.attachNetStream (netIn);
                 netIn.play (inStream);
            }
     
            private function setCam ():void
            {
                 cam=Camera.getCamera();
                 cam.setMode (240,180,15);
                 cam.setQuality (0,85);
            }
     
            private function setMic ():void
            {
                 mic=Microphone.getMicrophone();
                 mic.rate=11;
                 mic.setSilenceLevel (12,2000);
            }
     
            private function setVid ():void
            {
                 vidOut=new Video(240,180);
                 addChild (vidOut);
                 vidOut.x=25;
                 vidOut.y=110;
     
                 vidIn=new Video(240,180);
                 addChild (vidIn);
                 vidIn.x=vidOut.x+260;
                 vidIn.y=110;
            }
      }
    }

    当你运行这个程序,你会看到比Easy应用程序更完美。不管如何,当前的两个用户会看到同样的东西。图5-7显示了当应用程序运行正确时,预期你可以看到的。

    图5-7. Two-way chat with one module

    通过使用一个单一的模块,您可以使用一个单一的文件名称。此应用程序可以与世界各地一起测试。即使它是一个简单的应用,它也可以用来与世界各地沟通。

  • x
Powered by LuoYe Lab , Skin in Kaven,XiaoXu RSS Copyright © 2008