上一篇教程是基于Mysql数据库来进行开发的,缺点是用户异常退出时不能即时在数据库中更新,会出现问题(嘿嘿 实现条件简单嘛)。本项目中使用FMS的远程共享对象进行用户状态信息的存取。FMS服务器可以实时监控用户连接状态,只要有一个用户更新了远程共享对象,就会触发所有用户端的同步事件,从而将远程共享对象中的数据同步到用户端。
准备工作和上一篇一样,多了个FMS服务端。在使用FMS时需要注意,只有使用Flash Media Interactive Server或Flash Media Development Server这两个版本时才能够创建和使用远程共享对象,来实现多客户端的应用程序之间共享数据。如果是使用的Flash Media Streaming Server版FMS是不能创建远程共享对象的,只能创建本地共享对象,类似于传统Web开发中的Cookie。免费版的是Flash Media Development Server,貌似有连接数的限制,不过学习测试用够了,在这下载
下面正式开始,let's go!
首先关于P2P模块上篇教程中已经介绍的很详细了,这里pass,直接拿来用。
下面讲解远程共享对象的使用,引用下官方的pp,用户端与FMS服务端的关系如下图:

把FMS装好后,在程序目录下的applications文件夹下新建你的应用程序目录(用户端的flashplayer就连接到这个应用程序目录),我的是P2PFMS.

在应用程序目录P2PFMS下新建一个main.asc文件,服务端的程序就在这个asc文件里了。程序如下:
application.allowDebug=true;
/*当服务器第一次载入应用程序实例时调用。你可以用这个事件来初始化应用程序状态
*在这我们创建远程共享对象RemoteSo,服务器为get,客户端为getRemote,共享对象为非永久性
*/
application.onAppStart = function()
{
so = SharedObject.get("RemoteSo","false");
this.accept = 1;
}
/*当NetConnection.connect在客户端被调用并且客户尝试去连接一个应用程序时该事件将在
*服务端调用,这里定义了一个用户名判断的函数,如果已经存在用户名则拒绝连接
*/
application.onConnect = function(client,user)
{
for(i=0;i<application.clients.length;i++)
{
if(application.clients[i].username == user.name)
{
this.accept = 0;
}
else this.accept = 1;
}
if(this.accept == 1)
{
client.username = user.name;
client.ipAddress = function()
{
return client.ip;
}
so.send("loginon", client.username);
so.setProperty(client.username, user);
return true;
}
else
{
return false;
}
}
/*当用户端断开与应用程序的连接时该事件将在服务端调用,清除用户数据
*/
application.onDisconnect = function(client)
{
if(this.accept == 1)
{
so.send("loginout",client.username);
so.setProperty(client.username, null);
}
}
这样服务端就搞定了,接下来客户端用Flex开发。首先我们要连接上Adobe的Stratus服务器,以便进行p2p连接。
private var stratusConnect:NetConnection; stratusConnect=new NetConnection(); stratusConnect.addEventListener(NetStatusEvent.NET_STATUS,loginHandler); stratusConnect.connect(SERVER+DEVKEY);
连接成功后再连接上FMS服务器,下面Demo程序是连得我自己电脑上的FMS服务器(白天一般开着,晚上可能会关,连接失败就说明没开哈),没自己的服务器啊,杯具:
private var fmsConnect:NetConnection;
private var user:Object;
fmsConnect = new NetConnection();
fmsConnect.addEventListener(NetStatusEvent.NET_STATUS,connectHandler);
//在调用NetConnection.connect时传递了一个user的Object对象到共享对象
fmsConnect.connect("rtmp://你的ip,本机就loclhost/P2PFMS",user);
连接上FMS服务器后,通过fmsConnect连接到上面服务端创建的RemoteSo远程共享对象。客户端为getRemoto方法,添加同步事件侦听,任何一个连接到远程共享对象RemoteSo的客户端或服务端更新了对象中的值,就会触发所有客户端的SyncEvent事件,从而同步更新。
private var so:SharedObject;
so = SharedObject.getRemote("RemoteSo",fmsConnect.uri,false);
so.addEventListener(SyncEvent.SYNC, soOnSync);
so.connect(fmsConnect);
在SyncEvent事件处理函数soOnSync中处理显示数据和用户组。这里创建了一个数组来存放远程共享对象中的用户组数据。
private function soOnSync(event:SyncEvent):void
{
i++;
idleArray=[];
userNum=0;
for each (var m:Object in so.data)
{
userNum++;
if(m.state == 0)
{
var item:Object = new Object();
item = m;
idleArray.push(item);
}
}
trace("更新>>>>>>>>>>第"+i+"次");
idleNum = idleArray.length;
}
随机配对部分,通过查找上面创建的数组数据来获取目标用户的信息:name和peerID
protected function random_btn_clickHandler(event:MouseEvent):void
{
message_txt.text += "正在查询空闲用户\n";
if(callState != CallReady)
{
message_txt.text += "请先停止聊天\n";
}
else
{
var i:int = int(Math.random()*idleArray.length);
trace(idleArray[i].name);
if(idleArray[i].name == username && idleArray.length == 1)
{
message_txt.text += "无空闲用户\n";
}
else if(idleArray[i].name == username)
{
do
{
i = int(Math.random()*idleArray.length);
}
while(idleArray[i].name == username);
message_txt.text += "查找成功\n";
partnername = idleArray[i].name;
farPeerID = idleArray[i].peerID;
call();
trace(partnername);
}
else
{
message_txt.text += "查找成功\n";
partnername = idleArray[i].name;
farPeerID = idleArray[i].peerID;
call();
trace(partnername);
}
}
}
呵呵,我也加上了显IP,用FMS那个client.ip就可以直接输出了。
单机截图,弄了个美女-_-:

