出于工作需要,有时候需要模拟用户的慢网速对产品做进一步测试&优化,目前有三个软件可以模拟慢网速:Fiddler,NetLimiter,Network Delay Simulator。应该还有更多好用的软件尚待发掘。

 

Fiddler 免费软件。模拟网速功能比较单一(Rules --> Performance --> Simulate Modem speed),选项较少,Fiddler仅是减缓带宽并未引入包丢失(后面的Network Delay Simulator加入了包丢失模拟)。且因为浏览器并发连接数问题,会造成(Http watch 或Firebug)测试结果的瀑布图不准。所以虽然有这个功能,咱们一般不用它。fiddler的亮点在另一方面

 

NetLimiter 共享软件,需要自己注册。准确的说是一款网络流量控制软件,通过它,你可以直接来控制每个程序对Internet的访问以及流量分配情况。这里有前人制作的图。

 

Network Delay Simulator  免费软件,下载地址 。我正在使用的,三种之中功能最强大,监听Network Interface Card (NIC)和TCP/IP stack之间的网络流量,可以模拟延时、带宽甚至丢包率,更精确地模拟慢网速环境。设置也很简单方便:输入带宽,点击Save Flow即可,如果你要模拟丢包,填下丢包率便行。见图。

 

 还有更专业的~~~~

工具推荐中指出Fiddler的“Simulate Modem Speeds”功能(Rules->Performance-> Simulate Modem Speeds)是延时,下面具体分析其实现:

Fiddler2\Scripts\SampleRules.js中设置脚本规则

static function OnBeforeRequest(oSession: Session)

