上一篇教程是基于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(); }