Demo程序在这:http://flexdemo.online.cm/P2PFMS.swf
sigh,每天上线就得重换ip,没固定ip杯具。呵呵,欢迎跟帖交流哈!转载请注明出处 by疯狂的八神庵
FLEX完整代码在下面:
private var so:SharedObject;
private var stratusConnect:NetConnection;
private var fmsConnect:NetConnection;
private var user:Object;
private var rs:Responder;
[Bindable] private var username:String;
[Bindable] private var partnername:String;
[Bindable] private var userNum:int;
[Bindable] private var idleNum:int;
[Bindable] private var remoteIPAdress:String;
[Bindable] private var loginState:int;
[Bindable] private var playState:int;
private var farPeerID:String;
private var idleArray:Array;
private var ipAddress:String;
private var mic:Microphone;
private var camera:Camera;
private var video:Video;
private var callState:int;
private var i:int;
private const CallNotReady:int = 0;
private const CallReady:int = 1;
private const CallCalling:int = 2;
private const CallRinging:int = 3;
private const CallEstablished:int = 4;
private const CallFailed:int = 5;
private var listenStream:NetStream;
private var controlStream:NetStream;
private var outgoingStream:NetStream;
private var incomingStream:NetStream;
private const SERVER:String="rtmfp://stratus.adobe.com/";
private const DEVKEY:String="8b0f114ef5a20c433d5c2a33-201aeea5601b";
private function init():void
{
i=0;
userNum = 0;
idleNum = 0;
loginState = 0;
playState =0;
}
protected function login_btn_clickHandler(event:MouseEvent):void
{
if(user_txt.text == "")
{
message_txt.text += "大哥你总得有个名字吧-_-\n";
}
else
{
stratusConnect=new NetConnection();
stratusConnect.addEventListener(NetStatusEvent.NET_STATUS,loginHandler);
stratusConnect.connect(SERVER+DEVKEY);
}
}
private function loginHandler(event:NetStatusEvent):void
{
trace("stratus:"+event.info.code);
if(event.info.code == "NetConnection.Connect.Success")
{
loginState = 1;
info_txt.text+="与stratus连接成功\n";
connect();
}
else if(event.info.code == "NetConnection.Connect.Closed")
{
info_txt.text+="与stratus断开连接\n";
}
}
protected function connect():void
{
user =new Object();
user.name =user_txt.text;
user.peerID = stratusConnect.nearID;
user.state = 0;
rs = new Responder(onSuccess,onFailed);
fmsConnect = new NetConnection();
fmsConnect.addEventListener(NetStatusEvent.NET_STATUS,connectHandler);
fmsConnect.connect("rtmp://122.94.79.247/P2PFMS",user);
idleArray = new Array();
}
private function onSuccess(rs:Object):void
{
ipAddress = rs.toString();
message_txt.text += username+" 你的ip是: "+ipAddress+"\n你现在有权保持沉默,你的言行将被记录-_-\n";
}
private function onFailed(rs:Object):void
{
ipAddress = "diqiu";
}
private function connectHandler(event:NetStatusEvent):void
{
trace("fms:"+event.info.code);
if(event.info.code == "NetConnection.Connect.Success")
{
username = user_txt.text;
info_txt.text+="与疯狂的八神庵的FMS服务器连接成功\n";
info_txt.text += username+"上线了\n";
fmsConnect.call("ipAddress",rs);
innitApp();
initStream();
so = SharedObject.getRemote("RemoteSo",fmsConnect.uri,false);
so.addEventListener(SyncEvent.SYNC, soOnSync);
so.connect(fmsConnect);
var i:Object = new Object();
i.loginon = function(str:String)
{
info_txt.text += str+"上线了\n";
}
i.loginout = function(str:String)
{
info_txt.text += str+"下线了\n";
if(partnername == str && partnername != null)
{
message_txt.text += str+"和你断开连接\n";
send_btn.enabled=false;
stop();
}
}
so.client = i;
}
else if(event.info.code == "NetConnection.Connect.Closed")
{
info_txt.text+="与疯狂的八神庵的FMS服务器断开连接\n";
}
else if(event.info.code == "NetConnection.Connect.Rejected")
{
message_txt.text += "名字已被占用 ,请换个马甲登陆\n";
}
}
private function innitApp():void
{
var mics:Array=Microphone.names;
if(mics)
{
//micNames=mics;
}
else
{
message_txt.text+="没有检测到麦克风\n";
}
var cams:Array=Camera.names;
if(cams)
{
//cameraNames=cams;
}
else
{
message_txt.text+="没有检测到摄像头\n";
}
//micSelection.selectedIndex=micIndex;
//cameraSelection.selectedIndex=cameraIndex;
mic=Microphone.getMicrophone();
camera=Camera.getCamera();
var speakerVolume:Number = 0.8;
//speakerVolumeSlider.value = speakerVolume;
var micVolume:int = 20;
//micVolumeSlider.value = micVolume;
if (mic)
{
mic.codec=SoundCodec.SPEEX;
mic.encodeQuality=3;
mic.setSilenceLevel(10);
mic.setUseEchoSuppression(true);
mic.gain = micVolume;
mic.addEventListener(StatusEvent.STATUS, onDeviceStatus);
mic.addEventListener(ActivityEvent.ACTIVITY, onStatus);
}
else
{
message_txt.text+="启动本地麦克风失败\n";
}
if (camera)
{
camera.addEventListener(StatusEvent.STATUS, onDeviceStatus);
camera.addEventListener(ActivityEvent.ACTIVITY, onStatus);
camera.setMode(320, 240, 10);
camera.setQuality(360000 / 8, 0);
camera.setKeyFrameInterval(15);
local_videodisplay.attachCamera(camera);
}
else
{
message_txt.text+="启动本地摄像头失败\n";
}
}
private function onDeviceStatus(e:StatusEvent):void
{
}
private function onStatus(e:ActivityEvent):void
{
}
private function initStream():void
{
listenStream = new NetStream(stratusConnect,NetStream.DIRECT_CONNECTIONS);
listenStream.addEventListener(NetStatusEvent.NET_STATUS,loginHandler);
listenStream.publish(username);
var c:Object =new Object();
c.onPeerConnect = function(caller:NetStream):Boolean
{
if(callState == CallReady)
{
callState = CallRinging;
user.state = 1;
so.setProperty(username,user);
so.setDirty(username);
incomingStream = new NetStream(stratusConnect,caller.farID);
incomingStream.addEventListener(NetStatusEvent.NET_STATUS,loginHandler);
video =new Video();
video.attachNetStream(incomingStream);
remote_videodisplay.addChild(video);
incomingStream.play("caller");
//var st:SoundTransform = new SoundTransform(speakerVolumeSlider.value);
//incomingStream.soundTransform = st;
var i:Object = new Object();
i.onIncomingCall = function(caller:String,ip:String):void
{
if(callState != CallRinging)
{
info_txt.text += "onIncomingCall: Wrong call state: " + callState + "\n";
return;
}
send_btn.enabled=true;
remoteIPAdress = ip;
message_txt.text += caller + "已经成功与您连接上\n";
partnername = caller;
callState = CallEstablished;
playState =1;
}
i.onIm = function(caller:String,text:String):void
{
message_txt.text += caller+": "+text+"\n";
}
i.onDisconnected = function(caller:String):void
{
message_txt.text += caller+"和你断开连接\n";
send_btn.enabled=false;
stop();
}
incomingStream.client = i;
outgoingStream = new NetStream(stratusConnect,NetStream.DIRECT_CONNECTIONS);
outgoingStream.addEventListener(NetStatusEvent.NET_STATUS,callee_outgoingStreamHandler);
outgoingStream.attachCamera(camera);
outgoingStream.attachAudio(mic);
outgoingStream.publish("callee");
return true;
}
message_txt.text += "onPeerConnect: all rejected due to state: " + callState + "\n";
return false;
}
listenStream.client = c;
callState = CallReady;
}
private function call():void
{
callState = CallCalling;
user.state = 1;
so.setProperty(username,user);
so.setDirty(username);
controlStream = new NetStream(stratusConnect,farPeerID);
controlStream.addEventListener(NetStatusEvent.NET_STATUS,loginHandler);
controlStream.play(partnername);
outgoingStream = new NetStream(stratusConnect,NetStream.DIRECT_CONNECTIONS);
outgoingStream.addEventListener(NetStatusEvent.NET_STATUS,caller_outgoingStreamHandler);
outgoingStream.attachCamera(camera);
outgoingStream.attachAudio(mic);
outgoingStream.publish("caller");
//outgoingStream.send("onIncomingCall",username);
message_txt.text += "正在与"+partnername+"建立连接\n";
//var o:Object = new Object();
//o.onPeerConnect = function(callee:NetStream):Boolean
//{
incomingStream = new NetStream(stratusConnect,farPeerID);
incomingStream.addEventListener(NetStatusEvent.NET_STATUS,loginHandler);
video =new Video();
video.attachNetStream(incomingStream);
remote_videodisplay.addChild(video);
incomingStream.play("callee");
//var st:SoundTransform = new SoundTransform(speakerVolumeSlider.value);
//incomingStream.soundTransform = st;
var i:Object = new Object();
i.onIm = function(callee:String,text:String):void
{
message_txt.text += callee+": "+text+"\n";
}
i.onCallAccepted = function(callee:String,ip:String):void
{
if (callState != CallCalling)
{
message_txt.text += "连接失败";
return;
}
send_btn.enabled=true;
remoteIPAdress = ip;
message_txt.text += callee+"已经成功与您连接上\n";
callState = CallEstablished;
playState =1;
//idManager.change(callState);
}
i.onDisconnected = function(callee:String):void
{
message_txt.text += callee+"和你断开连接\n";
send_btn.enabled=false;
stop();
}
incomingStream.client = i;
// return true;
// }
// outgoingStream.client = o;
}
private function caller_outgoingStreamHandler(e:NetStatusEvent):void
{
if (e.info.code == "NetStream.Play.Start" )
{
outgoingStream.send("onIncomingCall",username,ipAddress);
}
}
private function callee_outgoingStreamHandler(e:NetStatusEvent):void
{
if (e.info.code == "NetStream.Play.Start" )
{
outgoingStream.send("onCallAccepted",username,ipAddress);
}
}
private function stop():void
{
if(video)
{
remote_videodisplay.removeChild(video);
video = null;
}
if (incomingStream)
{
trace("incomingStream");
incomingStream.close();
incomingStream.removeEventListener(NetStatusEvent.NET_STATUS, loginHandler);
}
if (outgoingStream)
{
trace("outgoingStream");
outgoingStream.close();
outgoingStream.removeEventListener(NetStatusEvent.NET_STATUS, loginHandler);
}
if (controlStream)
{
trace("controlStream");
controlStream.close();
controlStream.removeEventListener(NetStatusEvent.NET_STATUS, loginHandler);
}
partnername = null;
remoteIPAdress = null;
callState = CallReady;
user.state = 0;
so.setProperty(username,user);
so.setDirty(username);
playState = 0;
}
private function soOnSync(event:SyncEvent):void
{
i++;
idleArray=[];
userNum=0;
for each (var m:Object in so.data)
{
userNum++;
if(m.state == 0)
{
var item:Object = new Object();
item = m;
idleArray.push(item);
}
}
trace("更新>>>>>>>>>>第"+i+"次");
idleNum = idleArray.length;
}
protected function random_btn_clickHandler(event:MouseEvent):void
{
message_txt.text += "正在查询空闲用户\n";
if(callState != CallReady)
{
message_txt.text += "请先停止聊天\n";
}
else
{
var i:int = int(Math.random()*idleArray.length);
trace(idleArray[i].name);
if(idleArray[i].name == username && idleArray.length == 1)
{
message_txt.text += "无空闲用户\n";
}
else if(idleArray[i].name == username)
{
do
{
i = int(Math.random()*idleArray.length);
}
while(idleArray[i].name == username);
message_txt.text += "查找成功\n";
partnername = idleArray[i].name;
farPeerID = idleArray[i].peerID;
call();
trace(partnername);
}
else
{
message_txt.text += "查找成功\n";
partnername = idleArray[i].name;
farPeerID = idleArray[i].peerID;
call();
trace(partnername);
}
}
}
protected function clear_btn_clickHandler(event:MouseEvent):void
{
message_txt.text="";
}
protected function send_btn_clickHandler(event:MouseEvent):void
{
if(send_txt.text != "")
{
message_txt.text += username + ":" + send_txt.text + "\n";
outgoingStream.send("onIm",username,send_txt.text);
send_txt.text = " ";
}
else
message_txt.text += "-_-请输入内容\n";
}
protected function stop_btn_clickHandler(event:MouseEvent):void
{
if (callState == CallEstablished && outgoingStream)
{
outgoingStream.send("onDisconnected",username);
message_txt.text += "和"+partnername+"断开连接\n";
stop();
}
}
protected function disconnect_btn_clickHandler(event:MouseEvent):void
{
if (callState == CallEstablished && outgoingStream)
{
outgoingStream.send("onDisconnected",username);
message_txt.text += "和"+partnername+"断开连接\n";
stop();
}
if(fmsConnect)
{
fmsConnect.close();
}
if(stratusConnect)
{
stratusConnect.close();
}
init();
}
浙公网安备 33010602011771号