{

     if (m_SimulateModem){

     // Delay sends by 300ms per KB uploaded.

     oSession["request-trickle-delay"] = "300";

     }

 

static function OnBeforeResponse(oSession: Session)

{

if (m_SimulateModem){

     // Delay receives by 150ms per KB downloaded.

     oSession["response-trickle-delay"] = "150";

}

 

实现代码则在Fiddler.exe中的ServerChatter

public bool ResendRequest()

{

....

  if (this.m_session["request-trickle-delay"] != null)

  {

      int iMS = int.Parse(this.m_session.oFlags["request-trickle-delay"]);

      this.pipeServer.TrickleSend(0x400, iMS, this.m_session.oRequest.headers.ToByteArray(true, true, this._bWasForwarded && !this.m_session.isHTTPS));

      this.pipeServer.TrickleSend(0x400, iMS, this.m_session.requestBodyBytes);

   }

....

 

Fiddler.exe中的Session

public bool ReturnResponse(bool bKeepNTLMSockets)

{

    …

    try

    {

        if ((this.oRequest.pipeClient != null) && this.oRequest.pipeClient.Connected)

        {

            if (this.oFlags["response-trickle-delay"] != null)

            {

                int iMS = int.Parse(this.oFlags["response-trickle-delay"]);

                this.oRequest.pipeClient.TrickleSend(0x400, iMS, this.oResponse.headers.ToByteArray(true, true));

                if (this.responseBodyBytes != null)

                {

                    this.oRequest.pipeClient.TrickleSend(0x400, iMS, this.responseBodyBytes);

                }

            }

 

可以看到它们都用到了Fiddler.exe中类Pipe的函数TrickleSend,以下是其定义:

public void TrickleSend(int iSize, int iMS, byte[] data)

{

    for (int i = 0; i < data.Length; i += iSize)

    {

        Thread.Sleep((int) (iMS / 2));

        this.Send(data, i, iSize);

        Thread.Sleep((int) (iMS / 2));

    }

}

由此可见Fiddler.exe在请求和发送的每KB过程中都采取了延时模拟。关于延时,Issues with Time Warner Cable service in New York中有人说道”With the modem doing some QoS, it can maintain 150-300ms latency when uploading and downloading and the line remains pretty snappy.”BT Highway中也有类似描述。作者给出了如下解释These speeds were derived from a simple model of the effective throughput of a modem.You can adjust these to match any other connection speed you want, so long as such speed is slower than how you're actually connected to the network, of course.The neXpert plugin for Fiddler does response-time predictions using data from Microsoft Research. 

Fiddler无法模拟浏览器并发连接数限制,开启工具栏上的"Streaming"模式可以解决,但这样就打破了规则"Simulate Modem speeds",得出来的总体时间大幅缩短,此时Fiddler不仅延时了带宽还延时了浏览器并发连接。正如作者所说Fiddler仅是减缓带宽并未引入包丢失。

 

Speed Simulator看起来比Fiddler模拟的差不多(注意:多做几次测试,找一个稳定的结果,每次选择HttpWatchTools->Clear Cache and All Cookied清空MSIE):


 
 

 

 

 

 

本想用同样方法测试浏览器插件IE Throttle,结果它无法在x64 Windows XP SP2上的x86 IE中打开“IE Throttle Settings”。同类型的还有Firefox Throttle

 

Network Delay Simulator(简称NetSim)采用Token BucketLeaky_bucket算法模拟真实网络并监听网卡和Tcp/IP之间的网络流量,详见Is there any forum for Net Simulator显然,它采用的是网络包丢失模拟但需要注意这款免费软件从version 0.9.129开始支持x86 Windows 到Win7,关于此软件任何问题可联系Andrew 

HP推荐ShunraVE DesktopWAN emulation使用运行在FreeBSD上的免费Dummynet

 

ToDo:

HP LoadrunnerSpeed Simulation如何?MS VSTS 2010Network Emulation又如何?

 由于测不准,我们得不到准确的结果,但可以得到尽可能精确的结果。比如modem,你能确定国内中国电信、中国网通等ISP提供给终端用户的无论何时何地都是标准的速度56k吗?也许KeynoteGomez提供的服务,Google AnalyticsOmnitureWebtrendsWebPagetest运营监控是一种弥补措施。 【关于Fiddler其它】1. 通过WinINET设置Proxy转发给127.0.0.1:8888public bool Attach()
{
    if (!this._bIsAttached)
    {
        this.oAllConnectoids = new WinINETConnectoids();
        this.piPrior = this.oAllConnectoids.GetDefaultConnectionGatewayInfo();
        if ((this.piPrior.sHttpProxy != null) && this.piPrior.sHttpProxy.Contains(CONFIG.sFiddlerListenHostPort))
        {
            this.piPrior.sHttpProxy = null;
            this.piPrior.sHttpsProxy = null;
            this.piPrior.bUseManualProxies = false;
            this.piPrior.bAllowDirect = true;
        }
        if (CONFIG.bForwardToGateway && ((this.piPrior.sPACScriptLocation != null) || this.piPrior.bAutoDetect))
        {
            this.oAutoProxy = new WinHTTPAutoProxy(this.piPrior.bAutoDetect, this.piPrior.sPACScriptLocation);
        }
        if (CONFIG.bForwardToGateway && this.piPrior.bUseManualProxies)
        {
            this._ipepHttpGateway = Utilities.IPEndPointFromHostPortString(this.piPrior.sHttpProxy);
            this._ipepHttpsGateway = Utilities.IPEndPointFromHostPortString(this.piPrior.sHttpsProxy);
            this._ipepFtpGateway = Utilities.IPEndPointFromHostPortString(this.piPrior.sFtpProxy);
            if (this.piPrior.sHostsThatBypass != null)
            {
                this.oBypassList = new ProxyBypassList(this.piPrior.sHostsThatBypass);
                if (!this.oBypassList.HasEntries)
                {
                    this.oBypassList = null;
                }
            }
        }
        else
        {
            this._ipepFtpGateway = this._ipepHttpGateway = this._ipepHttpsGateway = null;
        }
        WinINETProxyInfo oNewInfo = new WinINETProxyInfo();
        if (CONFIG.bHookWithPAC)
        {
            oNewInfo.bAllowDirect = true;
            oNewInfo.sPACScriptLocation = "file://" + CONFIG.GetPath("Pac");
        }
        else
        {
            oNewInfo.bUseManualProxies = true;
            oNewInfo.bAllowDirect = true;
            oNewInfo.sHttpProxy = CONFIG.sFiddlerListenHostPort;
            if (CONFIG.bCaptureCONNECT)
            {
                oNewInfo.sHttpsProxy = CONFIG.sFiddlerListenHostPort;
            }
            else
            {
                oNewInfo.sHttpsProxy = this.piPrior.sHttpsProxy;
            }
            if (this.piPrior.bUseManualProxies)
            {
                oNewInfo.sFtpProxy = this.piPrior.sFtpProxy;
                oNewInfo.sSocksProxy = this.piPrior.sSocksProxy;
            }
            if (CONFIG.bCaptureFTP)
            {
                oNewInfo.sFtpProxy = CONFIG.sFiddlerListenHostPort;
            }
            oNewInfo.sHostsThatBypass = CONFIG.sHostsThatBypassFiddler;
        }
        if (this.oAllConnectoids.HookConnections(oNewInfo))
        {
            this._bIsAttached = true;
            FiddlerApplication.OnFiddlerAttach();
            this.WriteAutoProxyPACFile(true);
        }
        else
        {
            FiddlerApplication.DoNotifyUser("Failed to register Fiddler as the system proxy.", "Error");
            _setDynamicRegistryKey(false);
            return false;
        }
        _setDynamicRegistryKey(true);
    }
    return true;
}
 2.DNSTimeFiddler.exe函数HTTPSTunnel.TunnelDirectly,Session.ExecuteHTTPSConnect,ServerChatter.ConnectToHostServerChatter.ConnectToHost中:
int num3 = Environment.get_TickCount();
iPAddress = DNSResolver.GetIPAddress(str2, true);
this.m_session.Timers.DNSTime = Environment.get_TickCount() - num3;