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