远程共享对象(Remote Shared Objects) 可以用来跟踪、存储、共享以及做多客户端的数据同步操作。只要共享对象上的数据发生了改变,将会把最新数据同步到所有连接到该共享对象的应用程序客户端。FluorineFx所提供的远程共享对象(Remote Shared Objects)和FMS的共享对象的功能是一样,对于熟悉FMS开发的朋友来说,学习FluorineFx的远程共享对象是非常简单的。
共享对象可以在服务器端创建,也可以在客户端创建。在客户端创建共享对象的方法和使用FMS开发是一样的,创建一个NetConnection对象,通过该对象的connect()方法连接到服务器,然后通过SharedObject.getRemote()方法就可以在客户端创建一个远程共享对象。如下实例代码:
private
function connectionServer():
void
{
var nc:NetConnection
=
new
NetConnection();
nc.connect(
"
rtmp://localhost:1617/SOAPP
"
,
"
username
"
,
"
password
"
)
nc.addEventListener(NetStatusEvent.NET_STATUS,onStatusHandler);
nc.client
=
this
;
}
private
function onStatusHandler(
event
:NetStatusEvent):
void
{
if
(
event
.info.code
==
"
NetConnectin.Connect.Success
"
)
{
createSharedObject();
}
}
private
function createSharedObject():
void
{
var so:SharedObject
=
SharedObject.getRemote(
"
OnLineUsers
"
,nc.uri,
false
);
so.addEventListener(SyncEvent.SYNC,onSyncHandler);
so.connect(
this
.nc);
so.client
=
this
;
}
private
function onSyncHandler(
event
:SyncEvent):
void
{
//
..do other
}
在FluorineFx的服务器端创建远程共享对象和FMS有很大的区别,FluorineFx的ISharedObjectService接口提供了专门用于创建远程共享对象的方法CreateSharedObject(),ApplicationAdapter实现了此接口方法。定义如下:
public
bool
CreateSharedObject(IScope scope,
string
name,
bool
persistent)
{
ISharedObjectService service
=
(ISharedObjectService)ScopeUtils.GetScopeService(scope,
typeof
(ISharedObjectService));
return
service.CreateSharedObject(scope, name, persistent);
}
如果要在服务器端创建远程共享对象,直接调用ApplicationAdapter类中的CreateSharedObject()方法就可以。如下在FluorineFx服务器端创建远程共享对象的代码块:
ISharedObject users_so
=
GetSharedObject(connection.Scope,
"
OnLineUsers
"
);
if
(users_so
==
null
)
{
//
创建共享对象
CreateSharedObject(connection.Scope,
"
OnLineUsers
"
,
false
);
users_so
=
GetSharedObject(connection.Scope,
"
OnLineUsers
"
);
}
要想更新共享对象里的数据客户端还是使用setProperty()方法,而FluorineFx的服务器更新共享对象的方法则与FMS不一样,使用的是FluorineFx.Messaging.Api.IAttributeStore接口提供的SetAttribute()和RemoveAttribute()方法来更新共享对象里的数据。
陆续介绍了这么多,下面通过一个案例来看看该这么去应用远程共享对象。比如做IM、视频聊天、视频会议等及时通信类型的应用中,用户上线下线的频率非常高,这时候我们就可以使用远程共享对象去做在线用户的数据同步。
首先建立FluorineFx服务库,并建立一个应用类继承于ApplicationAdapter,通过重写ApplicationAdapter的相关方法来实现应用程序的不同需求,详细如下代码块:
using
System;
using
System.Collections.Generic;
using
System.Text;
using
FluorineFx.Messaging.Adapter;
using
FluorineFx;
using
FluorineFx.Messaging.Api;
using
System.Diagnostics;
using
FluorineFx.Messaging.Api.SO;
using
FluorineFx.Exceptions;
using
FluorineFx.Context;
using
FluorineFx.Messaging.Api.Service;
using
System.Collections;
using
Fx.Adapter.DTO;
namespace
Fx.Adapter
{
///
<summary>
///
自定义ApplicationAdapter
///
</summary>
[RemotingService]
public
class
MyApp : ApplicationAdapter
{
///
<summary>
///
应用程序启动
///
</summary>
///
<param name="application"></param>
///
<returns></returns>
public
override
bool
AppStart(IScope application)
{
Trace.WriteLine(
"
应用程序启动
"
);
return
true
;
}
///
<summary>
///
房间启动
///
</summary>
///
<param name="room"></param>
///
<returns></returns>
public
override
bool
RoomStart(IScope room)
{
Trace.WriteLine(
"
房间启动
"
);
if
(
!
base
.RoomStart(room))
return
false
;
return
true
;
}
///
<summary>
///
接收客户端的连接
///
</summary>
///
<param name="connection"></param>
///
<param name="parameters"></param>
///
<returns></returns>
public
override
bool
AppConnect(IConnection connection,
object
[] parameters)
{
string
userName
=
parameters[
0
]
as
string
;
string
password
=
parameters[
1
]
as
string
;
if
(password
==
null
||
password
==
string
.Empty)
throw
new
ClientRejectedException(
null
);
connection.Client.SetAttribute(
"
userName
"
, userName);
//
获取共享对象(OnLineUsers)
ISharedObject users_so
=
GetSharedObject(connection.Scope,
"
OnLineUsers
"
);
if
(users_so
==
null
)
{
//
创建共享对象
CreateSharedObject(connection.Scope,
"
OnLineUsers
"
,
false
);
users_so
=
GetSharedObject(connection.Scope,
"
OnLineUsers
"
);
}
//
更新共享对象
users_so.SetAttribute(userName, userName);
return
true
;
}
///
<summary>
///
加入房间
///
</summary>
///
<param name="client"></param>
///
<param name="room"></param>
///
<returns></returns>
public
override
bool
RoomJoin(IClient client, IScope room)
{
Trace.WriteLine(
"
加入房间
"
+
room.Name);
return
true
;
}
///
<summary>
///
离开房间
///
</summary>
///
<param name="client"></param>
///
<param name="room"></param>
public
override
void
RoomLeave(IClient client, IScope room)
{
Trace.WriteLine(
"
离开房间
"
+
room.Name);
base
.RoomLeave(client, room);
}
///
<summary>
///
用户退出
///
</summary>
///
<param name="connection"></param>
public
override
void
AppDisconnect(IConnection connection)
{
string
userName
=
connection.Client.GetAttribute(
"
userName
"
)
as
string
;
ISharedObject users_so
=
GetSharedObject(connection.Scope,
"
OnLineUsers
"
);
if
(users_so
!=
null
)
{
//
从共享对象中移除当前退出系统用户
users_so.RemoveAttribute(userName);
}
base
.AppDisconnect(connection);
}
}
}
开发好了ApplicationAdapter,还需要对此ApplicationAdapter进行通信配置,在FluorineFx的应用程序目录中添加app.config并进行如下配置:
<?
xml version="1.0" encoding="utf-8"
?>
<
configuration
>
<
application-handler
type
="Fx.Adapter.MyApp"
/>
</
configuration
>
另外还需要配置一个客户端方法的通信通道,通过FluorineFx网站下的WEB-INF/flex/service-config.xml配置:
<?xml version="1.0" encoding="utf-8" ?>
<services-config>
<channels>
<
channel-definition
id
="my-rtmp"
class
="mx.messaging.channels.RTMPChannel"
>
<
endpoint
uri
="rtmp://{server.name}:1617"
class
="flex.messaging.endpoints.RTMPEndpoint"
/>
</
channel-definition
>
</channels>
</services-config>
如上便完成了服务器端的开发,在flash/felx客户端通过NetConnection去连接应用,并根据当前的连接去连接服务器端的远程共享对象,最后通过异步事件来实现数据同步更新。如下程序运行截图:
此时开多个浏览器窗口测试,不同窗口使用不同的用户名登录,可以很清楚的看到,我们已经实现了在线用户的数据同步功能,可以及时的反映用户上线离线,可以及时的同步在线用户列表的数据。
另外远程共享对象还有一个功能非常强大的特性方法,就是连接到共享对象的客户端之间可以直接广播消息(客户端调用客户端的方法)。就以上面在线用户的案例为例,用户成功登陆服务器我需要广播一条消息,用户退出了我也需要广播一条消息,要实现这个功能就需要通过远程共享的客户端呼叫(send()方法)来实现,如下代码块:
private
function onCallClient(message:String):
void
{
so.send(
"
onSayMessage
"
,message);
}
远程共享对象的send()方法调用了onSayMessage这个客户端方法来实现对连接到共享对象上的所有客户端广播消息,那么我们的在定义一个onSayMessage方法,如下:
/*
*
* 接受客户端呼叫---此方法必须是public修饰
*/
public
function onSayMessage(message:Object):
void
{
traceWriteln(message.toString());
}
private function traceWriteln(param:String):void
{
txtTraceArea.htmlText += param + "\n";
txtTraceArea.validateNow();
txtTraceArea.verticalScrollPosition = txtTraceArea.maxVerticalScrollPosition;
}
如果想实现用户退出广播,可以通过服务器端RPC的方法调用客户端的方法来实现,关于RPC请查看《 Flex与.NET互操作(十一):基于FluorineFx.Net的及时通信应用(Remote Procedure Call)(二) 》有详细介绍。下面是Flex客户端的完整代码:
<?
xml version
=
"
1.0
"
encoding
=
"
utf-8
"
?>
<
mx:Application xmlns:mx
=
"
http://www.adobe.com/2006/mxml
"
layout
=
"
absolute
"
width
=
"
530
"
height
=
"
378
"
backgroundGradientAlphas
=
"
[1.0, 1.0]
"
backgroundGradientColors
=
"
[#000000, #686868]
"
fontSize
=
"
12
"
>
<
mx:Script
>
<!
[CDATA[
import mx.controls.Alert;
import dotnet.fluorinefx.VO.UserInfo;
private
var nc:NetConnection;
private
var so:SharedObject;
private
var info:UserInfo;
private
function connectionServer(
event
:MouseEvent):
void
{
info
=
new
UserInfo();
info.UserName
=
this
.txtUserName.text;
info.Password
=
this
.txtPassword.text;
nc
=
new
NetConnection();
nc.connect(
"
rtmp://localhost:1617/SOAPP
"
,info.UserName,info.Password);
nc.addEventListener(NetStatusEvent.NET_STATUS,onStatusHandler);
nc.client
=
this
;
this
.txtUserName.text
=
""
;
this
.txtPassword.text
=
""
;
this
.txtUserName.setFocus();
}
private
function onStatusHandler(
event
:NetStatusEvent):
void
{
this
.connStatus.text
=
"
连接状态:
"
+
event
.info.code;
if
(
event
.info.code
==
"
NetConnection.Connect.Success
"
)
{
//
连接远程共享对象
so
=
SharedObject.getRemote(
"
OnLineUsers
"
,nc.uri,
false
);
if
(so)
{
so.addEventListener(SyncEvent.SYNC,onSyncHandler);
so.connect(nc);
so.client
=
this
;
}
onCallClient(
"
用户【 <font color=\
"
#4100b9\
"
>
"
+
info.UserName
+
"
</font>】登陆了系统!
"
);
}
}
private
function onSyncHandler(
event
:SyncEvent):
void
{
var temp:Array
=
new
Array();
for
(var u:String
in
so.data)
{
//
traceWriteln("异步事件->共享对象:" + u + ":" + so.data[u]);
temp.push(so.data[u]);
}
this
.userList.dataProvider
=
temp;
}
private
function traceWriteln(param:String):
void
{
txtTraceArea.htmlText
+=
param
+
"
\n
"
;
txtTraceArea.validateNow();
txtTraceArea.verticalScrollPosition
=
txtTraceArea.maxVerticalScrollPosition;
}
private
function onCallClient(message:String):
void
{
so.send(
"
onSayMessage
"
,message);
}
/*
*
* 接受客户端呼叫
*/
public
function onSayMessage(message:Object):
void
{
traceWriteln(message.toString());
}
]]
>
</
mx:Script
>
<
mx:Label x
=
"
24
"
y
=
"
134
"
id
=
"
connStatus
"
width
=
"
288
"
color
=
"
#FFFFFF
"
/>
<
mx:List x
=
"
342
"
y
=
"
10
"
height
=
"
347
"
width
=
"
160
"
id
=
"
userList
"
>
</
mx:List
>
<
mx:Form x
=
"
24
"
y
=
"
10
"
width
=
"
236
"
>
<
mx:FormItem label
=
"
用户名:
"
color
=
"
#FFFFFF
"
>
<
mx:TextInput id
=
"
txtUserName
"
width
=
"
130
"
color
=
"
#000000
"
/>
</
mx:FormItem
>
<
mx:FormItem label
=
"
密 码:
"
color
=
"
#FFFFFF
"
>
<
mx:TextInput id
=
"
txtPassword
"
width
=
"
130
"
color
=
"
#000000
"
displayAsPassword
=
"
true
"
/>
</
mx:FormItem
>
<
mx:FormItem label
=
""
>
<
mx:Button label
=
"
登陆服务器
"
click
=
"
connectionServer(event)
"
enabled
=
"
{this.txtUserName.text.length>0?true:false}
"
color
=
"
#FFFFFF
"
/>
</
mx:FormItem
>
</
mx:Form
>
<
mx:TextArea x
=
"
24
"
y
=
"
174
"
width
=
"
288
"
height
=
"
153
"
alpha
=
"
1.0
"
backgroundColor
=
"
#F2D2D2
"
backgroundAlpha
=
"
0.26
"
color
=
"
#FFFFFF
"
id
=
"
txtTraceArea
"
borderColor
=
"
#FFFFFF
"
/>
</
mx:Application
>