在fms4以前Adobe只允许在stratus中才能使用p2p功能。令人高兴的是,在最新发布的fms4中,p2p功能已经集成进来了,这将给实时视频类的应用带来更高的效率,adobe这次很给力!
为了使用p2p,开发用的flex sdk至少要4.1以上(当然最高版本是代号为hero的4.5版本,可从adobe的官网下载),另外还需要fms4(同样可从adobe官网下载开发版本)。
先上完整代码吧:
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143 | 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:https://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的机器越多,接收到数据的客户端根据文中的代码处理,也可以变成发送方,这表示有可能本来在同一个网段的其它用户原本没有数据来源,但是只要本网段有一个用户接收到数据后(比如这个用户有多重网络),本网段的其它用户也能接收数据了,这在一程度上能解决打洞的矛盾。