You are on page 1of 57

Q http://www.xiaoshuo520.net/html/Book/63/63166/1090472.

shtml

1、Flex 3 使用 Red5 和 RSO 進行資


料交 換
AS3, FMS/FCS, Flex/Flash, Flex2, Flex3, Red5 GgNET 06 月 1st. 2008, 2:10am
Flex 3 要 讓 Server 同 步 SharedObject 資 料 , 必 須 使 用
SharedObject.setProperty(”name”, value); 而 不 能 直 接 使 用
SharedObject.data["name"] = value;
以下就是我簡化後的測試程式

<?xml version=”1.0″ encoding=”utf-8″?>


<mx:Application xmlns:mx=”http://www.adobe.com/2006/mxml”
layout=”absolute” width=”343″>
<mx:Script>
<![CDATA[
import com.live.spaces.chuiwenchiu.net.MyNetConnection;
public var conn:MyNetConnection = new MyNetConnection();
public var so:SharedObject = null;
private function btnStart_click():void{
conn.addEventListener(NetStatusEvent.NET_STATUS, function
onStatus(e:NetStatusEvent):void{
if (e.info['code'] == “NetConnection.Connect.Success”){
so = SharedObject.getRemote(”SampleChat”, conn.uri, false);
so.addEventListener(AsyncErrorEvent.ASYNC_ERROR,
function(e:AsyncErrorEvent):void{
var msg:String = e.error.message;
});
so.addEventListener(SyncEvent.SYNC, function(e:SyncEvent):void{
if (so.data["SampleChat"] == undefined){
return;
}
txtData.text += so.data["SampleChat"] + ‘\n’;
});
so.connect(conn);
btnSend.enabled = true;
}else{
trace(e.info['code']);
}
});
conn.connect(”rtmp://localhost/SOSample”);
}

private function btnSend_click():void{


if (txtMsg.text.length == 0){
return;
}
so.setProperty(”SampleChat”, txtMsg.text);
}
]]>
</mx:Script>
<mx:Button x=”23″ y=”10″ label=”Connect” click=”btnStart_click()” />
<mx:Button id = “btnSend” x=”218″ y=”229″ label=”Send” enabled=”false”
click=”btnSend_click()”/>
<mx:TextArea x=”23″ y=”40″ id=”txtData” width=”187″ height=”181″/>
<mx:TextInput x=”23″ y=”229″ width=”187″ id=’txtMsg’/>
</mx:Application>
本 文 来 源 于 冰 山 上 的 播 客 http://xinsync.xju.edu.cn , 原 文 地 址 :
http://xinsync.xju.edu.cn/index.php/archives/1942

2、 Red5 呼叫客 户端方 法


想要在客户端从你的 RED5 应用程序呼叫方法,
首先需要一个当前连接对象的引用
Import org.red5.server.api.IConnection;
Import org.red5.server.api.Red5;
Import org.red5.server.api.service.IServiceCapableConnection;
..
IConnection conn=Red5.getConnectionLocal();

如果连接继承 IServiceCapableConnection 接口,他支持另一端呼叫方法。


If(conn instanceof IServiceCapableConnection){
IServiceCapableConnection sc=(ISerciceCapableConnection) conn;
Sc.invoke(“the_method”,new Object[]{“One”,1});
}

如果你想要呼叫方法结果,你必须提供一个继承 IPendingServiceCallback 接口的


类。
Import org.red5.server.api.service.IPendingService;
Import org.red5.server.api.IPendingServiceCallback;
Class MyCallback implements IPendingServiceCallback{
Public void resultReceived(IPendingServiceCall call){
//Do something with “call.getResult()”
}
}

方法呼叫类似下面;
If(conn instanceof IServiceCapableConnection){
IServiceCapableConnection sc=(IServiceCapableConnection) conn;
Sc.invoke(“the_method”,new Object[]{“One”,1},new MyCallback());

当然你能在你的应用程序中继承接口,并且传递相关到应用程序实例

3、 AS3 与 Red5 之间的 参数传 递

参数传递是最基本的,之前是 AS2,现在用 AS3 与 Red5 0.63/0.7 了,几乎没什么变化.不过


Flash/Flex 这 边 的 可 以 传 递 的 参 数 也 就 多 了 一 些 . 就 基 本 的 是
String,int,Number,Boolean,Array, 对 应 到 red5 这 边 是
String,int,double,boolean,List

/**
* @(#)ParamRed5.as
* @author soda.C
* @version 1.0
*
Copyright (C), 2007 soda.C
*
This program is protected by copyright laws.
* @data 2008-2-19
*/
package org.sujun.red5.test
{
import flash.display.Sprite;
import flash.net.NetConnection;
import flash.events.NetStatusEvent;
import flash.events.SecurityErrorEvent;
import flash.net.Responder;
/**
* 测试 flash 与 red5 之间参数的传递
*/
public class ParamRed5 extends Sprite
{
private var netConnection:NetConnection;

public function ParamRed5():void


{
netConnection = new NetConnection();

netConnection.addEventListener(NetStatusEvent.NET_STATUS,
netStatusHandler);
netConnection.addEventListener(SecurityErrorEvent.SECURITY_ERROR
, securityErrorHandler);

netConnection.connect("rtmp://localhost/paramtest");

private function netStatusHandler(event:NetStatusEvent):void


{
trace("连接状态:" + event.info["code"]);

switch (event.info["code"])
{
case "NetConnection.Connect.Success":
trace("连接成功…..");
// 呼 叫 服 务 器 的 baseParam 方 法 , 传 递 基 本 参 数 ,
string,int,number,Boolean
netConnection.call("baseParam", new
Responder(baseParamResult),"soda.C",24,1000.1,false);
//封装数组,int
var ary:Array = new Array();
ary.push(1);
ary.push(2);
ary.push(3);
//封装数组,String
var ary1:Array = new Array();
ary1.push("a");
ary1.push("b");
ary1.push("c");
netConnection.call("receiveArray", new
Responder(baseParamResult),ary,ary1);
break;
case "NetStream.Play.StreamNotFound":
trace("Stream not found: ");
break;
}
}

private function baseParamResult(obj:Object):void


{
trace(obj);
trace("响应了…..");
}
private function securityErrorHandler(event:SecurityErrorEvent):void
{
trace("securityErrorHandler: " + event);
}
}
}
接下来看 Red5 服务端的 java 代码
ParamRed5App.java,该类继承了 ApplicationAdapt
package org.sujun.red5.test;

import java.util.List;

import org.red5.server.adapter.ApplicationAdapter;

/**
* 存放被 flash 客户端调用的方法
*/
public class ParamRed5App extends ApplicationAdapter
{
public ParamRed5App()
{
System.out.println("被初始化了……");
}
/**
* 接受服务器传过来的基本参数
*/
public void baseParam(String name, int age, double value, boolean flag)
{
System.out.println("—-name—-" + name);
System.out.println("—-age—-" + age);
System.out.println("—-value—-" + value);
System.out.println("—-flag—-" + flag);
}
/**
* 接受客户端传递过来的数组
*/
public void receiveArray(List<integer> intArray, List<string> strArray)
{
for(int i = 0; i < intArray.size(); i++)
{
System.out.println("—-intArray—-" + intArray.get(i).intValue());
}
for(int i = 0; i < intArray.size(); i++)
{
System.out.println("—-strArray—-" + strArray.get(i));
}
}
}</string></integer>

看结果…

要想调用客户端所有的 public 函数,客户端的 Netconnection 的 client 必须为 this;


代码很简单…….直接复制过去,建立一个 Red5 应用就可以使用了

4、ActionScript(AS3) 与 RED5 通信

第一步:设置环境
下载 red5 http://osflash.org/red5
安装版本 当前最新版本为:Red5 v0.6.3 Final released 。
另外一中方法是下载源码编译运行。我这里就不介绍了。
注意安装的时候,有个选项为 “注册为服务” 这个不要选,否则运行时候会报错。

下载 eclipse 3.2 以上版本,最好装一个 flex3 插件,(as3 的编辑器)


下载 jdk 或者 jre (red5 安装的时候要选择 jre 路径)
ant 这东西,如果不熟悉的话可以不用。直接用 eclipse 编译就可以。
客户端代码
package test
{
import flash.display.Sprite;
import flash.events.NetStatusEvent;
import flash.net.NetConnection;
import flash.net.ObjectEncoding;
import flash.net.Responder;
public class TestCon extends Sprite{
private var nc:NetConnection;
public function TestCon():void{
nc = new NetConnection( );
nc.objectEncoding = ObjectEncoding.AMF0;
nc.addEventListener( NetStatusEvent.NET_STATUS , netStatus );
/*
* 连接服务端,连接成功后方可调用服务端 java 代码
* 参数 true 可以不写。
*/
nc.connect(”rtmp://localhost/red5_server”,true);
/*
* 存储服务器的返回结果 ,如果返回结果没用问题调用 result 方法,
* 否则调用 status 方法
*/
var response:Responder = new Responder(result,status);

/*
* 调用服务端的 callme 方法,并把参数 luqinglong,123456 两个参数传过去
* 把服务端返回结果存放在 response 对象当中。
* 注意:服务端 callme 的参数个数要和此一一对应, 应该为
* public String callme(Stirng username,String password);否则回出错)
*/
nc.call(”callme”,response,”luqinglong”,”123456″);
}
/*
* 连接上服务器,做其他操作的发生的错误。比如参数传的有问题。
*/
private function status(error:Object):void
{
trace(”连接结果错误”+error);
}
/*
* 打印返回结果,一定要有个参数 re,否则报错,
* 现在只试过返回 String 格式的
* 返回 xml 和复杂数据结构还在研究当中
*/
private function result(returnobj:Object):void{
trace(”服务端返回结果为:…..”+returnobj);
}
/*
* 网络连接错误,根本就没连上
*/
private function netStatus (event:NetStatusEvent):void{
trace( “连接信息……..\n”+event.info.code);
if (event.info.code == “NetConnection.Connect.Rejected” ){
trace( event.info.application );
}
}
}
}
服务端代码
package test;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.red5.server.adapter.ApplicationAdapter;
import org.red5.server.api.IConnection;
public class TestCon extends ApplicationAdapter{
private static final Log log = LogFactory.getLog(TestCon.class );
public TestCon(){
}
public String callme(String username,String password){
String info = “服务端接收到你的调用请求,”;
info+= “你传过来的参数如下”;
info+= “用户名:”+username ;
info+= “密码是:”+password ;
return info ;
}

public boolean appStart(){


log.info( “Red5First.appStart” );
return true;
}
public void appStop(){
log.info( “Red5First.appStop” );
}
public boolean appConnect(IConnection conn , Object[] params){
log.info( “Red5First.appConnect ” + conn.getClient().getId() );
return true;
}
public void appDisconnect( IConnection conn , Object[] params ){
log.info( “Red5First.appDisconnect ” + conn.getClient().getId() );
}
}
第一步:新建元件
包括三部分
1.消息显示区,命名为 show_txt
2.消息发送区命名为 sent_txt
3.发送按钮,命名为 sent_btn
并绑定类 CharRoom

第二步:编写绑定类
package test2{
import fl.controls.TextArea;
import fl.controls.TextInput;
import flash.display.DisplayObject;
import flash.display.MovieClip;
import flash.events.MouseEvent;
public class ChatRoom extends MovieClip{
private var netclient:NetClient ;
public function ChatRoom(){
super();
//发送按钮
send_btn.addEventListener(MouseEvent.CLICK,sendMessage);
//连接服务端的
netclient = new NetClient(this);
}
/**
*服务端调用此方法来更新消息列表
*/
public function receiveBroadMes(mes:Object):void{
/*
*显示窗口加入消息 ,并且清空发送窗口
*因为和元件绑定所以直接访问实例名:show_txt
*/
show_txt.appendText(mes+”\n”);
send_txt.text = “” ;
}
/**
* 当点击发送按钮的时候,发送消息
*/
private function sendMessage(e:MouseEvent):void{
this.netclient.broadcastMes(send_txt.text);
}
}
}

第三步:编写客户端连接类
package test2{
import flash.events.NetStatusEvent;
import flash.net.NetConnection;
import flash.net.ObjectEncoding;
public class NetClient extends NetConnection
{
private var chatroom:ChatRoom ;
public function NetClient(chatroom:ChatRoom) {
this.chatroom = chatroom ;
this.objectEncoding = ObjectEncoding.AMF0;
this.addEventListener(NetStatusEvent.NET_STATUS , netStatus);
this.connect(”rtmp://192.168.0.20/red5_server”);
}
/**
* 服务端调用此方法
* 更新显示窗口
*/
public function updateMes(obj:Object){
trace(”test call back ….”+obj);
this.chatroom.receiveBroadMes(obj);
}
/**
* 一个客户端发消息所以客户端都能收到
* 调用服务端的广播方法
* 通知所有的在线用户
* @param mes 要发送的信息
*/
public function broadcastMes(mes:String):void{
this.call(”broadcastMes”,null,mes);
}
// 网络连接情况
private function netStatus (event:NetStatusEvent):void{
trace(”net connection case is ………”+event.info.code);
}
}
}

第四步:编写服务端类
package test;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.red5.server.adapter.ApplicationAdapter;
import org.red5.server.api.IClient;
import org.red5.server.api.IConnection;
import org.red5.server.api.IScope;
import org.red5.server.api.Red5;
import org.red5.server.api.service.IServiceCapableConnection;
public class NetServer extends ApplicationAdapter{
private static final Log log = LogFactory.getLog(TestCon.class);
public NetServer(){
}
/**
* broadcast the message .notify all client
*/

public boolean broadcastMes(String message){


log.info(”receive message is “+message) ;
log.info(”server trace 1…………..”) ;
IConnection currentcon = Red5.getConnectionLocal();
IScope scope = currentcon.getScope();
Iterator<IConnection> conns = scope.getConnections();
/*
* loop all the online user then broadcase message .
*/
while(conns.hasNext()){
IConnection conn = conns.next();
if(conn instanceof IServiceCapableConnection){
IServiceCapableConnection iservice = (IServiceCapableConnection)conn ;
iservice.invoke(”updateMes”,new Object[]{message});
}
}
log.info(”server trace 2…………..”) ;
return true ;
}
}

第五步:把元件发到主场景中
配置这里就不做说明,写的其他几遍文章都有详细说明。
本 文 来 源 于 冰 山 上 的 播 客 http://xinsync.xju.edu.cn , 原 文 地 址 :
http://xinsync.xju.edu.cn/index.php/archives/1559
5、 RED5 中使用 SharedObject
要在 RED5 中使用远程 sharedObject,必须注意以下几点:
1,
sharedObject = SharedObject.getRemote( “ 远 程 sharedObject 名 称 “ , nc.uri,
true );
对借助服务器在多个客户端间共享的对象返回一个 SharedObject 的引用。
nc 为 NetConnection 对象。
2,
sharedObject.client = this;
客户端对象为本身。便于广播消息,监听事件。
3,
sharedObject.connect( nc );
通过 nc 链接到服务器。
4,
在与 flashplayer 9 以前发布的 FMS 服务器进行 sharedObject 交互时,
一定要指定 nc.objectEncoding = flash.net.ObjectEncoding.AMF0;
否则 flash 无法监听到 SYNC 事件。目前使用的 RED5 服务器也是如此。
FLEX 实例代码:
<?xml version=”1.0″ encoding=”utf-8″?>
<mx:Application xmlns:mx=”http://www.adobe.com/2006/mxml”
layout=”absolute” creationComplete=”initFun()”>
<mx:Script>
<![CDATA[
import mx.collections.ArrayCollection;
private var nc:NetConnection;
private var clientID:Number;
private var soChat:SharedObject;
private var arr:Array = new Array();
[Bindable]
private var con:ArrayCollection;
private function initFun():void
{
nc = new NetConnection();
nc.objectEncoding = flash.net.ObjectEncoding.AMF0;
nc.addEventListener(NetStatusEvent.NET_STATUS,statusHandler);
nc.connect( “rtmp://localhost/MySOSample” );
}
private function statusHandler(event:NetStatusEvent):void
{
if(event.info.code == “NetConnection.Connect.Success”){
connectToChat();
}
}
private function connectToChat():void
{
soChat = SharedObject.getRemote( “wxwred5″, nc.uri, true );
soChat.addEventListener( NetStatusEvent.NET_STATUS, netStatusHandler );
soChat.addEventListener( AsyncErrorEvent.ASYNC_ERROR,
asyncErrorHandler );

//为共享对象添加异步事件,这也是服务器同步处理多客户端最关键的地方,
//一个客户端进行了什么操作,其它客户端也会有相应的变化,就是通过此事件来完成的
soChat.addEventListener( SyncEvent.SYNC, sharedObjectSyncHandler );

soChat.client = this;
soChat.connect( nc );
soChat.send( “getName” );
}
public function getName():void
{
trace( “getName: ” + txtUser.text );

soChat.setProperty(”key”,txtUser.text);
}
public function newName(str:String):void
{
arr.push(str);
soChat.setProperty(”arr”,arr);
con = new ArrayCollection(arr);
}
private function sharedObjectSyncHandler( event:SyncEvent ):void
{
for (var chng:uint; chng<e.changeList.length; chng++)
{
switch (e.changeList[chng].code)
{
case "clear" :
break;
case "success" :
trace (text_so.data.msg);
break;
case "change" ://一个客户端改变数据会更新所有客户端
textArea.htmlText+=text_so.data.msg + "\n";
break;
}
}
}
trace( “sharedObjectSyncHandler:code: ” + event.changeList );
arr.push(event.target.data.key);
con = new ArrayCollection(arr);
}
private function netStatusHandler( event:NetStatusEvent ):void
{
trace( “netStatusHandler:code: ” + event.info.code );
}
private function asyncErrorHandler( event:AsyncErrorEvent ):void
{
trace( “asyncErrorHandler:code: ” + event.error );
}
]]>
</mx:Script>
<mx:TextInput id=”txtUser” horizontalCenter=”1″ verticalCenter=”-109″/>
<mx:Button click=”getName()” label=”Button” horizontalCenter=”0″
verticalCenter=”-53″/>
<mx:List id=”listView” height=”203″ dataProvider=”{con}”
verticalCenter=”68″ horizontalCenter=”0″></mx:List>
</mx:Application>
本 文 来 源 于 冰 山 上 的 播 客 http://xinsync.xju.edu.cn , 原 文 地 址 :
http://xinsync.xju.edu.cn/index.php/archives/1549

基本 狐狐 柏夫
6、 RED5 服务 器调用 客户端 程序

callClient(uid,”makeOnlineList”,new Object[]{onLineList});
private boolean callClient(String uid, String method_name,Object[] obj)
{
IConnection toClient=onLineClient.get(uid);
if (toClient instanceof IServiceCapableConnection)
{
//转发消息
IServiceCapableConnection sc = (IServiceCapableConnection) toClient;
sc.invoke(method_name, obj);
return true;
}
return true;
}
其中 uid 为: IConnection.getClient().getId(); 其实就是客户端连接到服务器时服务器
给与的 ID 值
其中”makeOnlineList”为:客户端函数
其中 new Object[]{onLineList}为:传递的参数。
调用方法是:sc.invoke(method_name, obj);
先通过 IConnection toClient=onLineClient.get(uid);来获取指定 id 对象的客户端,然
后调用
名称为 method_name 的函数,参数为 obj;
要想调用客户端所有的 public 函数,客户端的 Netconnection 的 client 必须为 this;

AS3 端
flash 这边的可以传递的参数也就多了一些.就基本的是
String,int,Number,Boolean,Array, 对 应 到 red5 这 边 是
String,int,double,boolean,List

//封装数组,int
var ary:Array = new Array();
ary.push(1);
ary.push(2);
ary.push(3);
//封装数组,String
var ary1:Array = new Array();
ary1.push("a");
ary1.push("b");
ary1.push("c");
netConnection.call("receiveArray", new
Responder(baseParamResult),ary,ary1);

JAVA
/**
* 接受客户端传递过来的数组
*/
public void receiveArray(List<Integer> intArray, List<String> strArray)
{
for(int i = 0; i < intArray.size(); i++)
{
System.out.println("----intArray----" + intArray.get(i).intValue());
}
for(int i = 0; i < intArray.size(); i++)
{
System.out.println("----strArray----" + strArray.get(i));
}
}

JAVA 回调 AS3 函数

((IServiceCapableConnection) conn).invoke(” 函式名稱” , new Object[]{ 參數 1,


參數 2, …參數 n}, this);

//說話
public void talk(String myName, String msg){
log.info("talk(" + myName + ", " + msg + ")");

Iterator<IConnection> it = scope.getConnections();
while (it.hasNext()) {
IConnection conn = it.next();

if (conn instanceof IServiceCapableConnection) {


//通知所有人
((IServiceCapableConnection) conn).invoke("onSomeoneTalking", new
Object[]{myName, msg}, this);
}
}
}

callClient(uid,”makeOnlineList”,new Object[]{onLineList});
private boolean callClient(String uid, String method_name,Object[] obj)
{

IConnection toClient=onLineClient.get(uid);
if (toClient instanceof IServiceCapableConnection)
{
//转发消息
IServiceCapableConnection sc = (IServiceCapableConnection) toClient;
sc.invoke(method_name, obj);
return true;
}

return true;
}

其中 uid 为: IConnection.getClient().getId(); 其实就是客户端连接到服务器时服务器


给与的 ID 值

其中”makeOnlineList”为:客户端函数

其中 new Object[]{onLineList}为:传递的参数。

调用方法是:sc.invoke(method_name, obj);

先通过 IConnection toClient=onLineClient.get(uid);来获取指定 id 对象的客户端,然


后调用

名称为 method_name 的函数,参数为 obj;

要想调用客户端所有的 public 函数,客户端的 Netconnection 的 client 必须为 this;

客户端的 调用方法:

myconn.call("broadcastMes", null,str_msg);

7、 Red5 中的 共享对 象
在应用程序中存储共享对象的方法在接口 ISharedObjectService 中定义。当在服务端脚
本里处理共享对象时,要特别注意他们被创建的范围。当一个房间被创建的时候为了去创建
一个新的共享对象,你可以在你的应用程序里面重写方法 roomStart:
Import org.red5.server.adapter.ApplicationAdapter;
Import org.red5.server.api.IScope;
Import org.red5.server.api.so.ISharedObject;
Public class SampleApplication extends ApplicationAdapter{
Public Boolean roomStart(IScope room){
If(!super.roomStart(room))
Return false;
createSharedObject(room,”sampleSO”,true);
ISharedObject so=getSharedObject(room,”sampleSO”);
//.现在你可以用这个共享对象作一些事情。
Return true;
}
每次当第一个用户一个应用程序房间,例如:通过 rtmp://server/application/room1,一
个共享对象 sampleSO 被服务创建。
如果一个连向主程序的共享对象要是被创建,例如 rtmp://server/application,相同的操
作在方法 appStart 中被执行。
一个共享对象提供的更多方法的信息请参考接口 ISaredObjec 的 API 文档。
为 了 得 到 共 享 对 象 的 改 变 通 报 类 似 fcs/fms 的 onSync , 监 听 器 必 须 继 承 接 口
ISharedObjectListener.
Import org.red5.server.api.so.ISharedObject;
Import org.red5.server.api.so.ISharedObjectListener;

Public class SampleSharedObjectListener implements ISharedObjectListener


{
Public void onSharedObjectUpdate(ISharedObject so,String key,Object value)
{
//共享对象 so 的属性<key>
//被修改成<value>
}
Public void onSharedObjectDelete(ISharedObject so,String key){
{
//共享对象 so 的属性为<key〉的被删除
}
Public void onSharedObjectSend(ISharedObject so,String method,List params)
{
//共享对象 so 的方法<method>被呼叫带有参数<params>
}
//其他的方法在接口里被描述
}

此外,监听在共享对象里必许获得注册。
ISharedObject so=getSharedObject(scope,”sampleSO”);
So.addSharedObjectListener(new SampleSharedObjectListener());

应用程序代码里的改变
一个共享对象也能被服务端改变。
ISharedObject os=getSharedObject(scope,”sampleSO”);
So.setAttribute(“fullname”,”Sample user”);

这里所有被署名的客户端得属性新建和改变最好作为注册句柄被通报。
如果关于共享对象的若干动作在一个客户端更新事件中被结合,方法 beginUpdate 和
endUpdate 必须被用到:
ISharedObject so=getSharedObject(scope,”sampleSO”);
So.beginUpdate();
So.setAttribute(“One”,” 1”);
So.setAttribute(“Two”,”2”);
So.removeAttribute(“Three”);
So.endUpdate();

8、Flex 3 使用 Red5 和 RSO 進行資


料交 換
Flex 3 要 讓 Server 同 步 SharedObject 資 料 , 必 須 使 用
SharedObject.setProperty(”name”, value); 而 不 能 直 接 使 用
SharedObject.data["name"] = value;
以下就是我簡化後的測試程式

<?xml version=”1.0″ encoding=”utf-8″?>


<mx:Application xmlns:mx=”http://www.adobe.com/2006/mxml”
layout=”absolute” width=”343″>
<mx:Script>
<![CDATA[
import com.live.spaces.chuiwenchiu.net.MyNetConnection;
public var conn:MyNetConnection = new MyNetConnection();
public var so:SharedObject = null;
private function btnStart_click():void{
conn.addEventListener(NetStatusEvent.NET_STATUS, function
onStatus(e:NetStatusEvent):void{
if (e.info['code'] == “NetConnection.Connect.Success”){
so = SharedObject.getRemote(”SampleChat”, conn.uri, false);
so.addEventListener(AsyncErrorEvent.ASYNC_ERROR,
function(e:AsyncErrorEvent):void{
var msg:String = e.error.message;
});
so.addEventListener(SyncEvent.SYNC, function(e:SyncEvent):void{
if (so.data["SampleChat"] == undefined){
return;
}
txtData.text += so.data["SampleChat"] + ‘\n’;
});
so.connect(conn);
btnSend.enabled = true;
}else{
trace(e.info['code']);
}
});
conn.connect(”rtmp://localhost/SOSample”);
}

private function btnSend_click():void{


if (txtMsg.text.length == 0){
return;
}
so.setProperty(”SampleChat”, txtMsg.text);
}
]]>
</mx:Script>
<mx:Button x=”23″ y=”10″ label=”Connect” click=”btnStart_click()” />
<mx:Button id = “btnSend” x=”218″ y=”229″ label=”Send” enabled=”false”
click=”btnSend_click()”/>
<mx:TextArea x=”23″ y=”40″ id=”txtData” width=”187″ height=”181″/>
<mx:TextInput x=”23″ y=”229″ width=”187″ id=’txtMsg’/>
</mx:Application>

9、 Red5 系统 中的其 他事件 函数

对许多应用程序,现存包含和 RED5 不是相关的应用程序逻辑的类需要重用。为了使他们


在客户端通过 RTMP 协议连接的时候可用,这些类需要作为 RED5 事件函数被注册。
现在有两种方法注册这些事件:
1. 把他们加到配备文件中;
2. 从应用程序中手动注册他们;

通过下面的代码事件函数被客户端执行:

nc=new NetConnection();
nc.connect(“rtmp://localhost/myapp”);
nc.call(“handler.method”,nc,”Hello world!”);
如果一个事件被注册。Red5 总是在检查上下文配备文件以前在定义范围内寻找他。
配备文件里的事件函数
方法最适合事件处理在应用程序运行范围,他们在应用程序寿命期间是不需要改变的。
注册类 com.fancycode.red5.HandlerSample 为事件 sample,下面的 bean 需要加到
web-inf/red5-web.xml 中。
<bean id=”sample.service”
Class=”com.fancycode.red5.HandlerSample”
Singleton=”true”/>
注:bean 的 id 是由事件名称和关键字 service 构成。
应用程序代码里的事件
所有使用事件处理的应用程序在各种范围或者想要改变事件处理是不同的。从服务段代码需
要一个方法注册他们。这些事件总是凌驾于在 red5-web.xml 中配置的事件。需要注册的方
法在接口 IServiceHnadlerProvider 中被描述,通过 ApplicationAdapter 被执行。
public boolean appStart(IScope app){
if(!super.appStart(scope))
return false;
object handler=new com.fancycode.red5.HandlerSample();
app.registerServiceHandler(“sample”,handler);
return true;
}
注:在这个例子中,仅仅应用程序范围有 sample 事件,不适合子范围.如果事件在 room
中一样可用,必须在 roomStart 时在 room 范围内注册。
本 文 来 源 于 冰 山 上 的 播 客 http://xinsync.xju.edu.cn , 原 文 地 址 :
http://xinsync.xju.edu.cn/index.php/archives/1205

10、 Red5 初探: 聊天室

照著 Red5 安裝目錄下的文件: {Red5}\doc\HOWTO-NewApplications.txt ,可以建


立 Java Server 上的程式,以及 Flash Client 端的程式。
於是,我的初探程式,就來試做一下聊天室吧!

透 過之前介紹過的線上影音教學: http://www.flashextensions.com/tutorials.php,
不懂 Eclipse 的人可以稍微認識一下基本開發環境,以及如何在 Eclipse 中設定開發
Red5 程式的環境。然後要順便認識一下 Ant 開發工具,了解如何 Compile 開發好的
Java 程式並啟動 Red5 Server。
我的資料夾放在這:{Red5}\webapps\FirstRed5App
依照 HOWTO-NewApplications.txt 的說明,修改以下檔案
{Red5}\webapps\FirstRed5App\WEB-INF\web.xml
<context-param>
<param-name>webAppRootKey</param-name>
<param-value>/FirstRed5App</param-value>
</context-param>{Red5}\webapps\FirstRed5App\WEB-INF\red5-web.xml
<bean id="web.handler"
class="idv.ben.red5.Application"
singleton="true" />{Red5}\webapps\FirstRed5App\WEB-INF\red5-
web.properties
webapp.contextPath=/FirstRed5App
webapp.virtualHosts=localhost, 127.0.0.1 所 以 , 我 這 個 Context 的 位 置 在
/FirstRed5App,並且將會由 idv.ben.red5.Application 這個類別作處理!
以下,是這支 Java 程式,會處理來自 Flash 送來的資訊,並廣播到所有連線端!
package idv.ben.red5;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.red5.server.adapter.ApplicationAdapter;
import org.red5.server.api.IConnection;
import org.red5.server.api.IScope;
import org.red5.server.api.Red5;
import org.red5.server.api.scheduling.IScheduledJob;
import org.red5.server.api.scheduling.ISchedulingService;
import org.red5.server.api.service.IPendingServiceCall;
import org.red5.server.api.service.IPendingServiceCallback;
import org.red5.server.api.service.IServiceCapableConnection;

public class Application extends ApplicationAdapter implements


IPendingServiceCallback {

protected static Log log = LogFactory.getLog(Application.class.getName());

//存一份 連線物件 與 登入名稱 的對照表


private List<MyConn> connNameList = new ArrayList<MyConn>();
private String jobId = null;

//實作 IPendingServiceCallback 介面所要實作的 method


public void resultReceived(IPendingServiceCall call) {
// TODO Auto-generated method stub
}

@Override
public boolean appStart(IScope app) {
// TODO Auto-generated method stub
if(!super.appStart(app)){
return false;
}

IScheduledJob job = new MyJob(this);


jobId = this.addScheduledJob(5000, job);

return true;
}

@Override
public void appStop(IScope app) {
// TODO Auto-generated method stub
this.removeScheduledJob(jobId);

super.appStop(app);
}

//新加入
public void join(String myName){
log.info("join(" + myName + ")");

IConnection current = Red5.getConnectionLocal();

//加到名單
connNameList.add(new MyConn(current, myName));

Iterator<IConnection> it = scope.getConnections();
while (it.hasNext()) {
IConnection conn = it.next();

if (conn instanceof IServiceCapableConnection) {


//通知所有人
((IServiceCapableConnection) conn).invoke("onNewMemberJoined", new
Object[]{myName}, this);
}
}
}

//說話
public void talk(String myName, String msg){
log.info("talk(" + myName + ", " + msg + ")");

Iterator<IConnection> it = scope.getConnections();
while (it.hasNext()) {
IConnection conn = it.next();

if (conn instanceof IServiceCapableConnection) {


//通知所有人
((IServiceCapableConnection) conn).invoke("onSomeoneTalking", new
Object[]{myName, msg}, this);
}
}
}

//移動
public void walk(String myName, double x, double y){
log.info("walk(" + myName + ", " + x + ", " + y + ")");

Iterator<IConnection> it = scope.getConnections();
while (it.hasNext()) {
IConnection conn = it.next();

if (conn instanceof IServiceCapableConnection) {


//通知所有人
((IServiceCapableConnection) conn).invoke("onSomeoneWalking", new
Object[]{myName, x, y}, this);
}
}
}

//檢查斷線
public void checkDisconnection(){
log.info("checkDisconnection()");

//IConnection current = Red5.getConnectionLocal();

Iterator<IConnection> it = scope.getConnections();
for(int i=connNameList.size()-1; i>=0; i--){
MyConn myConn = (MyConn)connNameList.get(i);

boolean isDisconnect = true;


while (it.hasNext()) {
IConnection conn = it.next();
if (conn.equals(myConn.conn)) {
isDisconnect = false;
break;
}
}

if(isDisconnect){
//通知所有人 有人斷線
notifyDisconnect(myConn.name);

//從名單移除
connNameList.remove(i);
}
}

//通知所有人 有人斷線
private void notifyDisconnect(String myName){
log.info("notifyDisconnect(" + myName + ")");

Iterator<IConnection> it = scope.getConnections();
while (it.hasNext()) {
IConnection conn = it.next();

if (conn instanceof IServiceCapableConnection) {


//通知所有人
((IServiceCapableConnection) conn).invoke("onSomeoneDisconnect", new
Object[]{myName}, this);
}
}
}
}

class MyJob implements IScheduledJob {

private Application app;


public MyJob(Application app){
this.app = app;
}

public void execute(ISchedulingService service) throws


CloneNotSupportedException {
// TODO Auto-generated method stub

//檢查斷線
this.app.checkDisconnection();
}
}在這個 Java 程式中,我存了一個 connNameList 陣列,用來存放每個連線,我自訂了
MyConn 類別來存放 連線物件 與 登入名稱,這個 MyConn 類別如下:
package idv.ben.red5;

import org.red5.server.api.IConnection;

public class MyConn {

public IConnection conn;


public String name;

public MyConn(IConnection conn, String name){


this.conn = conn;
this.name = name;
}
} 在上面的 Java 程式中,除了要注意每個 method 的名稱與參數外,因為那些是讓
Flash 可以呼叫的名稱,另外要注意的就是這句由 Server –> Flash 的寫法:
((IServiceCapableConnection) conn).invoke(” 函式名稱” , new Object[]{ 參數 1,
參數 2, …參數 n}, this);
另外一個大重點是,要如何在這個 Java 程式中,製作週期性的動作,可以觀察我在
appStart() 中的寫法:
IScheduledJob job = new MyJob(this);
jobId = this.addScheduledJob(5000, job);
自 訂一個實作 IScheduledJob 介面的 MyJob 類別,裡面要實作 execute() 方法,撰
寫實際要執行的工作內容即可。這部份也可以參考另外這個網站的範例:
http://www.joachim- bauch.de/tutorials/red5/MigrationGuide.txt
接下來,以下是 Flash 的程式部分:
var nc:NetConnection = new NetConnection();
nc.connect("rtmp://127.0.0.1/FirstRed5App");
nc.onStatus = function(info:Object){
for(var i in info){
trace(i + "=" + info[i]);
}
}
nc.onResult = function(obj) {
//trace("The result is "+obj);
};

//----------------------------------Server to Flash
nc.onNewMemberJoined = function(name:String) {
showMsg("歡迎" + name + "加入");

if(_root[name + "_mc"]==undefined){
var mc:MovieClip = _root.createEmptyMovieClip(name + "_mc",
_root.getNextHighestDepth());
mc.lineStyle(1, 0x000000, 100);
if(name==myName){
mc.beginFill(0xFF0000);
}else{
mc.beginFill(0xFFCC00);
}
mc.lineTo(5, 0);
mc.lineTo(5, 5);
mc.lineTo(0, 5);
mc.lineTo(0, 0);
mc.endFill();
}
};
nc.onSomeoneTalking = function(myName:String, msg:String) {
showMsg(myName + "說:" + msg);
};
nc.onSomeoneWalking = function(name:String, x:Number, y:Number){
if(_root[name + "_mc"]!=undefined){
_root[name + "_mc"].targetX = x;
_root[name + "_mc"].targetY = y;
_root[name + "_mc"].onEnterFrame = function(){
this._x += (this.targetX - this._x) * 0.2;
this._y += (this.targetY - this._y) * 0.2;

if(Math.abs(this.targetX - this._x)<0){
this._x = this.targetX;
}
if(Math.abs(this.targetY - this._y)<0){
this._y = this.targetY;
}
if(this._x == this.targetX && this._y == this.targetY){
delete this.onEnterFrame;
}
}
}
}
nc.onSomeoneDisconnect = function(name:String){
showMsg(name + "離開了");
if(_root[name + "_mc"]!=undefined){
_root[name + "_mc"].removeMovieClip();
delete _root[name + "_mc"];
}
}

//----------------------------------Flash to Server
function join(myName:String){
nc.call("join", nc, myName);
}
function talk(myName:String, msg:String){
nc.call("talk", nc, myName, msg);
}
function walk(myName:String, x:Number, y:Number){
nc.call("walk", nc, myName, x, y);
}

//----------------------------------Tools
_global.showMsg = function(msg:String){
//trace(msg);
msg_txt.text = msg + "\n" + msg_txt.text;
}

//----------------------------------
//加入聊天室
var myName:String = "user" + (new Date()).getTime();
join(myName);

//亂說話
function talkSomething(){
talk(myName, "msg" + new Date());
}
setInterval(talkSomething, 5000);

//到處亂走
function workSomewhere(){
walk(myName, Math.random()*Stage.width, Math.random()*Stage.height);
}
workSomewhere();
setInterval(workSomewhere, 10000); 這個聊天室,只要一進入,我就會自動給一個
登錄名稱 myName,之後所有的動作都要傳遞此一識別字串,然後會週期性的亂走與亂
說話。
可以看到,Flash –> Server 的部份,搭配 Java 的 Server 程式,不難理解吧?
nc.call(”join”, nc, myName);
nc.call(”talk”, nc, myName, msg);
nc.call(”walk”, nc, myName, x, y);
然後是 Server –> Flash 的部份,應該也不難理解?!
nc.onNewMemberJoined = function(name:String) {…}
nc.onSomeoneTalking = function(myName:String, msg:String) {…}
nc.onSomeoneWalking = function(name:String, x:Number, y:Number){…}
nc.onSomeoneDisconnect = function(name:String){…}
大 概就這樣,該提的重點應該都提到了!不過以上的程式,判斷連線中斷並通知所有
Flash 的部份,我單機開多個瀏覽器時會測不出來,不知道是否是因為每個瀏覽器都在相
同的電腦上,所以被視同為相同的 IConnection?所以會使得其中一個瀏覽器若是重新整
理或離開時,會造成所有瀏覽器上的 MovieClip 都被移除,被視為離開!當然也有可能
是我的 Java 程式寫得有誤,這就需要進一步的測試了,不過至少週期執行的寫法是可以
學的。

http://xinsync.xju.edu.cn/index.php/archives/608

11、 Red5 初探:聊天室 (修正斷線的


處理 )

上 次 遇 到 聊 天 室 踢 掉 斷 線 的 MC 的 部 份 , 寫 錯 了 !

我查了一下在 Server 端,連線 與 斷線 時的 IConnection 物件,以及 IClient 物件,


才稍微知道一個 IClient 可以有多個 IConnection 的事情,每次 Flash --> Server 時
都 是 不 同 的 IConnection , 所 以 我 不 能 用 IConnection 來 做 判 斷 。

以下測試畫面中,第一個紅色區塊是連線,第二個是斷線,兩個紅色區塊中的藍色區塊是
IConnection 物 件 , 不 一 樣 吧 !

http://bp2.blogger.com/_HsFEtLMw5zU/RsUQQ81o8KI/AAAAAAAAAik/CeJaROW
Ra1w/s320/Clipboard01.jpg
(http://bp2.blogger.com/_HsFEtLMw5zU/RsUQQ81o8KI/AAAAAAAAAik/CeJaRO
WRa1w/s1600-h/Clipboard01.jpg)

不過,IClient 就是相同的,而且是累加的,第一個瀏覽器是 0,第二個就是 1,若是第


二 個 瀏 覽 器 重 新 整 理 , IClient 1 就 會 斷 掉 , 新 增 加 一 個 IClient 2 。

http://bp3.blogger.com/_HsFEtLMw5zU/RsUQRM1o8LI/AAAAAAAAAis/gRgs0rb8
F9k/s320/Clipboard02.jpg
(http://bp3.blogger.com/_HsFEtLMw5zU/RsUQRM1o8LI/AAAAAAAAAis/gRgs0rb
8F9k/s1600-h/Clipboard02.jpg)

所 以 , 我 修 改 了 程 式 , 將 IConnection 改 以 IClient 處 理 。

package idv.ben.red5;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.red5.server.adapter.ApplicationAdapter;
import org.red5.server.api.IClient;
import org.red5.server.api.IConnection;
import org.red5.server.api.IScope;
import org.red5.server.api.Red5;
import org.red5.server.api.scheduling.IScheduledJob;
import org.red5.server.api.scheduling.ISchedulingService;
import org.red5.server.api.service.IPendingServiceCall;
import org.red5.server.api.service.IPendingServiceCallback;
import org.red5.server.api.service.IServiceCapableConnection;

public class Application extends ApplicationAdapter implements


IPendingServiceCallback {

protected static Log log = LogFactory.getLog(Application.class.getName());

// 存 一 份 連 線 物 件 與 登 入 名 稱 的 對 照 表
private List connNameList = new ArrayList();

private String jobId = null;

// 實 作 IPendingServiceCallback 介 面 所 要 實 作 的 method
public void resultReceived(IPendingServiceCall call) {
// TODO Auto-generated method stub
}

@Override
public boolean appStart(IScope app) {
// TODO Auto-generated method stub
if(!super.appStart(app)){
return false;
}

IScheduledJob job = new MyJob(this);


jobId = this.addScheduledJob(5000, job);

return true;
}

@Override
public void appStop(IScope app) {
// TODO Auto-generated method stub
this.removeScheduledJob(jobId);

super.appStop(app);
}

@Override
public boolean appConnect(IConnection conn, Object[] params) {
// TODO Auto-generated method stub
if(!super.appConnect(conn, params))return false;

log.info("[appConnect]" + conn + ", client=" + conn.getClient());

return true;
}

@Override
public void appDisconnect(IConnection conn) {
// TODO Auto-generated method stub
super.appDisconnect(conn);
log.info("[appDisconnect]" + conn + ", client=" + conn.getClient());
}

@Override
public boolean appJoin(IClient client, IScope app) {
// TODO Auto-generated method stub
if(!super.appJoin(client, app))return false;

log.info("[appJoin]" + client);

return true;
}

@Override
public void appLeave(IClient client, IScope app) {
// TODO Auto-generated method stub
super.appLeave(client, app);

log.info("[appLeave]" + client);
}

// 新 加 入
public void join(String myName){
//log.info("join(" + myName + ")");

IConnection current = Red5.getConnectionLocal();

// 加 到 名 單
connNameList.add(new MyClient(current.getClient(), myName));

Iterator it = scope.getConnections();
while (it.hasNext()) {
IConnection conn = it.next();

if (conn instanceof IServiceCapableConnection) {


// 通 知 所 有 人
((IServiceCapableConnection) conn).invoke("onNewMemberJoined", new
Object[]{myName}, this);
}
}
}

// 說 話
public void talk(String myName, String msg){
//log.info("talk(" + myName + ", " + msg + ")");

Iterator it = scope.getConnections();
while (it.hasNext()) {
IConnection conn = it.next();

if (conn instanceof IServiceCapableConnection) {


// 通 知 所 有 人
((IServiceCapableConnection) conn).invoke("onSomeoneTalking", new
Object[]{myName, msg}, this);
}
}
}

// 移 動
public void walk(String myName, double x, double y){
//log.info("walk(" + myName + ", " + x + ", " + y + ")");

Iterator it = scope.getConnections();
while (it.hasNext()) {
IConnection conn = it.next();

if (conn instanceof IServiceCapableConnection) {


// 通 知 所 有 人
((IServiceCapableConnection) conn).invoke("onSomeoneWalking", new
Object[]{myName, x, y}, this);
}
}
}

// 檢 查 斷 線
public void checkDisconnection(){
//log.info("checkDisconnection()");

//IConnection current = Red5.getConnectionLocal();

for(int i=connNameList.size()-1; i>= 0; i--){


MyClient myClient = (MyClient)connNameList.get(i);

boolean isDisconnect = true;

Iterator it = scope.getConnections();
while (it.hasNext()) {
IConnection conn = it.next();
if (conn.getClient().equals(myClient.client)) {
isDisconnect = false;
break;
}
}

if(isDisconnect){
// 通 知 所 有 人 有 人 斷 線
notifyDisconnect(myClient.name);

// 從 名 單 移 除
connNameList.remove(myClient);
}
}

// 通 知 所 有 人 有 人 斷 線
private void notifyDisconnect(String myName){
//log.info("notifyDisconnect(" + myName + ")");

Iterator it = scope.getConnections();
while (it.hasNext()) {
IConnection conn = it.next();

if (conn instanceof IServiceCapableConnection) {


// 通 知 所 有 人
((IServiceCapableConnection) conn).invoke("onSomeoneDisconnect", new
Object[]{myName}, this);
}
}
}
}

class MyJob implements IScheduledJob {

private Application app;

public MyJob(Application app){


this.app = app;
}

public void execute(ISchedulingService service) throws


CloneNotSupportedException {
// TODO Auto-generated method stub

// 檢 查 斷 線
this.app.checkDisconnection();
}
}

在我自訂的連線名單 connNameList 中,就改用 MyClient 存放 IClient 與自訂登入名


稱 :

package idv.ben.red5;

import org.red5.server.api.IClient;

public class MyClient {

public IClient client;


public String name;

public MyClient(IClient client, String name){


this.client = client;
this.name = name;
}
}

Flash 的 部 份 :

var nc:NetConnection = new NetConnection();


nc.connect("rtmp://127.0.0.1/FirstRed5App");
nc.onStatus = function(info:Object){
for(var i in info){
trace(i + "=" + info[i]);
}
}
nc.onResult = function(obj) {
//trace("The result is "+obj);
};

//----------------------------------Server to Flash
nc.onNewMemberJoined = function(name:String) {
showMsg(" 歡 迎 " + name + " 加 入 ");

if(_root[name + "_mc"]==undefined){
createMC(name);
}
};
nc.onSomeoneTalking = function(name:String, msg:String) {
showMsg(name + " 說 :" + msg);
};
nc.onSomeoneWalking = function(name:String, x:Number, y:Number){
if(_root[name + "_mc"]==undefined){
createMC(name);
}

_root[name + "_mc"].targetX = x;
_root[name + "_mc"].targetY = y;
_root[name + "_mc"].onEnterFrame = function(){
this._x += (this.targetX - this._x) * 0.2;
this._y += (this.targetY - this._y) * 0.2;

if(Math.abs(this.targetX - this._x)

12、 RED5 基本 概念
red5 里面,每个应用对应一个域(scope),所有的客户端(client)通过连
接(connection)连接到域当中(目前我还没有接触到复合域)。所以说,一个
域基本上就对应一个 java 主程序,所有的配置文件均指向此程序。对于单一域,
每个连接对应一个客户端,而每个客户端对应一个 id,简单的应用,操作就针
对这个 id 和连接进行。

ApplicationAdapter 是所有应用的基础,运行时候里面包含几个事件处理:
public boolean appStart(IScope app) 此应用开始的时候触发,app 为此域
public boolean appConnect(IConnection conn, Object[] params) 客户端
连接到域的时候触发,也就是 nc.connect 的时候触发,conn 为当前连接,后面
为参数
public void appDisconnect(IConnection conn) 客户端断开时触发,conn
为客户端
public boolean appJoin(IClient client, IScope app) 也是连接到应用时
触发,没搞太明白这个
使用 as3 连接服务器端的方法是 nc.call("方法名",响应器,变量),如果有返
回值则会传递到响应器的正确函数中,没有返回值依然会调用正确函数,只是
没有传参。服务器回调 as3 函数时,先判断连接是否正常,然后用 invoke("方
法名",参数)方法调用;as3 这边,nc 是首选接受回调方法的,但是 as3 种,
直接用 nc.callBackMethod=function(){}的方法 flash ide 会报错,因而通常
用 nc.client 属性来定义回调函数所在的位置。比如我用 data_model 类来组合
nc,那么就是 nc.client=this; public function callBackMethod(val){}便
没有问题。

啊,总结至此。下面开始 YY。个人认为 YY 是人生一大美事,哈哈。

I feel like I'm really close to getting this.. but missing something.

In flash:

myObj={one:"hello",two:"world"};
nc.connect("rtmp://my.red5.com/instance",myObj);

In red5:

public boolean appConnect(IConnection conn, Object[] params) {


Object test = params[0];
log.debug(test.one);

13、第 一个 rso 的例 子
red5 里面怎么用 sharedObject
1>flash 端控制,
2>/ 也可以用 red 的某个事件控制 , 来触发 so 的改变事件来通知所有的 client.
第 一 种 :
conn.connect("rtmp://localhost/Presstest"); // 建 立
conn
so=SharedObject.getRemote(”d5″,nc.uri,false);// 得到
so 的引
如果是第一人这一步在 red5 生成一个 videos 的共享对象 , 其余参数 , 可以看
cs3 的 api.
so.connect(nc);//so 连 接
so.addEventListener(SyncEvent.SYNC,soSyncHandle); 为
so 增 加 同 步 信 息 出 来 函 数 soSyncHandle
so.setProperty(”x”,"111111"); // 将共享对象里面的属性值
设置为 111111, 这个 方法会触发服务器去调用所有连接了这个 so 的 client,
并 调 用 该 客 户 端 方 法 soSyncHandle.
public function soSyncHandle(e:SyncEvent):void //同步处
理 函 数
{

var getValue:String = so.x;


trace(getValue); 这样就可以提取出 so 里面改变的值 ,实现
了 同 步 .
}

而服务器端共享对象是差不多的,同客户端的区别是,将改变 so 的值的步骤
so.setProperty 这一步在服务器通过事件触发来做.
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
layout="absolute" creationComplete="init()">
<mx:Script>
<![CDATA[
import mx.controls.Alert;

var c:NetConnection;
var myso:SharedObject;
private function init():void{
c=new NetConnection();
c.addEventListener(NetStatusEvent.NET_STATUS,sucess);
c.connect("rtmp://localhost/SOSample");
}

private function sucess(evt:NetStatusEvent):void{


if(evt.info.code=="NetConnection.Connect.Success")
{
}
}

private function txtchange():void{


myso.send("setVar",msg.text);
}

public function setVar(text:String):void{


msg.text=text;
}
]]>
</mx:Script>
<mx:VBox>
<mx:TextArea id="msg" />
<mx:Button label="sync" click="txtchange()"/>
</mx:VBox>
</mx:Application>

14、进 入房间
贴一段代码,那位高手给分析一下!
其中: if (!super.roomStart(room))
{
return false;
}
的作用是什么??
还有就是里面有个 public boolean roomStart(IScope room)
room.getName(),IScope 好像没有 getName()这个函数?老是到这就提示错误如下:
The type org.springframework.core.io.support.ResourcePatternResolver
cannot be
resolved. It is indirectly referenced from required .class files
这句的错误是什么意思???还忘高手指点!!!
俺的 QQ:43794298

import java.util.*;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.red5.server.api.stream.IServerStream;
import org.red5.server.api.stream.IStreamCapableConnection;
import org.red5.server.api.stream.support.SimpleConnectionBWConfig;
import org.red5.server.adapter.ApplicationAdapter;
import org.red5.server.api.IConnection;
import org.red5.server.api.IClient;
import org.red5.server.api.IScope;
import org.red5.server.api.Red5;
import org.red5.server.api.service.IServiceCapableConnection;
import org.red5.server.api.so.ISharedObject;
import org.red5.server.api.ScopeUtils;
import org.red5.server.api.so.ISharedObjectService;
import org.red5.server.api.*;
import org.red5.server.api.scheduling.*;
import org.red5.samples.components.ClientManager;
import org.red5.server.api.service.*;

public class Application extends ApplicationAdapter


{
// holding current rooms
private HashMap<String, Room> rooms = new HashMap<String,
Room>();
// holding current audiences in each room
private HashMap<String, ArrayList<Audience>> audiences = new
HashMap<String, ArrayList<Audience>>();

private String SCOPE_HALL = "hall";


private String ATT_CONNECTION_ME = "me";

public boolean roomStart(IScope room)


{
// System.out.println("room.getName==="+room.getName());
if (!super.roomStart(room))
{
return false;
}
if (room.getName().compareTo(SCOPE_HALL) != 0)
{
// create a new room
Room r = new Room();
r.Name = room.getName();
r.Count = 0;
rooms.put(r.Name, r);
audiences.put(r.Name, new ArrayList<Audience>());
// client call:a new room is created
ServiceUtils.invokeOnAllConnections(getChildScope(SCOPE_HA
LL),
"C_Hall_OnRoomCreated", new Object[] { r });
}
return true;
}

public void roomStop(IScope room)


{
super.roomStop(room);
if (room.getName().compareTo(SCOPE_HALL) != 0)
{
// client call:the room is destroyed
ServiceUtils.invokeOnAllConnections(getChildScope(SCOPE_HA
LL),
"C_Hall_OnRoomDestroyed", new Object[]
{ rooms.get(room
.getName()) });
// remove the room
rooms.remove(room.getName());
audiences.remove(room.getName());
}
}

public boolean roomConnect(IConnection conn, Object[] params)


{
if (!super.roomConnect(conn, params))
{
return false;
}
IScope scope = conn.getScope();
if (scope.getName().compareTo(SCOPE_HALL) == 0)
{
// hall scope
if (params != null && params.length == 1)
{
// first connect
// create a new audience
Audience audience = new Audience();
audience.Name = params[0].toString();
audience.IsTalking = false;
audience.IsShowing = false;
audience.HasCamera = false;
// set ME
conn.setAttribute(ATT_CONNECTION_ME, audience);
}
else
{
// connection from returning from a room
}
}
else
{
// room scope
}
return true;
}

public boolean roomJoin(IClient client, IScope room)


{
if (!super.roomJoin(client, room))
{
return false;
}
Audience me = (Audience) Red5.getConnectionLocal().getAttribute(
ATT_CONNECTION_ME);
if (room.getName().compareTo(SCOPE_HALL) == 0)
{
// client call:a new audience join the hall
ServiceUtils.invokeOnAllConnections(room, "C_Hall_OnJoin",
new Object[] { me });
}
else
{
Room theRoom = rooms.get(room.getName());
theRoom.Count++;
audiences.get(room.getName()).add(me);
// client call:the audience join a room
ServiceUtils.invokeOnAllConnections(room, "C_Room_OnJoin",
new Object[] { theRoom, me });
// client call:update room information
ServiceUtils.invokeOnAllConnections(getChildScope(SCOPE_HA
LL),
"C_Hall_OnRoomUpdated", new Object[]
{ theRoom });
}
return true;
}

public void roomLeave(IClient client, IScope room)


{
super.roomLeave(client, room);
Audience me = (Audience) Red5.getConnectionLocal().getAttribute(
ATT_CONNECTION_ME);
if (room.getName().compareTo(SCOPE_HALL) == 0)
{
// client call:the audience leave the hall
ServiceUtils.invokeOnAllConnections(room, "C_Hall_OnLeave",
new Object[] { me });
}
else
{
Room theRoom = rooms.get(room.getName());
theRoom.Count--;
audiences.get(room.getName()).remove(me);
// client call:the audience leave the hall
ServiceUtils.invokeOnAllConnections(room,
"C_Room_OnLeave",
new Object[] { theRoom, me });
// client call:update room information
ServiceUtils.invokeOnAllConnections(getChildScope(SCOPE_HA
LL),
"C_Hall_OnRoomUpdated", new Object[]
{ theRoom });
}
}

public boolean S_CreateAndJoinRoom(String roomName)


{
IScope scope = getChildScope(roomName);
if (scope == null)
{
if (!S_CreateRoom(roomName))
{
return false;
}
}
return S_JoinRoom(roomName);
}

public boolean S_CreateRoom(String roomName)


{
if (rooms.containsKey(roomName) || !createChildScope(roomName))
{
return false;
}
return true;
}

public boolean S_JoinRoom(String roomName)


{
if (!audiences.containsKey(roomName))
{
return false;
}
IScope roomScope = getChildScope(roomName);
IConnection conn = Red5.getConnectionLocal();
getChildScope(SCOPE_HALL).disconnect(conn); // leave hall
boolean ok = conn.connect(roomScope); // connect to the room
if (ok)
{
return true;
}
else
{
return false;
}
}

public boolean S_LeaveRoom(String roomName)


{
IScope roomScope = getChildScope(roomName);
IConnection conn = Red5.getConnectionLocal();
roomScope.disconnect(conn); // leave the room
conn.connect(getChildScope(SCOPE_HALL)); // connect to hall
return true;
}

public boolean S_SendPublicMessage(String msg)


{
IConnection conn = Red5.getConnectionLocal();
Audience me = (Audience)
conn.getAttribute(ATT_CONNECTION_ME);
String roomName = conn.getScope().getName();
ServiceUtils.invokeOnAllConnections(conn.getScope(),
"C_Room_ReceivePublicMessage", new Object[] {
rooms.get(roomName), me, msg });
return true;
}

public Collection<Room> S_GetRooms()


{
return rooms.values();
}

public Collection<Audience> S_GetAudiences(String roomName)


{
return audiences.get(roomName);
}

public class Audience


{
public String Name;
public boolean IsTalking;
public boolean IsShowing;
public boolean HasCamera;
}

public class Room


{
public String Name;
public int Count;
}
}

IScope roomScope = getChildScope(roomName);


IConnection conn = Red5.getConnectionLocal();
getChildScope(SCOPE_HALL).disconnect(conn); // leave hall
boolean ok = conn.connect(roomScope); // connect to the room

看来这这些 scope 和其是有直接关系的


StatefulScopeWrappingAdapter.getChildScope
StatefulScopeWrappingAdapter.createChildScope
但还不太理解这些东西!忘高手指点!

问题已基本被我给搞定啦,和我上面想的着不多,不过我要实现的东西不需要
getChildScope(SCOPE_HALL).disconnect(conn); // leave hall
boolean ok = conn.connect(roomScope); // connect to the room
这 个 功 能 是 退 出 大 厅 , 后 进 入 新 的 房 间 , 而 我 是 直 接 IScope roomScope =
getChildScope(roomName);不用经过大厅,呵,东西终于被给做出来啦,累死俺啦!

Scope 这个东西,在 red5 里面俺感觉真是个好东西,中文解释应该是:“区域,范围”


的意思,每个房间都有自己的 scope(就像房间号一样),而你如果要告诉在这个房间里
的每个人,则要用到:ServiceUtils.invokeOnAllConnections(conn.getScope(),……
这里才是关键,通过这句,你可以不管本房间其它以外的人,这个 conn.getScope()就是
你的地盘,你可以通过 ServiceUtils.invokeOnAllConnections(conn.getScope() 来管
能知调用在你地盘上人的事件或一些其它东东!

再次对 Scope 顶一下,感觉 Scope 不错,用着挺好滴!


15、Red5 API 之 IScope 接口 的理解
IScope 接口定义了 Red5 中作用域对象.该对象维护了一个由一组客户端连接组成
的上下文状态.通过作用域对象我们就可以很轻松的实现一个分

级访问,区域对象的共享的功能 .那么,对于一个作用域对象它可以有父作用域对象 ,
也可以有子作用域对象.如果一个客户端连接到了一个作用域

对象,同时也连接到了它的父作用域对象 .通过作用域对象就可以访问资源 ,共享对象,


视 音 频 流 等 . 作 用 域 对 象 在 应 用 程 序 中 定 义 了 一 些 组 选 项 .

下 面 是 作 用 域 所 有 的 名 称 :application,room,place,lobby.

下 面 简 单 介 绍 一 下 IScope 接 口 的 方 法 :
boolean addChildScope(IBasicScope scope)
描述:添加一个子作用域 .如果添加成功返回 True, 如果添加的子作用域已经是该作
用 域 的 子 作 用 域 , 那 么 返 回 False.
参 数 :scope 一 个 子 作 用 域 对 象 .
返回值:True 添加成功,False 添加失败,添加的子作用域已经是该作用域的子作用域 .

boolean connect(IConnection conn)


描 述 : 添 加 一 个 连 接 对 象 .
参 数 :conn 一 个 连 接 对 象 .
返 回 值 :True 表 示 成 功 , 如 果 该 连 接 对 象 已 经 属 于 该 作 用 域 对 象 则 返 回 False.

boolean connect(IConnection conn,Object[] params)


描 述 : 添 加 一 个 连 接 对 象 , 并 传 入 相 应 的 参 数 对 象 .
参 数 :conn 连 接 对 象 .
params: 参 数 对 象 .
返 回 值 :True 表 示 成 功 . 如 果 该 连 接 对 象 已 经 属 于 该 作 用 域 对 象 则 返 回 False.

boolean createChildScope(String name)


描述:通过一个字符串名称创建一个子作用域对象 .成功返回 True, 如果该子对象已
经 存 在 本 作 用 域 中 , 则 返 回 False.
参 数 :name 子 作 用 域 的 名 称 .
返回值 :True . ,
表示创建成功 如果该子对象已经存在本作用域中 则返回 False.

void disconnect(IConnection conn)


描述:从该作用域对象的连接对象列表中删除一个指定的连接对象 .这样就会把所有
提 供 该 连 接 对 象 的 客 户 端 和 本 作 用 域 断 开 连 接 .
参 数 : 提 供 的 连 接 对 象 ..

IBasicScope getBasicScope(String type,String name)


描 述 : 获 得 一 个 子 作 用 域 对 象 .
参 数 :type 子 作 用 域 的 类 型 .
name: 子 作 用 域 的 名 称 .
返 回 值 : 如 果 指 定 子 作 用 域 对 象 存 在 返 回 该 对 象 , 否 则 返 回 Null.

Iterator<String> getBasicScopeNames(String type)


描 述 : 获 得 所 有 指 定 类 型 的 子 作 用 域 .
参 数 : 类 型 名 称 .
返回值:返回一个范型的迭代器,通过该迭代器可以获得所有子作用域对象 .

Set<IClient> getClients()
描述 : 返回当前作用域对 象 中 所 有 子 作 用 域 对 象 的 范 型 集 合 .
返 回 值 : 所 有 子 作 用 域 对 象 的 范 型 集 合 .

Iterator<IConnection> getConnections()
描 述 : 获 得 本 作 用 域 所 有 连 接 对 象 的 范 型 迭 代 器 .
返 回 值 : 连 接 对 象 迭 代 器 .

IContext getContext()
描 述 : 返 回 本 作 用 域 上 下 文 环 境 .
返 回 值 : 返 回 本 作 用 域 上 文 对 象 .

String getContextPath()
描 述 : 返 回 上 下 文 路 径 .
返 回 值 : 上 下 文 路 径 .

IScopeHandler getHandler()
描 述 : 获 得 该 作 用 域 对 象 的 控 制 器 对 象 .
返 回 值 : 作 用 域 的 控 制 器 对 象 .

IScope getScope(String name)


描 述 : 通 过 名 称 获 得 作 用 域 对 象 .
参 数 :name 作 用 域 对 象 名 称 .
返 回 值 : 指 定 名 称 的 作 用 域 对 象 .
Iterator<String> getScopeNames()
描 述 : 获 得 所 有 子 作 用 域 的 名 称 迭 代 器 .
返 回 值 : 子 作 用 域 名 称 迭 代 器 .

boolean hasChildScope(String name)


描 述 : 判 断 当 前 作 用 域 是 否 有 指 定 名 称 的 子 作 用 域 .
参 数 :name 子 作 用 域 的 名 称 .
返 回 值 : 如 果 存 在 返 回 True, 反 之 返 回 False.

boolean hasChildScope(String type, String name)


描述 通过指定类型和名称判断当前作用域是否有指定名称的子作用域 .
:
参 数 :type 子 作 用 域 的 类 型 .
name 子 作 用 域 的 名 称 .
返 回 值 : 如 果 存 在 返 回 True, 反 之 返 回 False.

boolean hasHandler()
描 述 : 判 断 该 作 用 域 是 否 存 在 控 制 器 .
返 回 值 : 存 在 返 回 True, 反 之 返 回 False.

Set<IConnection> lookupConnections(IClient client)


描 述 : 通 过 客 户 端 对 象 , 查 找 连 接 对 象 .
返 回 值 : 返 回 只 读 的 连 接 对 象 集 合 的 迭 代 器 .

void removeChildScope(IBasicScope scope)


描 述 : 删 除 指 定 的 子 作 用 域 .
参数:子作用域对象.

16、 kikachat 中的 事件
在 appconnect….join,start 等 RED5 系统事件,都需要在 myconnct 中注册回
调函数,并且定义派发。

17、 send 或 call 在使 用上有 何差異 ?


call:這 method 在 Client 端上可使用的是 netconnection 物件,在 Server 端上
可 使 用 的 是
netconnection 與 Client 物 件
1. 在 Client 端利用 netconnection.call 來觸發執行 Server 端上 Client 物件的
method 。
2. 在 Server 端上利用 Client 物件 .call 來觸發 Client 端上 netconnection 上的
method 。
3. 在 Server 端上使用 netconnection.call 時,這時這 server 的角色就像一個
client 端 一 樣
, 是 在 觸 發 另 一 個 Server 端 上 Client 物 件 的 method 。
send: 在 Client 端 上 可 使 用 這 method 的 包 含 SharedObject 與 netStream
send 這 個 method 很 有 趣 , 他 讓 你 由 client 端 去 啟 動 所 有 同 在 client 端 的
function , 但 因 為
在觸發 function 時可以帶參數過去,這個特性是可以讓你利用來做小量資料的廣
播 的 , 要
廣播給所有人接收到的資料,並不一定就要放在 SharedObject 內,利用其
onSync 來 做 同 步
, 有 時 用 send 也 是 一 個 很 簡 單 的 做 法 , 如 何 定 義
1. 在 netStream 或 SharedObject 上 定 義 好 method "myfun"
2. 利用 netStream.send("myfun") 或 sharedobject.send("myfun" , myvar)
可 讓 所 有 client
上 的 "myfun" 都 會 被 觸 發

四 . 幾 種 可 能 的 互 動 型 態 範 例

1.Client 端 對 Server 端 傳 送 data 或 呼 叫 執 行 Server 端 function


應 用 範 例 : 一 個 簡 易 聊 天 室 , 聊 天 內 容 只 存 在 Server 端 的 變 數 內 , 不 使 用
SharedObject 存放呼叫 Server 端的 message 這 method 來處理 client 傳上去
的 msg 這 對 話 內 容
Client 端 :
nc.call("message" , null , msg);
Server 端 :
application.onAppstart=function(){
application.chat_content="";
}
application.onConnect=function(newClient){
.......
newClient.message=function(msg){
application.chat_content+=msg;
}
}
2.Client 端 對 所 有 Client 廣 播 data 並 執 行 指 定 Client 端 function
應用範例:以之前在站上回覆過的問題為例,一個 clinet 要輸入一個網址 url_txt,
要 讓 所 有 的
client 都 會 開 啟 這 網 址 的 網 頁
Client 端 :
先 定 義 一 個 附 掛 在 so 上 的 method
lobby_so.openPage=function(receive_url){
getURL(receive_url);
}
利 用 send 就 可 讓 所 有 client 接 收 到 這 網 址 並 開 啟
lobby_so.send("openPage" , url_txt);

3.Server 傳 送 data 給 特 定 Client


應用範例:當有使用者連線上 server,當使用者資料驗證正確時,接受其連線,一
方 面 要 client
去執行指定的 function 跳到某頁或讓某 mc 出現...,同時又要把 server 端的資料
帶 過 去
說明 :server 只回應正在與 server 做互動的那個 client ,如以上範例, server 只
會 去 觸 發 請 求
連線的該 client 去執行指定的 function,其他 client 不會有反應

三 .send 或 call 這 兩 個 method 在 使 用 上 有 何 差 異 ? 如 何 使 用

call:這 method 在 Client 端上可使用的是 netconnection 物件,在 Server 端上


可 使 用 的 是
netconnection 與 Client 物 件
1. 在 Client 端利用 netconnection.call 來觸發執行 Server 端上 Client 物件的
method 。
2. 在 Server 端上利用 Client 物件 .call 來觸發 Client 端上 netconnection 上的
method 。
3. 在 Server 端上使用 netconnection.call 時,這時這 server 的角色就像一個
client 端 一 樣
, 是 在 觸 發 另 一 個 Server 端 上 Client 物 件 的 method 。
send: 在 Client 端 上 可 使 用 這 method 的 包 含 SharedObject 與 netStream
send 這 個 method 很 有 趣 , 他 讓 你 由 client 端 去 啟 動 所 有 同 在 client 端 的
function , 但 因 為
在觸發 function 時可以帶參數過去,這個特性是可以讓你利用來做小量資料的廣
播 的 , 要
廣播給所有人接收到的資料,並不一定就要放在 SharedObject 內,利用其
onSync 來 做 同 步
, 有 時 用 send 也 是 一 個 很 簡 單 的 做 法 , 如 何 定 義
1. 在 netStream 或 SharedObject 上 定 義 好 method "myfun"
2. 利用 netStream.send("myfun") 或 sharedobject.send("myfun" , myvar)
可 讓 所 有 client
上 的 "myfun" 都 會 被 觸 發

四 . 幾 種 可 能 的 互 動 型 態 範 例

1.Client 端 對 Server 端 傳 送 data 或 呼 叫 執 行 Server 端 function


應 用 範 例 : 一 個 簡 易 聊 天 室 , 聊 天 內 容 只 存 在 Server 端 的 變 數 內 , 不 使 用
SharedObject 存 放 呼
叫 Server 端的 message 這 method 來處理 client 傳上去的 msg 這對話內容
Client 端 :
nc.call("message" , null , msg);
Server 端 :
application.onAppstart=function(){
application.chat_content="";
}
application.onConnect=function(newClient){
.......
newClient.message=function(msg){
application.chat_content+=msg;
}
}
2.Client 端 對 所 有 Client 廣 播 data 並 執 行 指 定 Client 端 function
應用範例:以之前在站上回覆過的問題為例,一個 clinet 要輸入一個網址 url_txt,
要 讓 所 有 的
client 都 會 開 啟 這 網 址 的 網 頁
Client 端 :
先 定 義 一 個 附 掛 在 so 上 的 method
lobby_so.openPage=function(receive_url){
getURL(receive_url);
}
利 用 send 就 可 讓 所 有 client 接 收 到 這 網 址 並 開 啟
lobby_so.send("openPage" , url_txt);

3.Server 傳 送 data 給 特 定 Client


應用範例:當有使用者連線上 server,當使用者資料驗證正確時,接受其連線,一
方 面 要 client
去執行指定的 function 跳到某頁或讓某 mc 出現...,同時又要把 server 端的資料
帶 過 去
說明 :server 只回應正在與 server 做互動的那個 client ,如以上範例, server 只
會 去 觸 發 請 求
連線的該 client 去執行指定的 function ,其他 client 不會有反應

Server 端 :
application.onConnect=function(newClient , pwd){
if(pwd=="ok"){
application.acceptConnection(newClient);
newClient.call("get_message" , null , message);
}else{
application.rejectConnection(newClient , errObj);
}
}
Client 端 :
........
nc.get_message=function(message){};

4.Server 廣 播 data 給 所 有 Client


應用範例:當有人斷線時,由 server 端廣播所有 client,讓所有 client 都能同步更
新 client 名 單
說 明 : 以 上 範 例 來 說 , 當 clinet 無 預 警 的 斷 線 , 只 有 Server 上 的
application.onDisconnect 這 handler
會被觸發,也就是說你需要在這 handler 內寫一些程式去廣播通知給所有的
client 。
如 何 廣 播 ? 有 以 下 兩 種 做 法
1.把資料放在 remote SharedObject 物件內,只要 SO 物件內容更動,即自動觸
發 Client 端 的 so.onSync
將線上人員名單寫在 remote SharedObject 物件內,當有人斷線,只要把 so 內
該 筆 資 料 剔 除 掉 , 因 為
so 內容改變,因此所有 Client 端的 so.onSync 這 handler 將被觸發,即可達到
你 要 更 新 資 料 的 目 的 。
Server 端
application.onDisconnect=function(newClient){
userlist_so.setProperty(newClient.name , "");
}
註 :相對的當 server 端無預警的斷線, client 端可由 nc.onStatus 這 handler 內
由 判 斷 info.code 來 取
得 資 訊
2. 當資料不是存在 so 內時,只是存在 server 端的一個變數上,可以善加利用
application.clients 來 對
所有 client 廣播。以下會觸發所有 client 端上的 client_fun ,並把 server 上的
sendvar 變 數 帶 過 去
server 端 :
application.onDisconnect=function(newClient){
for(var i=0;i<application.clients.length;i++) {
application.clients[i].call("client_fun" , null , sendvar);
}
}
Client 端 :
nc.client_fun=function(myvar){ }

增 加 一 個 廣 播 的 方 法
Server 端 傳 送 給 有 getRemote 同 一 個 ShareObject 的 Client 端

Server 端 :
application.abc_so = SharedObject.get("abc_so", false);
application.abc_so.send("msgFromSrvr", msg);
Client 端 :
abc_so = SharedObject.getRemote("abc_so", abc_nc.uri, false);
abc_so.msgFromSrvr = function(msg) {
showMsg(msg);
};

18、FMS 常常会 用到 3 个 Call 和 3 个


Send
关于 Flash Communication Server 程序的编写,常常会用到 3 个 Call 和 3 个
Send 语句。刚刚接触的朋友可能容易混淆,下面我就简单总结一下。
3 个 Call,客户端有 1 个,服务器端有 2 个。
客户端:
NetConnection.call
用法:
myConnection.call(remoteMethod, resultObject | null [, p1,...,pN])
这个方法是通过客户端调用服务器端的函数命令或者方法。

服务器端:
Client.call
用法
Client.call(methodName, [resultObj, [p1, ..., pN]])
在发送的客户端或另一个服务器上执行一个方法。这个方法可以任意的返回数据,
返回的数据作为结果传递到 resultObj 参数中去。

NetConnection.call
用法:
myNetConnection.call(methodName, [resultObj, p1, ..., pN])
调用一个 Flash Communication Server 或者其他应用服务器上的命令或方法。
用法和客户端的 NetConnection.call 的用法一样。他调用一个远程服务器上的
方法。
3 个 Send,客户端有 2 个,服务器端有 1 个。

客户端:
NetStream.send
用法:
myStream.send(handlerName [,p1, ...,pN])
对所有请求某个指定流数据的客户端机器广播一个消息。这个方法只能用在发布
这个流数据的客户端。为了处理和响应这个消息,需要建立一个句柄,格式是
myStream.HandlerName。

SharedObject.send
用法:
myRemoteSharedObject.send(handlerName [,p1, ...,pN])
一种方法,把一个消息广播到所有连接到 myRemoteSharedObject 上的客户端,
包括发送消息的客户机。为了处理并相应这个消息,建立一个名称为
handlerName 的函数绑定相应的 SharedObject 上。

服务器端:
SharedObject.send
用法:
SharedObject.send(methodName, [p1, ..., pN])
执行客户端上的一个方法。可以利用 SharedObject.send 异步的执行所有连接
到 SharedObject 上的客户机上的一个方法。不管成功、失败还是返回消息的响
应值,服务器都不会接受客户机的信息。

19、再说说 onSync,SharedObject

* 最多人不懂的就是:那个 li st 参数

看代码:

my_rso = SharedObject.getRemote("myRSO", NC.uri, true);


my_rso.onSync = function(list) {//.......};
my_rso.connect(NC); //连接

在 onSync 回调中我们可以知道,我们的 my_rso 被改变了,但 my_rso 里具体什


么改变了呢? 我们就要分析这个 list 参数 了

list 参数其实是一个对象数组 ,首先它是一个数组,里边装了很多对象


(Object),每一个对象都包括了 SharedObject 中一个插槽(slot)的改动信息。
我暂时给他起名叫插槽信息对象。 。。这名字太猥亵了。。但我就这么叫了。 。
插槽信息对象包含两个属性,name 和 code ,偶尔还会有个 oldValue?我不太
常用,不说它

name 描述被改变的属性名

code 描述该属性的改变方式 ,有可能为以下几种值:"success" , "change"


, "delete" , "reject" , "clear" ,具体含义后边说

说白了这个插槽信息对象大概就是这么个样子:

{name:"x",code:"success"}

表示 x 属性被修改成功

要得到这些插槽信息对象就要 for in 这个 list 参数

for (var i in list) {

list[i] 就是插槽信息对象

要分析具体 so 哪改变了,就是分析 list[i],比如

if(list[i].code=="change") trace("list[i].name"+被+"change 了")

if(list[i].code=="delete") trace("list[i].name"+被+"delete")

“change”是啥?“delete”是啥?

"success" , "change" , "delete" , "reject" , "clear" 具


体含义:

success : 表示当前影片修改 so 的插槽获得了成功

change : 表示 so 的插槽被别人修改,或填加

也就是说,你修改 so 的某个属性成功了会收到 "success" ,与此同时其他影片


会收到 "change"

reject : 拒绝修改
例如发生在两个或多个客户端同时要修改一个 so 的插槽,这时候 fms 会只让一
个 client 修改,并返回"success" 其他的会收到"reject"

delete , clear : 这个好理解,一个是删除,一个是清空,看例子:

比如服务器端删除某个 so

so = SharedObject.get("某个 so");
so.lock( );
var names = so.getPropertyNames( );
for (i in names) {
so.setProperty(names[i], null);
}
so.unlock( );

这样 client 端会收到 若干个插槽信息对象,所有的 code 都为"delete",表示


若干个 item 被删除

然而这样:

so = SharedObject.get("某个 so");
so.clear( );

client 端就只会收到一个插槽信息对象,code 属性为“clear”

完。

20、视频播 放流程
1. 建 立 NetConnection 对 象
var nc:NetConnection=new NetConnection()

2. 建 立 NC 连 接
nc.connect("rtmp://xxxxx");// 注 意 你 的 代 码 里 写 成 了 rtmp:/ , 少 了 一 个 /
如 果 是 直 接 通 过 HTTP 方 式 播 放 , 则 此 处 应 为
nc.connection(null);

3. 建 立 NetStream
var ns:NetStream=new NetStream(nc);
注 意 这 里 有 一 个 必 须 参 数 nc
4. 连 接 源
xxVideo.attachVideo(ns);

5. 播 放
ns.play("xxxx");

具体代码:
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
layout="absolute" applicationComplete="init()">
<mx:Script>
<![CDATA[
import mx.core.UIComponent;
public function init():void
{
var nc:NetConnection=new
NetConnection();
nc.connect(null);
var ns:NetStream=new NetStream(nc);

ns.addEventListener(AsyncErrorEvent.ASYNC_ERROR,
asyncErrorHandler);
var screen:UIComponent=new UIComponent();
screen.setActualSize(800,600);
var myVideo:Video=new Video(320,240);
myVideo.attachNetStream(ns);
screen.addChild(myVideo);
addChild(screen);
ns.play("http://xxx/video.flv");
}

public function asyncErrorHandler(e:Object):void


{

}
]]>
</mx:Script>
</mx:Application>
需要把 Video 加入一个 UIComponent 里才可以 addChild,在我机器上测试通过了:
特 别 要 声 明 , 这 里 需 要 为 ns 增 加 事 件 侦 听 器
ns.addEventListener(AsyncErrorEvent.ASYNC_ERROR, asyncErrorHandler);哪怕
只是个空函数也要增加。否则会报错(但不影响视频播放)

You might also like