最近在做一个基于BS结构的视频会议系统,决定采用开源的FluorineFx.net与Flex结合的方法进行开发,前期开发都非常顺利,包括同步白板等。但到了实时视频传输的时候,原本设计是每个客户端可以显示三路视频,但到IIS上测试的时候,发现状态很不稳定,偶尔可以全部显示出来,大部分情况下,客户端总是随机显示一个或两个。查询了ActionScript的技术文档、FluorineFx的技术文档等,也没有找出与这个问题相关的描述,包括网络上,对NetConnection与NetStream的对应关系也没有很肯定的说明或总结。程序中实在找不出问题的所在,我初步怀疑是浏览器并发连接数限制的问题导致无法在内置于浏览器的swf中同时连接多路视频,于是决定将这部分抽象出来,专门做一个测试,测试选择了三个比较具有代表性的浏览器进行。
开发环境:VS2008(FluorineFx.Net)与FlexBuilder3
测试环境:IE 8,Chrome,FF + Flash player 11 Debug版
一、测试用的流处理服务程序编写
首先利用Vs 2008新建了一个FluorineService项目,并添加应用程序适配器类,在RTMP连接AppConnect时用来判断连接的合法性,即只有当rtmp连接的parameters[1]为"000"的连接请求才允许连接服务器上的rtmp信道端口,如下:
using FluorineFx;using FluorineFx.Messaging.Adapter;using FluorineFx.Messaging.Api;using FluorineFx.Messaging.Api.SO; namespace OnLineService{ public class DemoApp:ApplicationAdapter { public override bool AppConnect(IConnection connection, object[] parameters) { string userName = parameters[0] as string; string password = parameters[1] as string; // 这里可以自定义登录判断验证逻辑,此处只要密码为000即可进入 if (password != "000") return false; return true; } } }
新建一个FluorineFx Web后,修改其Web-INF/Flex文件夹下的services-config.xml文档,指定rtmp传输信道所用的端口:
<channel-definition id="my-rtmp" class="mx.messaging.channels.RTMPChannel"> <endpoint uri="rtmp://{server.name}:2037" class="flex.messaging.endpoints.RTMPEndpoint"/> <properties> <idle-timeout-minutes>20</idle-timeout-minutes> </properties> </channel-definition>
在FluorineFx Web项目根目录下添加apps文件夹,在apps下添加OnLineUser文件夹,在OnLineUser下添加app.config配置文件及一个streams文件夹,streams下放入五个flv视频文件(我这里自己随便用Flex写的小程序采集了一小段视频,并复制成5份放在这个文件夹下,分别命名为mytest1...)
在app.config中指定流处理的服务程序为DemoApp,如下:
<?xml version="1.0" encoding="utf-8"?><configuration> <application-handler type="OnLineService.DemoApp"/></configuration>
编译后启动调试,使Asp.Net Development Server处于运行状态(以提供流处理的服务)
二、测试用Flex项目的开发
打开Flex Builder3后,新建一个Flex项目,项目路径设置在“一”中建立的FluorineFx Web项目的根目录下的Flex目录,并引入相关libs与编译参数,如下图所示:
然后将mxml文件编写如下:
<?xml version="1.0" encoding="utf-8"?><mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" width="1254" height="246" backgroundGradientAlphas="[1.0, 1.0]" backgroundGradientColors="[#000000, #049FF1]" fontSize="12" initialize="init()"><mx:HBox id="hbox" x="10" y="31" width="1234" height="187"></mx:HBox><mx:Script source="FiveConnFiveStream.as"/></mx:Application>
添加一个FiveConnFiveStream.as文件,在其下建立五个NetConnection与五个NetStream,分别连接到“一”中的五个视频文件,如下:
import flash.events.NetStatusEvent;import flash.net.NetConnection;import flash.net.NetStream;import mx.controls.VideoDisplay;import mx.messaging.config.ServerConfig;private var rtmpChanner:String;private var nc:NetConnection;private var nc2:NetConnection;private var nc3:NetConnection;private var nc4:NetConnection;private var nc5:NetConnection;private var ns1:NetStream;private var ns2:NetStream;private var ns3:NetStream;private var ns4:NetStream;private var ns5:NetStream; private function init():void{ rtmpChanner=ServerConfig.getChannel("my-rtmp").endpoint+"/OnLineUser"; nc=new NetConnection(); nc.addEventListener(NetStatusEvent.NET_STATUS,OnConnHandler); nc.client=this; nc.connect(rtmpChanner,"adsfasdf","000"); nc2=new NetConnection(); nc2.addEventListener(NetStatusEvent.NET_STATUS,OnConnHandler2); nc2.client=this; nc2.connect(rtmpChanner,"adsfadasdf","000"); nc3=new NetConnection(); nc3.addEventListener(NetStatusEvent.NET_STATUS,OnConnHandler3); nc3.client=this; nc3.connect(rtmpChanner,"adsfffasdf","000"); nc4=new NetConnection(); nc4.addEventListener(NetStatusEvent.NET_STATUS,OnConnHandler4); nc4.client=this; nc4.connect(rtmpChanner,"adfgg","000"); nc5=new NetConnection(); nc5.addEventListener(NetStatusEvent.NET_STATUS,OnConnHandler5); nc5.client=this; nc5.connect(rtmpChanner,"adfaaa","000");} private function OnConnHandler(event:NetStatusEvent):void{ trace("连接一状态:"+event.info.code); if(event.info.code=="NetConnection.Connect.Success") { var vds:VideoDisplay=new VideoDisplay(); vds.height=160; vds.width=240; var vd:Video=new Video(); vd.width=240; vd.height=160; this.ns1=new NetStream(this.nc); this.ns1.addEventListener(NetStatusEvent.NET_STATUS,OnStreamHandler1); vd.attachNetStream(this.ns1); this.ns1.client=this; this.ns1.play("mytest1"); vds.addChild(vd); this.hbox.addChild(vds); trace("播放一");} } private function OnConnHandler2(event:NetStatusEvent):void{ trace("连接二状态:"+event.info.code); if(event.info.code=="NetConnection.Connect.Success") { var vds2:VideoDisplay=new VideoDisplay(); vds2.height=160; vds2.width=240; var vd2:Video=new Video(); vd2.width=240; vd2.height=160; this.ns2=new NetStream(this.nc2); this.ns2.addEventListener(NetStatusEvent.NET_STATUS,OnStreamHandler2); vd2.attachNetStream(this.ns2); this.ns2.client=this; this.ns2.play("mytest2"); vds2.addChild(vd2); this.hbox.addChild(vds2); trace("播放二"); }} private function OnConnHandler3(event:NetStatusEvent):void{ trace("连接三状态:"+event.info.code); if(event.info.code=="NetConnection.Connect.Success") { var vds3:VideoDisplay=new VideoDisplay(); vds3.height=160; vds3.width=240; var vd3:Video=new Video(); vd3.width=240; vd3.height=160; this.ns3=new NetStream(this.nc3); this.ns3.addEventListener(NetStatusEvent.NET_STATUS,OnStreamHandler3); vd3.attachNetStream(this.ns3); this.ns3.client=this; this.ns3.play("mytest3"); vds3.addChild(vd3); this.hbox.addChild(vds3); trace("播放三"); }} private function OnConnHandler4(event:NetStatusEvent):void{ trace("连接四状态"+event.info.code); if(event.info.code=="NetConnection.Connect.Success") { var vds4:VideoDisplay=new VideoDisplay(); vds4.height=160; vds4.width=240; var vd4:Video=new Video(); vd4.width=240; vd4.height=160; this.ns4=new NetStream(this.nc4); this.ns4.addEventListener(NetStatusEvent.NET_STATUS,OnStreamHandler4); vd4.attachNetStream(this.ns4); this.ns4.client=this; this.ns4.play("mytest4"); vds4.addChild(vd4); this.hbox.addChild(vds4); trace("播放四"); }} private function OnConnHandler5(event:NetStatusEvent):void{ trace("连接五状态"+event.info.code); if(event.info.code=="NetConnection.Connect.Success") { var vds5:VideoDisplay=new VideoDisplay(); vds5.height=160; vds5.width=240; var vd5:Video=new Video(); vd5.width=240; vd5.height=160; this.ns5=new NetStream(this.nc5); this.ns5.addEventListener(NetStatusEvent.NET_STATUS,OnStreamHandler5); vd5.attachNetStream(this.ns5); this.ns5.client=this; this.ns5.play("mytest5"); vds5.addChild(vd5); this.hbox.addChild(vds5); trace("播放五"); }} private function OnStreamHandler1(event:NetStatusEvent):void{ trace("流一状态:"+event.info.code);}private function OnStreamHandler2(event:NetStatusEvent):void{ trace("流二状态:"+event.info.code);}private function OnStreamHandler3(event:NetStatusEvent):void{ trace("流三状态:"+event.info.code);}private function OnStreamHandler4(event:NetStatusEvent):void{ trace("流四状态:"+event.info.code);}private function OnStreamHandler5(event:NetStatusEvent):void{ trace("流五状态:"+event.info.code);}
三、开始测试
在FlexBuilder中Builder后,进行Debug调试。
此时,我们就可以看到在浏览器中已经可以实时加载视频了,为了我们测试的目的与结果,我们要设计如下几种测试以验证结果及进行对比:
a、排除服务器的因素,即服务器是否能提供多个并发连接的能力(以证明客户端有的实时视频显示不出非服务器的原因引起)
b、场景一:在Flex中建立5个NetConnection与5个NetStream,5个NetStream分别指向5个视频文件(验证客户端连接建立能力及每个连接加载实时流后,浏览器可维持的连续的并发连接的能力)
c、场景二:在Flex中建立5个NetConnection与5个NetStream,5个NetStream同时指向一个视频文件(验证是否可以将多个连接的并发连接同时关联服务器上同一个文件)
d、场景三:在Flex中建立1个NetConnection,并以这个NetConnection建立5个NetStream,分别测试指向一个视频文件与5个视频文件时的表现
e、场景四:将bcd中的五个都改为3个连接与3个流做对比测试
(“二”为b场景一的as代码,其它场景的在附件中有,这里不一一列出。)
注:以上几个场景,除a外,其它都分别编译后,在IE8,FF,Chrome中进行测试(IE的话还需要对注册表中连接数进行修改再次测试),每个单独的测试刷新10次,记录客户端实时连接并成功显示的视频数,并且为了排除缓存的影响,每次测试前都清空各个浏览器的缓存。
首先排除服务器的影响,我们对服务器进行连接的压力测试,同时打开多个浏览窗口,同时连接这个应用,看是不是可以支持多于5路的视频传输,测试效果如下(排除服务器因素,证明FlourineFx.Net并没有论坛中所言的连接性能诡异的问题,但这并不是压力测试,我这里的目的只是为了证明服务器的rtmp应用程序适配器可以支持5路以上的实时连接,下图中三个浏览器同时打开,并访问这个应用,可以轻易看到10个连接)
以下为随机截取的几个测试场景的图片与Flex Builder Console中的输出情况
(IE8在没有修改注册表连接数前,用5个连接与5个NetStream连接服务器时的情况,成功进行了4个并发的实时连接)
(在Flex中只用一个NetConnection,建立5个NetStream,不管是连接一个文件还是5个文件,在各个浏览器中,都只有最后一个NetStream可以成功连接上,由此证明,如果要连接实时视频数据流时,一个NetConnection只能与一个NetStream相关联,如下图)
四、测试结果统计
以下为测试结果的统计表格:
IE8在没有修改注册表连接数之前:
IE8修改注册表,将连接数改为10之后
(IE8的注册表连接数修改:HKEY_CURRENT_USER/SoftWare/Microsoft/Windows/CurrentVerSion/InternetSettings下添加两个DWORD,MaxConnectionsPer1_OServer与MaxConnectionsPerServer,分别指定其值即可指定连接数,如下:
)
Chrome的测试数据
FF的测试数据
五,总结
1、在Flex开发中,一个NetConnection只能在同一时间被一个NetStream占用,用来传输实时数据
2、现在的浏览器一般都至少能稳定地支持两个并发连接,但跟某些网站上所言的,支持几个并发连接的能力,并不是稳定的,在我上述的测试中,五个NetConnection都可以建立成功,但若利用这些连接通道同时并发传输数据,并不能保证每次都能成功建立起这些并发连接。
另附:我查阅了一下HTTP/1.1 RFC中的相关章节8.1.4,其中规定单客户端与任何服务器或代理的连接不应超过两个(single-user client SHOULD NOT maintain more than 2 connections with any server or proxy.)。但现在的浏览器基本上都超过了这个并发连接数,以下为国外某网站上列出的各浏览器支持的并发连接数表格截图:
但经过我上面的测试发现,对于并发的且长时间被占用的连接,浏览器一般只能稳定地支持两个,不管是IE\Chrome\FF,都是如此,或许浏览器方所宣称的支持多路并发连接的能力,是对于那些突发式的、不需要长时间占用这个连接来传送大量数据而言的。
做以上测试花了一上午时间,因此写个总结在这里。原创文章,转载请注明一下。观点可能有些片面,不正确之处望指正。
附:测试中用的源文件下载 (注:以上不同的测试场景请修改mxml中引入不同的as文件)
分类: Flex与ActionScript3学习笔记, FlourineFx.net使用心得
标签: Flex, NetConnection与NetStream的对应关系, Fluorine, FluorineFx.Net, 实时视频, 浏览器并发连接