在fms4以前Adobe只允许在stratus中才能使用p2p功能。令人高兴的是,在最新发布的fms4中,p2p功能已经集成进来了,这将给实时视频类的应用带来更高的效率,adobe这次很给力!
为了使用p2p,开发用的flex sdk至少要4.1以上(当然最高版本是代号为hero的4.5版本,可从adobe的官网下载),另外还需要fms4(同样可从adobe官网下载开发版本)。
先上完整代码吧:
package { import fl.controls.Button; import fl.controls.Label; import fl.controls.TextArea; import flash.display.Sprite; import flash.events.MouseEvent; import flash.events.NetStatusEvent; import flash.net.GroupSpecifier; import flash.net.NetConnection; import flash.net.NetGroup; import flash.net.NetGroupReplicationStrategy; import flash.text.TextFormat; public class p2p_HelloWorld extends Sprite { private var _lbl:Label; private var _btnAddToWant:Button; private var _btnGenData:Button; private var _btnAddToHave:Button; private var _txtObj:TextArea; private var _txtOutput:TextArea; private var _data:Vector.<String>; private var _dataLength:uint = 100; private var _nc:NetConnection; private var _ng:NetGroup; private var _spec:GroupSpecifier; private var _server:String = "rtmfp://localhost/HelloServer"; private var _groupName:String = "myGroup"; private var _connected:Boolean = false; public function p2p_HelloWorld(){ init(); } private function init():void { this._btnAddToWant = btnAddToWant; this._btnAddToHave = btnAddToHave; this._btnGenData = btnGenData; this._txtObj = txtObj; this._txtOutput = txtOutput; this._lbl = lbl; var style:TextFormat = new TextFormat("宋体", 12, 0x000000,false,false,false,null,null,null,null,null,null,5); this._btnAddToHave.setStyle("textFormat", style); this._btnAddToWant.setStyle("textFormat", style); this._btnGenData.setStyle("textFormat", style); this._txtObj.setStyle("textFormat", style); this._txtOutput.setStyle("textFormat", style); this._lbl.setStyle("textFormat", style); this._btnGenData.addEventListener(MouseEvent.CLICK, _btnGenData_Click); this._btnAddToHave.addEventListener(MouseEvent.CLICK, _btnAddToHave_Click); this._btnAddToWant.addEventListener(MouseEvent.CLICK, _btnAddToWant_Click); //先连接到服务器 _nc = new NetConnection(); _nc.addEventListener(NetStatusEvent.NET_STATUS, _nc_Net_Status); _nc.connect(_server); output("正在连接 " + _server + " ..."); } private function _nc_Net_Status(e:NetStatusEvent):void { output(e.info.code); switch (e.info.code){ case "NetConnection.Connect.Success": //连接成功后,要设置NetGroup this._spec = new GroupSpecifier(this._groupName); _spec.serverChannelEnabled = true;//设置允许创建到服务端的通道 _spec.objectReplicationEnabled = true;//允许对象复制 _ng = new NetGroup(_nc, _spec.groupspecWithAuthorizations()); _ng.addEventListener(NetStatusEvent.NET_STATUS, _nc_Net_Status); break; case "NetGroup.Connect.Success": _connected = true; _ng.replicationStrategy = NetGroupReplicationStrategy.LOWEST_FIRST;//设置数据块传输时,先传递索引号最小的块 break; case "NetGroup.Replication.Fetch.SendNotify": //每当"接收方"有数据到达(但尚未开始接收)时,将触发此处理 output(" -->通知:数据块 " + e.info.index + " 即将被接收"); break; case "NetGroup.Replication.Fetch.Failed": //“接收方”有数据接收失败时,将触发此处理 output(" -->错误:数据块 " + e.info.index + " 接收失败"); break; case "NetGroup.Replication.Fetch.Result": //“接收方”每次成功接收到数据时,触发此段处理 output(" -->数据块 " + e.info.index + " 已成功接收,值:" + e.info.object); _ng.addHaveObjects(e.info.index, e.info.index); //接收完成以后,将接收到的数据加入“待发送对象列表"中,这样人越多,传输越稳定,速度也越快 if (_data == null) { _data = new Vector.<String>(this._dataLength); } _data[e.info.index] = e.info.object.toString(); //说明全部接收完了 if (e.info.index == this._dataLength - 1) { for (var i:int = 0; i < _dataLength; i++){ _data[i] = "这是数据 " + i.toString(); this._txtObj.appendText("index:" + i.toString() + ",data:" + _data[i] + " | "); } } break; case "NetGroup.Replication.Request": //每当有数据传输请求时,“提供方”将触发此处理 _ng.writeRequestedObject(e.info.requestID, _data[e.info.index]);//这里才是真正的响应“接收方",将指定的数据发送过去 output(" -->数据块 " + e.info.index + " 请求被发送,本次请求ID:" + e.info.requestID); break; default: break; } } //初始化生成数据 private function _btnGenData_Click(e:MouseEvent):void { this._txtObj.text = ""; if (_data==null){ _data = new Vector.<String>(this._dataLength); } for (var i:int = 0; i < _dataLength; i++){ _data[i] = "这是数据 " + i.toString(); this._txtObj.appendText("index:" + i.toString() + ",data:" + _data[i] + " | "); } } //将生成的初始数据,添加到待发送的“列表”中 private function _btnAddToHave_Click(e:MouseEvent):void { this._ng.addHaveObjects(0, _dataLength - 1); } //请求接收数据 private function _btnAddToWant_Click(e:MouseEvent):void { this._ng.addWantObjects(0, _dataLength - 1); } //输出结果 private function output(s:String):void { this._txtOutput.appendText(s + "\n"); } } }
在这段代码中我们看到了一个全新的NetGroup对象,要使用p2p,“接收方”与“接收方”必须先加入到“相同名称"的NetGroup中。而且要发送的数据,必须分解有顺序的一块一块(通常用有序数组来保存这些数据块),然后"发送方"调用addHaveObjects方法设置待发送的数据块,而"接收方"则调用addWantObjects请求需要接收的块。
一旦"接收方"调用了addWantObjects方法后,"发送方"便会进入"NetGroup.Replication.Request"状态,此时"发送方"响应"接收方"的请求,将需要的数据块以udp协议发送过去,然后“接收方”会收到"NetGroup.Replication.Fetch.SendNotify"的数据到达通知,如果成功接收,将进入“NetGroup.Replication.Fetch.Result”状态,全部接收完成后,开发人员可根据需要将这些块重新合并成原始对象。
处理过程示意图如下:
文中代码最终的运行截图:
测试方法:发送方先点击“生成初始数据”,然后点击“添加要发送的数据”,最后接收方点击“接收数据”
此外,如果多开几个"接收方",可以验证一下“接收方”收到数据后是否能变成数据提供者,向其它接收方提供数据,也就是所谓的p2p中"人越多,速度越快,传输越稳定"的现象
但是,FMS4中的p2p也不是完美无缺,实际测试下来,目前尚不能打洞,即所有peer端如果在同一个网段,传输是正常的,但是如果不是同一个网段则无法进行p2p。
不过,如果参与p2p的机器越多,接收到数据的客户端根据文中的代码处理,也可以变成发送方,这表示有可能本来在同一个网段的其它用户原本没有数据来源,但是只要本网段有一个用户接收到数据后(比如这个用户有多重网络),本网段的其它用户也能接收数据了,这在一程度上能解决打洞的矛盾。