在解决日常的支持需求中,经常会遇到一些用户反馈一些无法简单复现的bug,有很大一部分的bug是由于用户自身的网络环境波动,或者是本身网络环境就较为恶劣,而服务在面对这种恶劣的网络环境的健壮性不够,导致会出现一些意想不到的bug。而在正常的开发自测过程中很难去营造出这种恶劣的网络环境,使得这些bug较难被提前发现和修复。另外一些服务在恶劣网络环境下虽然不会出现不可用的情况,但是用户体检很差,为了优化这个情况下的用户体验,也需要去在本地模拟这种环境来进行调优。
所以要去复现这些bug,甚至是去提前发现这些bug,就需要能够在开发环境中模拟出恶劣的网络环境,从而看到在这种恶劣的网络环境下的服务的表现等。当前模拟恶劣网络环境主要可以通过以下这些手段实现:
- 通过应用层或者传输层的代理服务器,通过在代理服务器上设置一些模拟恶劣网络环境的参数,使得通过这些代理服务器的流量都被转化为恶劣网络环境下的流量。如利用Fiddler,Charles等具有代理服务器功能的网络流量分析软件来实现。
- 通过利用一些更底层的驱动层面的服务,通过控制网卡的收包发包的行为,来模拟恶劣的网络环境。如dummynet的ipfw驱动等。
- 通过建立一个可控的网关,在网关上部署模拟恶劣环境的相关程序,所有需要借助该网关进行转发的流量都会被模拟为恶劣网络条件。Linux下的netem就提供了这类支持。
这里主要先讲的是第一种手段,即利用Fiddler来模拟恶劣的网络环境,对服务进行测试,这个手段实现简单,较为直观,但是缺点是只能支持那些利用HTTP进行通信和交互的服务。在之后的文章中也会进一步说一下后两种手段。
【Fiddler是啥】
Fiddler的官网上是这样描述它自己的:The free web debugging proxy for any browser, system or platform,即跨浏览器、跨系统、跨平台的免费Web Debug代理服务器。当你的HTTP浏览经过Fiddler时,Fiddler可以监视流量,查看HTTP通讯的各种信息,设置断点查看和修改HTTP数据,甚至可以构造各种测试用的HTTP包以及重放已记录的包等。其官网是http://www.fiddler2.com/fiddler2/,上面详细地介绍了Fiddler到底是什么。
【简单地利用Fiddler限速模拟恶劣网络环境】
Fiddler本身已经预置提供了模拟Modem速度的选项,其位置位于:
Rules – Performances – Simulate Modem Speeds
勾选该选项后,所有通过Fiddler代理的流量都会变得和多年前的56k小猫时上网一般的慢。
由于Fiddler只是一个HTTP代理,要直观地看出限速效果,最好是运行在浏览器中的测速工具,这里选用speedtest.net提供的测速工具进行测试。
首先是开启该选项之前的速度:
打开了Simulate Modem Speeds后:
速度已经回到了当年那种无法忍受的低速了,注意到这里PING值也有了显著的提高,而事实上ping值是ICMP层的控制报文,并不会被Fiddler影响,理论上ping值并不会出现提高的情况,进一步分析Fiddler中的报文则可以看出端倪:
事实上网页插件并不能实现发送ICMP包并得到ping值的功能,而是用多次较小的HTTP GET请求的响应时间来计算PING值,这里实际算出来的是一个平均的HTTP的RTT值,所以受到Fiddler模拟恶劣环境的影响就是正常的了。
【调整模拟恶劣网络环境的参数】
直接模拟Modem速度实在是慢爆了,事实上就算是在很差信号的情况下,手机移动网络的速度都已经超过了当年的56k Modem速度了,所以采用默认的配置模拟出来的环境过于恶劣,并不一定符合需求,此时就需要对限速的参数进行调整。
Fiddler本身就提供了一个配置文件供调整这些参数,点击:
Rules – Customize Rules…
就会用文本编辑器打开CustomRules.js文件,其默认位于用户目录的文档目录下的\Fiddler2\Scripts 位置,后缀名是js,其内容实质是JScript.NET——微软对ECMAScript规范的实现,与日常使用的javascript是属于同一个规范下的,但是在扩展的细节实现存在一定的不同。
打开该文件后,可以找到一个m_SimulateModem标志位:
if (m_SimulateModem) {
- 该标志位控制着oSession的两个参数值的设置,当勾选了Simulate Modem Speeds时,request-trickle-delay与response-trickle-delay就会被设置,其中request-trickle-delay中的值代表每KB的数据被上传时会被延时多少毫秒,response-trickle-delay则对应下载时每KB的数据会被延时多少毫秒,如果本身网速已经相当快的话,这里设置的值就可以近似地推算出开启模拟后的上传和下载带宽了,比如默认设置下下载延时为150ms,上传延时为300ms,对应可以推算出大致的模拟带宽为:
上传带宽=(1*8/1000)/0.300≈0.053Mbps
下载带宽=(1*8/1000)/0.150≈0.027Mbps
然而实际情况下却得到了两倍于这个值的带宽,推测可能是Fiddler的内部实现上有一些和描述上的不同,为何为造成这个现象现在还不是很清楚,所以上述公式最后还需要修正一个2.0的系数,即:
上传带宽=((1*8/1000)/0.300)*2.0≈0.106Mbps
下载带宽=((1*8/1000)/0.150)*2.0≈0.053Mbps
假设我们将两个参数都设置为50,则会得到上下载带宽均为0.32Mbps,测速结果如下所示:
【编写自定义脚本】
进一步地,我们可以扩展CustomRules.js里的逻辑,参照Jscript的文档可以在模拟恶劣环境中加入更多自定义的逻辑,这里实现了一个随机延时量设置,使得网络带宽不是恒定为一个低速的值,而是会在一定范围内随机抖动:
static function randInt(min, max) {
return Math.round(Math.random()*(max-min)+min);
}
if (m_SimulateModem) {
得到的测试结果如下:
在测速过程中的瞬时速度的趋势图如下:
可以看到整体的网络限速存在了一定程度的抖动。
通过进一步扩展CustionRules.js可以实现很多需要的恶劣环境模拟场景,如果场景较为复杂的话,也可以通过编写Fiddler的插件的方式,编写C#插件代码来进一步控制Fiddler的行为,在这里就不多做赘述了。详细可以参照:http://docs.telerik.com/fiddler/extend-fiddler/extendwithdotnet
【Fiddler模拟恶劣网络环境的局限性】
Fiddler进行限速较为简单和灵活,配置也较为方便,但是由于它是一个应用层的HTTP的代理,只能模拟该层上的行为,对于一些复杂的网络层的丢包、重传等恶劣情况就不能很好的模拟出来,而且对于其他协议的应用也不支持,后续会介绍一些其他的模拟恶劣环境的方法和软件来弥补这些缺失。
利用Dummynet模拟恶劣网络环境
在之前的文章中提到了三种模拟恶劣网络环境调试代码的手段:
- 应用层或者传输层的代理服务器
- 传输层或者网络层控制数据包的驱动
- 网络层控制数据包的网关
同时在之前的文章中介绍了第一种手段,即利用应用层的HTTP代理Fiddler来模拟恶劣网络环境,这种方式简单且灵活,但是其处于应用层,限制较大,同时也没有办法从带宽和延时两个方面分别去精细化地对恶劣网络环境进行模拟,这里介绍第二种手段——Dummynet。
Dummynet简介
Dummynet的官网地址是:http://info.iet.unipi.it/~luigi/dummynet/,官网上对于dummynet的描述是这样的:dummynet is a live network emulation tool, originally designed for testing networking protocols, and since then used for a variety of applications including bandwidth management. 即Dummynet是一个实时的网络模拟工具,事实上dummynet是ipfw防火墙的一部分,ipfw是一个网络层的防火墙,并内建于FreeBSD之中。
利用ipfw的options DUMMYNET选项,可以设置一系列的pipes,从而做到对网络流量进行控制的目的。
Dummynet基本原理
如官网上提供的这张图所示,dummynet通过在两个网络层中(一般是在传输层和应用层之间或者网络层与传输层之间)建立一条条pipe的方式来控制网络流量,符合设定的规则中的网络流量会被引入这些管道中去,从而使得dummynet可以介入这些流量之中,控制带宽、延迟,甚至进一步控制丢包率等众多参数。
Dummynet安装(Windows)
Dummynet本身作为ipfw防火墙的一部分,其内建于FreeBSD,其本身支持FreeBSD,OSX,Linux,Windows多种操作系统下的安装和使用,在OSX和Linux下下载源码编译安装一般即可使用,这里主要介绍一下Windows下的Dummynet安装。
Dummynet在Windows下是作为一个网卡上的服务驱动存在的,在官方给出的二进制文件包中已经包含了该驱动的sys与inf文件,但是该驱动是没有包含数字签名的,在并没有引入驱动强制签名机制的Windows操作系统(如Windows XP)上时,直接到官网下载到最新的二进制包,然后依照之后的操作步骤进行安装即可以完成ipfw+dummynet服务驱动的安装,但是在引入了驱动强制签名机制(一般是Windows 7以后的Windows版本,或者设置了比较严格的组策略)的操作系统上,是无法安装ipfw+dummynet服务驱动的,要解决这个问题可以使用两种方案:
- 打开Windows的测试模式,关闭驱动强制签名机制,然后安装无签名的驱动
这种方式的实现也有两种方式,一种是在启动Windows时启动选项中关闭驱动强制签名校验机制,可以参考这篇文章 http://jingyan.baidu.com/article/7c6fb42879543380642c9036.html ,另外一种方式则是进入Windows的测试模式,可以参考 http://jingyan.baidu.com/article/acf728fd21c3e7f8e510a3ef.html 文章中的说明。 - 为无签名的ipfw+dummynet服务驱动打上自签名或者可用的签名
如何进行自签名或者打上可用的签名可以参考MSDN上的说明 https://msdn.microsoft.com/en-us/library/windows/hardware/ff544865(v=vs.85).aspx ,具体如何签名并不在本文的范畴内。
本文主要使用第二种方式,为ipfw+dummynet服务驱动打上百度签名后,即可在Windows 7上正常安装。
安装的方式如下:
- 找到你需要限制带宽和流量的网卡,进入属性:
- 点击下方的安装,选择服务,然后选择从磁盘安装,并在浏览中找到ipfw+dummynet服务驱动的inf安装文件:
- 在“此连接使用下列项目”中看到ipfw+dummynet之后,即代表ipfw+dummynet服务驱动安装成功,如果安装成功后遇到网卡无法使用的情况,一般会是前一次卸载没有卸载完全,重启电脑可以解决。
安装完成后可以以管理员权限运行binary目录下的testme.bat,如果运行结果中没有报警信息或者错误信息,则驱动安装是成功的:
利用Dummynet模拟恶劣网络环境
利用dummynet的pipe,可以设置一些特定的规则,就可以达到模拟恶劣网络环境的目的,且这些设置对于在操作系统中运行的应用程序来说是透明的、自动生效的,不需要像在使用Fiddler来进行模拟时还需要设置http代理。
- 低带宽模拟
恶劣网络环境最基础的模拟方式就是模拟较小带宽下的情况,利用dummynet中的pipe设置,可以将所有的tcp流和udp流的带宽限制在一定的级别上,运行一个管理员的命令行,并利用binary目录下的ipfw.exe文件就可以设置dummynet中的pipe,在命令行中执行以下命令:
ipfw add pipe 2 in proto tcp
ipfw pipe 2 config bw 2Mbit/s
即可将tcp的下行流量限制在2Mbit/s的带宽上,这里采用和上一篇文章中采用的相同的测速网站speedtest.net来验证限制带宽的效果,在执行以下指令以前的测速结果如下:
在执行完上述指令以后的测速结果如下:
- 高延时模拟
在管理员权限的命令行中执行以下命令:
ipfw add pipe 10 ip from any to any
ipfw pipe 10 config delay 200
这会使得所有的包都被延时200ms,这里利用ping命令来ping百度的方式来直接测试延时,命令执行以前结果如下:
命令执行以后结果如下:
- 高丢包率模拟
在管理员权限的命令行中执行以下命令:
ipfw add pipe 10 ip from any to any
ipfw pipe 10 config plr 0.1
这里设置的是10%的丢包率,这里利用ping -t指令继续ping百度,来观察丢包率的变化:
执行以前:
执行以后:
- 更加精细化的设置
可以看到相比Fiddler来说,利用dummynet可以更加精细化地从各个角度去设置一个恶劣网络环境的参数,上文中提到的带宽、延时和丢包率的模拟当然也可以组合起来使用,同时在设置pipe规则时还可以进一步设置更加精细的规则,比如指定的ip段,指定的协议等,比如这一段指令:
ipfw add pipe 4 src-ip 10.1.2.0/24 in
ipfw pipe 4 config bw 1Mbit/s delay 123 plr 0.1
就设置了一个只会介入源ip来自10.1.2.0/24网段的数据包的pipe,并且这个pipe会限制1Mbit/s的带宽,延时123ms,并且有10%的丢包率,通过组合这些参数,可以模拟出很复杂的恶劣网络环境,从而满足开发和测试的需要。相关的设置参数的说明和设置方法可以参考官网的相关文档和其他的一些说明文档。
在最后,当完成模拟恶劣网络环境后,执行以下命令:
ipfw -q flush
ipfw -q pipe flush
来清除所有设定的pipe和规则,进一步如果需要还原原来的环境的话可以将驱动删除,并重启计算机即可。
三、clumsy 0.2模拟网速
clumsy 能在 Windows 平台下人工造成不稳定的网络状况,方便你调试应用程序在极端网络状况下的表现。
简介
利用封装 Winodws Filtering Platform 的WinDivert 库, clumsy 能实时的将系统接收和发出的网络数据包拦截下来,人工的造成延迟,掉包和篡改操作后再进行发送。无论你是要重现网络异常造成的程序错误,还是评估你的应用程序在不良网络状况下的表现,clumsy 都能让你在不需要额外添加代码的情况下,在系统层次帮你达到想要的效果:
特色:
- 下载即用,不需要安装任何东西。
- 不需要额外设置,不需要修改你的程序的代码。
- 系统级别的网络控制,可以适用于命令行,图形界面等任何 Windows 应用程序。
- 不仅仅只支持 HTTP,任何 TCP, UDP 的网络连接都可以被处理。
- 支持本地调试(服务器和客户端都在 localhost)
- "热插拔",你的程序可以一直运行,而 clumsy 可以随时开启和关闭。
- 实时调节各种参数,详细控制网络情况。
实例
下面的动画展示了 clumsy 作用于一个本地的基于 netcat 的 UDP 服务器/客户端的情况。仔细观察你可以看到数据根据在 clumsy 的影响下产生了相应的变化。 如果你基本知道了 clumsy 是干什么用的,不妨到下载页面选择适用于你系统的版本进行下载。
详细信息
clumsy 首先根据用户选择的 filter 来拦截指定的网络数据。在 filter 中可以设定你感兴趣的协议(tcp/udp),端口号,是接收还是发出的端口。你也可以通过简单的逻辑语句来进一步缩小范围。当 clumsy 被激活时,只有符合这些标准的网络数据会被进行处理,而你不感兴趣的数据仍然会由系统正常传输。
当被 filter 的网络数据包被拦截后,你可以选择 clumsy 提供的功能来有目的性的调整网络情况:
- 延迟(Lag),把数据包缓存一段时间后再发出,这样能够模拟网络延迟的状况。
- 掉包(Drop),随机丢弃一些数据。
- 节流(Throttle),把一小段时间内的数据拦截下来后再在之后的同一时间一同发出去。
- 重发(Duplicate),随机复制一些数据并与其本身一同发送。
- 乱序(Out of order),打乱数据包发送的顺序。
- 篡改(Tamper),随机修改小部分的包裹内容。
尽管当前宽带网络连接十分普及,但网络传输其本身在本质上总不是稳定的。如果你的应用程序中没有应对各种情况的处理,那么有可能一个丢失的 UDP 包裹都会让你的程序崩溃。正确的调试这类行为 显然需要再代码结构上进行仔细的设计和处理,还会很花功夫。而且在某些封装紧密的开发环境(Unity3D 自带的网络库可能是一个例子)下会更麻烦。clumsy 以尽可能减轻程序员负担为目标, 希望提供一个简单方便(但并不完美)的解决方案。
项目的代码可以在github上获取。在下载页面有编译好的版本。强烈建议在使用前花点时间阅读一下文档,来 了解 clumsy 的功能和限制。