当前位置: 首页 > 科技观察

使用WireShark深度调试网络请求

时间:2023-03-15 23:12:04 科技观察

背景最近发现我们的产品有一定概率在打开广告链接(Webview)时非常慢,白屏时间超过10s。在追踪广告的过程中遇到了很多有趣的事情,感觉还是蛮有收获的。我想在这里分享。我主要想说说bughunting的方法论。当然,也不能太空。还需要带点干货,比如WireShark的使用。Bug复现遇到Bug后首先要做的当然是复现。经过一番测试,发现bug只出现在iPhone6等老机型上,而我的7Plus基本没有问题。4G和Wifi都有一定的概率出现,Wifi似乎更频繁。其实一些有经验的开发者看到这里应该会觉得有点懵。这应该不是客户端的BUG,更有可能是广告商的网页质量不高或者网络环境不稳定。但是作为一个可靠的程序员,你怎么能向上级报告这种毫无根据的猜测呢?关注点分离我们知道加载一个网页可以由两部分时间组成,一个是本地处理时间,一个是网络加载时间。两者的分水岭应该在UIWebview的shouldStartLoadWithRequest方法。该方法调用前,本地处理比较耗时,调用后是请求网络加载。所以我们可以把事情分成两部分:从接受点击事件的cell的didSelectedRowAtIndexPath到UIWebview的shouldStartLoadWithRequest。从shouldStartLoadWithRequest到UIWebview的webViewDidFinishLoad。由于偶尔会出现bug,不可能长时间用Xcode调试,所以也要注意写一个简单的工具,持久化保存每条log日志,把函数调用、耗时、具体参数等都保留下来。每一步。这样一旦再次出现,就可以连接电脑读取手机中的日志了。本地处理本地处理的耗时比较短,但是逻辑一点也不简单。个人认为,从展示UITableview到处理点击事件的过程,足以体现一个团队的技术实力。毫不夸张的说,最好的teamleader能做出这样的小生意,其中必然涉及到MVC/MVVM等架构的选型设计和具体实现,网络层和持久层的封装,以及项目模块。现代化分裂等核心知识点。这些我会尽快找时间在一些文章中讲到,这里不再赘述。花了很多时间梳理业务流程,做统计后,还真是有些收获。客户端的逻辑是执行完pushViewController动画后发送请求,浪费了大概0.5s的动画时间,本来可以用来加载网页的。借助网络请求的日志,我也发现本地处理虽然比较浪费时间,但是时间还是比较稳定的,在1s左右。较大的耗时来自于网络请求部分。一般情况下,打开网页都会出现短暂的白屏时间。这段时间系统会加载渲染HTML等资源,同时界面上有菊花在旋转。白屏何时消失取决于系统何时完成加载网页,这是我们无法控制的。但是菊花消失的时间是已知的,我们的逻辑是写在webViewDidFinishLoad里面的。这不一定准确,因为网页重定向时也会调用webViewDidFinishLoad方法,导致客户端误认为已经加载完成。更准确的方法可以参考:如何准确判断WebView加载完成。当然,这只是更准确。就UIWebview而言,几乎不可能准确判断网络加载是否完成(感谢@JackAlan的实践)。所以网络加载也可以细分为两部分,一是纯白屏时间,二是出现网页但菊花还在转的时间。这是因为webViewDidFinishLoad方法是在一个Frame(可以是HTML或者iFrame)完全加载完(包括CSS/JS等)之后才被调用的,所以会出现网页已经渲染但是JS请求还在的情况正在执行,反映在用户端。也就是能看到网页但是菊花还在转。如果这种情况持续时间过长,会引起用户的不耐烦,但比纯白屏时间更容易接受。同时我们也可以确认,如果网页已经加载完毕,但是JS请求还在继续,这是广告商网页质量不好造成的。损失应该由他们承担,我们也无能为力。长时间白屏是我们应该重点关注的问题。小结其实这里的分析已经可以汇报给领导了。网络加载时间分为三个阶段。第一期是本地处理时间,在性能上比较浪费,但是比较稳定。第二段是网页的白屏时间。在此期间,系统的UIWebView正在请求资源并进行渲染。第三段是加载网页后的菊花旋转时间,一般用的时间比较少,我们无法控制。我们也知道UIWebView提供的API非常少。从请求开始到网页加载结束,完全是黑盒模式,几乎无从下手。但作为一个有抱负、有理想、有抱负、有本事的程序员,又怎么可能轻易放弃呢?WireShark客户端在调试网络时最常用的工具是Charles,但它只能调试HTTP/HTTPS请求,TCP层无能为力。要了解HTTP请求过程的细节,就必须借助一个更强大(当然也更复杂)的武器,这就是本文的主角WireShark。一般来说,越是强大的工具越是丑陋,WireShark无一例外地有着令人迷惑的外观。但是不要着急,我们不需要很多东西。上方红色方框内的蓝色鲨鱼标志表示开始监控网络数据,红色按钮可以猜到是停止录制。与Charles只监听HTTP请求不同,WireShark可以调试到IP层甚至更多细节,所以它的数据包也比较多,几秒内就会被上千个请求淹没,所以建议用户控制稍微监控一下的时长,或者我们可以在第二个红框里面输入过滤条件,减少干扰,下面会详细介绍。WireShark可以监控机器的网卡或者手机的网络。使用WireShark调试真机时,无需连接代理,直接通过USB连接电脑即可,否则无法调试4G网络。我们可以使用rvictl-sdeviceUDID命令来创建虚拟网卡:rvictl-s902a6a449af014086dxxxxxx346490aaa0a8739当然,查看手机的UDID还是比较麻烦的。作为一个懒人,没有命令行怎么行呢?仪器-s|awk'{print$NR}'|sed-n3p|awk'{printsubstr($0,2,length($0)-2)}'|xargsrvictl-s这样只要手机连接上就可以直接获取到UDID。运行命令后,会看到rvi0虚拟网卡创建成功的提示,双击rvi0这一行即可。我们主要关注抓包界面中的两个内容。上面红色的大框是数据流向,包括TCP、DNS、ICMP、HTTP等协议,颜色五颜六色,绚丽多彩。一般来说,黑色的内容表示遇到了错误,需要重点关注,其他内容是辅助理解。反复调试几次后,基本可以记住每种颜色的含义。下面的小红框主要是某个数据包的详细数据,会根据不同的协议层进行划分。比如我选择的99号数据包是一个TCP数据包,它的IP头和TCP头可以清楚的看到部和TCPPayload。必要时可以对这些数据进行更详细的分析,但一般不需要关注。一般来说,一个请求的数据包会很大,可能有几千条。如何找到自己感兴趣的请求,我们可以使用上面提到的过滤功能。WireShark的过滤使用了一套自定义的语法。不熟悉的就需要上网查一下,或者用自动补全功能“看懂单词”。由于我们要查看HTTP请求的具体细节,所以首先要找到请求的URL,然后使用ping命令获取其对应的IP地址。这种做法一般是没有问题的,但不排除某些域名会被优化,比如不同的IP地址在请求DNS解析时返回不同的IP地址,以保证下载速度。也就是说,手机端的DNS解析结果与电脑端的解析结果并不总是一致的。在这种情况下,我们可以通过查看DNS数据包来确定。例如从图中可以看出,域名res.wx.qq.com解析了大量的IP地址,但实际使用的只有前两个。解析地址后,我们可以做简单的过滤,输入ip.addr==220.194.203.68:这样只会显示与220.194.203.68主机的通信。注意红框中的SourcePort,这是客户端端口。我们知道HTTP是支持并发请求的,不同的并发请求必然占用不同的端口。所以图中看到的上下数据包不一定是请求和响应的关系。它们可能属于两个不同的港口,彼此没有任何关系,但它们恰好在时间上最接近。如果只想显示某个端口的数据,可以使用:ip.addr==220.194.203.68和tcp.dstport==58854。如果只想看HTTP协议的GET请求和响应,你可以使用ip.addr==220.194.203.68和(http.request.method=="GET"||http.response.code==200)来过滤。如果想看丢包的数据,可以使用ip.addr==220.194.203.68and(tcp.analysis.fast_retransmission||tcp.analysis.retransmission)以上是我在调试过程中比较常用的命令,仅供参考。有兴趣的读者可以自己抓包实验,就不一一贴图了。Case1:DNS解析多次抓包后,我开始分析那些长时间白屏网页对应的数据包,发现了很多问题,比如这里:可以清晰的看到一串黑色的错误信息,但是如果你要调试这些数据包,然后掉入陷阱。DNS是基于UDP的协议,不会有TCP重传,所以这些黑数据包肯定是之前丢包的重传,不用担心。如果只看蓝色的DNS请求,会发现连续发送了几次请求都没有响应,直到12s才获取到解析的IP地址。从172.24开头的DNS请求的接收地址可以看出这是一个内网DNS服务器,不知道为什么卡了半天。Case2:握手响应延迟下图是一个典型的TCP握手场景。同时也可以看到,第一张图的SYN握手包发出后,过了一秒才收到ACK。当然原因也不清楚,只能解释为网络抖动。然后在4G网络下又抓到了一个包:这次的事情更离谱。第二秒发送的SYN握手包反复丢失(也可能是服务器没有响应,或者ACK丢了)。通过SYN包。更有趣的是,看一下TSval,它代表发送数据包时的时间戳。当我们观察这些值时,我们会发现前几次的间隔时间是1s,后来变成了2s、4s、8s。这让我想起了RTO的概念。我们知道RTT表示的是从发起网络请求到收到响应的时间,是一个随网络环境动态变化的数值。TCP有窗口的概念,对于窗口的第一个数据包,如果不能发送,窗口就不能回滑。客户端将收到ACK视为数据包发送成功的标志,那么收不到ACK怎么办?当然,客户端不会一直等下去,它会设置一个超时时间,一旦超过这个时间,就认为数据包丢失,重新开始。经过。这个超时称为RTO,显然必须比RTT略大,否则会误报丢包。但是也不能太大,不然会浪费时间。因此,合理的RTO必须跟随RTT动态调整,始终大于RTT但不能太大。看上面的截图,可以看出在某些情况下RTT很小,小到几毫秒。如果RTO也设置成几毫秒,就不合理了,会增加客户端和一路上路由器的压力。所以RTO也会设置一个下限,不同的操作系统可能会有不同的实现,比如在Linux上是200ms。同时,RTO也会设定一个上限。具体算法可以参考本文和本文。需要注意的是,RTO是随RTT动态变化的,但是如果达到RTO,导致超时重传,以后的RTO将不再随RTT变化(此时的RTT无法计算),会增加呈指数增长。这就是为什么上面截图中的间隔时间从2s变为4s再变为8s的原因。同样,我们发现握手耗时20s,但我们也无法给出确切原因,只能解释为网络抖动。小结通过TCP层面的抓包,我们不仅学习了WireShark的使用,还回顾了TCP协议的相关知识,对问题进行了更深入的分析。从最开始的网络问题开始,开始细化挖掘,得出白屏时间过长,网页加载过慢的结论,最后计算了多少HTTP请求,DNS解析,TCP握手,TCP数据传输等阶段时间。由此看来,网页加载慢的罪魁祸首不是广告商网页的质量,而是网络的不稳定。虽然最后没有得到有效的解决办法,但至少弄清了问题的原因,给出了有说服力的解释。