经常有人在群里问:iOS 端网络出问题,能不能像服务端那样直接 tcpdump 一把梭?问的人多了,我发现大家真正想要的是——在整个 iOS App 的开发调试和线上问题排查里,有一套能拿到 TCP、UDP 数据包、并能看懂数据的完整方法。说实话,这件事在 iOS 上比在安卓和 Linux 上麻烦不少,但它又是联调、定位弱网问题、排查服务端“甩锅”时最硬核的证据。这篇文章就把我这几年在 iOS App 上做数据包抓取的实操经验整理出来,适合所有做 iOS 原生开发、又得和网络问题死磕的工程师。
先泼一盆冷水:iOS 不是 Linux,你不能随手开个 root shell 在设备上跑 tcpdump,也不能像安卓那样拿个抓包软件就能看到所有 UDP。但也不是完全没辙。通过 Mac 端的虚拟网卡、系统自带的网络诊断日志,再配合 Wireshark 这一层的分析工具,TCP 和 UDP 的包都能完整拿到。这篇文章不会只给你几条命令,我会把“为什么这样抓”“抓到之后怎么看”“遇到 HTTPS 怎么办”“线上偶发超时怎么定位”这几个问题串起来,尽量让你少走弯路。
1. 先厘清需求:iOS 抓 TCP/UDP 包,到底在什么场景下才是必要动作
1.1 哪些问题非抓包不可
先说个常见的尴尬场景:后端说“我这边日志显示请求根本没到”,客户端同事说“我明明发了请求”,两边都觉得自己没错。
这种纠纷如果不看网络层的真实数据,基本靠猜。而抓包的意义就在于:把双方“认为发生了什么”替换成“网络里实际发生了什么”。比如 TCP 三次握手是否完成、SYN 发出后有没有回 ACK、请求体是在哪个分片丢的、UDP 包是不是发出去就石沉大海——这些信息,业务日志里不会写,SDK 的报错回调也不一定准。
我归纳了一下,真正值得抓包的场景大概有三类:
- 联调排错:接口通了但结果不对,或者时通时不通,需要区分是客户端没发出去、中间链路丢了、还是服务端没回。这类问题抓 TCP 最有效。
- 弱网和音视频体验问题:直播、语音、实时数据同步这类场景,UDP 是主力。用户反馈“卡顿”“断字”“不同步”的时候,业务层很难说清楚是丢包、乱序还是抖动。只有抓到原始报文,用应用层序号和时间戳推算,才能定位。
- 安全和隐私自查:App 里到底有没有偷偷发明文?证书校验到底有没有生效?哪些域名走了 IPv6?这类问题不抓包,光看代码容易漏。
新手容易犯的错是一拿到工具就开抓,然后被成百上千条包淹没。所以动手之前,先想清楚:你是要确认“连不连得上”,还是要分析“报文内容对不对”,还是要判断“网络质量差在哪”。目标不同,抓包的位置、过滤条件和分析方式完全不一样。
1.2 TCP 和 UDP 在排查思路上是两码事
这一点我每次带人的时候都会反复强调:TCP 和 UDP 的抓包分析,思维方式根本不同。
TCP 是面向连接的可靠流协议。它自带序号(Sequence Number)、确认号(Acknowledgment Number)、状态标志(SYN/ACK/FIN/RST)。所以你看 TCP 包,本质上是在看一台状态机:三次握手建立连接、数据传输、四次挥手关闭连接,每一步都有明确的报文。包丢了会有重传,包序错了会有乱序提示,对端没收到会有重复 ACK。你可以非常清晰地得到结论:这次连接到底建没建成,卡在哪个阶段,谁先发的 RST。
UDP 就不一样了。它没有连接概念,没有确认机制,也不保证顺序。你抓到的就是一个个独立的数据报,网络层能告诉你的只有源地址、目的地址、源端口、目的端口、包长度和时间戳。丢没丢包、乱没乱序,协议层一概不知。这时候就得借助应用层的信息:很多 UDP 协议会在 payload 里放递增序号、时间戳、帧标记,你要靠这些字段推算丢包率和抖动。
还有个容易被忽略的点:TCP 的抓包分析可以精确到“字节的可靠性”,UDP 只能精确到“报文的存在性”。所以用 UDP 做核心业务的时候,应用层必须自己设计序号和确认机制,否则出了问题连从哪查起都不知道。
1.3 iOS 平台的特殊约束
在 iOS 上抓包,你还要先接受三件事。
第一,设备侧权限有限。非越狱环境拿不到 root,不能在 iPhone 本机直接监听所有网卡流量。最实用的办法是把流量引导到 Mac 上再抓,后面会详细讲。
第二,系统层 TLS 保护越来越严。从 iOS 10.3 开始,安装的根证书默认不被完全信任,必须到“设置 → 通用 → 关于本机 → 证书信任设置”里手动开启。如果 App 再做证书固定(SSL Pinning),中间人解密这条路基本就断了,只能靠代码层日志。
第三,现在的网络是 IPv4/IPv6 双栈。你可能只关注了域名和端口,但实际流量可能走了 IPv6,过滤条件没写对就会漏掉一半包。我见过不少同事在 Wireshark 里只写tcp.port == 443,结果 IPv6 的流量一条都显示不出来,白白折腾半天。
2. 工具选型的真实边界:从 Wireshark 到 Xcode 自带诊断,怎么组合不折腾
2.1 三层抓包方案:设备侧、链路侧、协议侧
我习惯把 iOS 抓包方案分成三层,按需选择:
- 链路侧:在 Mac 上用 Wireshark/tcpdump 捕获网卡流量。覆盖面最广,能看到设备离开 App 之后的所有真实网络行为。适合定位 TCP 连接问题、MTU 问题、DNS 解析问题。
- 设备侧:通过 Xcode 的 Network 调试能力或 Charles 这类工具,在 App 进程附近做流量中转与解密。适合看 HTTP/HTTPS 请求内容、请求头、Cookie、接口响应。
- 协议侧:在 App 代码里用 URLSession 的 taskMetrics、Network.framework 的 NWConnection 状态回调,把自己关心的网络指标打点输出。适合线上问题、难以复现的问题、以及加了证书固定的 App。
实际项目中,这三层要配合用。比如你要定位一个 HTTPS 接口偶发超时,先用设备侧确认请求内容没问题,再用链路侧确认 TCP 握手、TLS 协商、数据传输在哪一步断了。单靠一层,经常会得出片面的结论。
2.2 可视化工具擅长与不擅长的部分
很多人一上来就问:Wireshark 和 Charles 到底选哪个?这其实是在问两个不同维度的问题。
Wireshark是协议分析工具,它的主场是 TCP/IP 协议栈。它能把一条 TCP 流从头到尾重组出来,标出重传、乱序、重复 ACK、零窗口,还能分析 TLS 握手细节和 DNS 响应时间。但不擅长做 HTTPS 内容的“人话”展示,解密需要额外配置证书,而且配置失败率其实挺高。
Charles是英文开发调试工具,它的主场是 HTTP/HTTPS 的请求响应。装好证书后,你能直接看到某个接口的 URL、Header、Body、响应码,还能断点修改请求。但它的底子是 TCP 之上的 HTTP 解析,UDP 流量基本看不见,底层 TCP 重传、丢包这类信息也完全无能为力。
所以我的习惯是:看业务内容用 Charles,看网络行为用 Wireshark。如果你只对“接口返回了什么”感兴趣,Charles 就够了;如果你要解决“网络为什么慢、为什么断”,请打开 Wireshark。
顺便说一句,服务端老同事最爱的 tcpdump 在 iOS 开发里也不是没用,只是跑在 Mac 上。它轻量、无界面、适合在终端里快速抓一小段 pcap 文件再丢给 Wireshark 分析。后面讲 RVIT 的时候会用到。
2.3 用 USB 虚拟网卡捕获完整 iOS 流量(RVIT)
这是我这几年用下来最顺手的方案,也是 iOS 官方支持的做法——通过 USB 让 Mac 生成一个虚拟网卡,把 iPhone 的流量在 Mac 上完整镜像出来。苹果自带工具叫 Remote Virtual Interface,命令行工具是rvictl。
具体步骤很简单:
- 准备好 iPhone 和 Mac,用数据线连接。
- 在 Mac 上打开终端,确认设备已被识别,用
xcrun xctrace list devices查看设备 UDID。 - 执行
sudo rvictl -s <UDID>,这时候系统会创建一个名为rvi0(或rvi1等)的虚拟网卡。 - 打开 Wireshark,选择
rvi0接口开始抓包,或者用sudo tcpdump -i rvi0 -s 0 -w ios.pcap先存文件。 - 用完执行
sudo rvictl -x <UDID>删除虚拟接口。
这个方案的优点非常明显:第一,不需要手机越狱,不需要装任何插件;第二,抓到的流量是设备真实的网络栈输出,包含 TCP、UDP、ICMP、DNS 等所有报文,而不仅仅是 HTTP 层;第三,手机走的 Wi-Fi 或蜂窝流量都会经过这个虚拟网卡,观察面很全。
要注意的是,抓包期间设备要保持 USB 连接并且信任这台 Mac。另外,rvi0上抓到的流量并不包含所有系统内部通信,但对 App 自身发起的网络请求来说,完整度已经足够日常排查了。
3. 实操演示:从零抓住第一条 TCP 流和 UDP 包
3.1 配好过滤条件,避免被噪声淹没
拿到rvi0接口之后,第一件事不是点开始,而是先把过滤条件想清楚。我见过太多人打开 Wireshark 就抓,三秒钟抓了几万条包,然后完全不知道从哪里看起。
我的习惯是这样:先确定要分析的目标 App 的服务端域名或端口,然后在抓包工具里只保留和它相关的流量。Wireshark 的过滤语法很简单,比如:
ip.addr == 192.168.1.100 && tcp——只看该 IP 的 TCP 包。tcp.port == 443——只看 443 端口。udp.port == 8000——只看某 UDP 端口的包。- 也可以用域名过滤:
dns.qry.name contains "example.com"先找到实际解析出的 IP,再切回 IP 过滤。
不要一上来就用tcp或者直接不填过滤条件。Wi-Fi 环境下会混入大量局域网广播、其他设备的流量,过滤得当能让你少看 90% 的噪声。
另外,抓包文件的命名也建议规范。我一般按“日期-场景-方向”来命名,比如20250112_upload_timeout.pcapng。排查线上问题时,一般要抓好几轮,命名不清后面根本对不上号。
3.2 TCP 连接过程怎么看:三次握手、重传、乱序
现在假设你已经抓到了一条完整的 TCP 流。打开 Wireshark,找到第一条SYN包,看整个连接的过程。
正常情况下,你会看到:
192.168.1.5 -> 203.0.113.10 TCP SYN 203.0.113.10 -> 192.168.1.5 TCP SYN+ACK 192.168.1.5 -> 203.0.113.10 TCP ACK三次握手完成。接下来就是数据传输阶段。这一步不要逐条看 payload,先看 Wireshark 给你标的异常指示:TCP Retransmission、TCP Dup ACK、TCP Out-Of-Order、TCP ZeroWindow。
- 重传(Retransmission):说明有包丢了或对端没确认,网络质量差或者服务端处理不过来。
- 重复 ACK(Dup ACK):对端反复要求重发同一个序号,说明中间丢了包。
- 乱序(Out-Of-Order):包到达顺序和发送顺序不一致,常见于多路径、Wi-Fi 切换等场景。
- 零窗口(ZeroWindow):对端缓冲区满了,TCP 层在“喊停”,这不是网络问题,是应用层消费速度跟不上。
我一般会先用“统计 → 流量图”或者右键“追踪流 → TCP 流”把整条流可视化,再点开异常包看具体是哪个序号段的。判断根因的时候,第一步永远是看异常集中在哪个阶段:握手阶段、TLS 协商阶段、还是数据传输阶段。阶段不同,排查方向完全不同。
新手最容易犯的错是:看到一条重传就觉得是“网络丢包”,其实可能是服务端设置了很小的接收窗口、客户端一次发太大、或者 MTU 过大导致分片被中间设备丢弃。这些都要结合包长和窗口大小一起看。
3.3 UDP 只看端口和时间戳,如何推断丢包与抖动
UDP 抓包比 TCP 看起来“清爽”很多,没有握手也没有重传,但看的人往往更懵——看起来一切正常,但用户就是卡。
我自己的分析套路是四步:
- 建基线:先用 Wireshark 的“统计 → 会话”看这个 UDP 对端的包数量、字节数,建立一个正常量级。
- 看序号连续性:大部分真实业务 UDP 协议会在 payload 里带序号。筛选出协议字段,按顺序拉出来看。如果出现 1、2、3、5、6 这种跳跃,掉的那一个序号大概率就是丢了。有时候序号是连续的但时间戳间隔突然变大,那就是抖动。
- 看时间戳间隔:在 Wireshark 里把
frame.time_delta字段显示出来,如果相邻包的间隔从正常的 20ms 突然变成 500ms,再结合序号没有丢失,说明是网络延迟增大,不是丢包。 - 比对客户端和服务端两个方向:双向都看一下,确认丢包是“上行丢”还是“下行丢”。这决定是让用户换网络、改 App 发送策略,还是服务端加大缓冲区。
UDP 抓包有个天然局限:你只能看到“发出的包”和“收到的包”,没法知道中间丢了多少。所以最终结论往往用“推测+交叉验证”的方式给出:序号跳跃说明某处丢了包,时间戳间隔异常说明网络抖动,接收端的应用层日志里如果正好有“等待超时”的记录,证据链就闭合了。
4. HTTPS 流量怎么开:证书安装、信任与双向认证的坑
4.1 让抓包工具能解开 HTTPS 的标准步骤
绝大多数业务现在都是 HTTPS,如果你只能在 Wireshark 里看到一堆 TLS 加密数据,那对排查业务问题基本没用。所以我需要单独讲一下怎么让数据“看得懂”。
最常用的方法是用 Charles 这类工具做中间人解密的 TLS 转发。大致流程是:
- 在 Mac 上启动 Charles,开启 HTTPS 解密功能,并安装并信任 Charles 的根证书。
- iPhone 连上 Mac 所在的局域网,Wi-Fi 设置里把 HTTP 流量转发指向 Mac 的监听端口。
- 用 iOS 的 Safari 访问
chls.pro/ssl,下载并安装根证书。 - 到“设置 → 通用 → 关于本机 → 证书信任设置”,把 Charles 根证书的“完全信任”打开。这一步 iOS 10.3 以上必做,漏了这一步会提示证书无效。
- 在 Charles 里为需要解密的域名勾选 SSL Proxying,重新发起请求,就能看到明文了。
这里要特别提醒:不要为了图方便全局开启 SSL Proxying。一是性能影响很大,二是很多 App 会检测到中间人并直接异常退出或发送假数据。我通常只在需要排查的域名上开,排查完立刻关掉。
4.2 遇到证书固定与双向认证时怎么办
很多金融、出行类 App 会做证书固定,也就是在客户端内置服务端证书指纹,校验不匹配就断开连接。这种情况下,Charles 就算装了根证书也解不开,因为 App 根本不认它。
遇到证书固定,我一般会放弃中间人解密,改用三层方案里的“设备侧 + 协议侧”:
- 关闭 HTTPS 解密,只在 Wireshark 里看 TLS 握手是否成功、用了哪个密码套件。
- 在代码里用 URLSession 的
taskMetrics把连接建立耗时、TLS 握手耗时、请求发送耗时打点输出。 - 遇到双向认证(客户端证书)的场景,抓包工具里就算导入了客户端 p12 也常常不好使,因为客户端证书的私钥保护和使用时机都由系统安全模块管理。这种时候老老实实加日志,比折腾抓包工具更高效。
一句话总结:抓包工具在 HTTPS 场景下能解的是“信任该证书”的流量,解不开的是“不信任任何额外证书”的流量。区分清楚这个边界,能少浪费很多时间。
5. 一次线上偶发超时问题的完整排查链路
5.1 从一条超时日志开始的证据链
第 3 章讲的都是“怎么抓到”,这一章用一个真实复盘把整个过程串起来。当时的情况是:某项目组反馈,iOS 端用户在上传文件时偶发报错,提示“请求超时”,Android 端同样网络环境下却基本正常。一开始大家怀疑是服务端问题,因为同一个接口,客户端日志显示“请求已发送”。
我接手之后没有直接上 Wireshark,而是先做了一件事:让项目组把超时发生时刻的客户端日志、设备网络状态、服务端访问日志全部拉出来对齐。结果发现,服务端在那个时间窗口里根本没有收到任何请求记录,说明问题大概率出在“客户端发出请求”到“请求到达服务端”这一段链路。
这时候才决定上抓包。由于超时是偶发的,直接在用户设备上抓包不现实,我们采用了可复现方案:在测试机上跑同样的上传流程,用 RVIT 把 iOS 虚拟网卡挂到 Mac 上,持续抓包,等待问题出现。大概跑了半个多小时,终于在一次弱网模拟下复现了超时。
5.2 抓包验证与条件收窄
抓到包后,我先把过滤条件设为目标服务端 IP 和tcp,然后看 TCP 流的整体形态。结果发现,三次握手都是成功的,TLS 协商也完成了,问题出在数据传输阶段:
- 出现了大面积的
TCP Retransmission,集中在几个比较大的 segment 上。 - 很多包标注了
TCP Dup ACK,但连接没有立即断开。 - 我们用的测试网络环境是 Wi-Fi,此时丢包率明显升高。
把这些异常和包长度放一起看,发现上传请求的数据段普遍超过了一个阈值,部分分片在弱网环境下容易被丢弃。于是结论逐渐清晰:问题不是服务端不响应,而是客户端一次性发送的数据量过大,在弱网 Wi-Fi 上分片后被丢弃,触发重传和超时。
随后我们又对比了蜂窝网络下的抓包结果,同样条件下问题复现概率大幅下降。这进一步支持了“大包在弱网丢包”的判断——因为蜂窝网络的分片和调度策略与 Wi-Fi 不同。
5.3 根因、代码改动与二次验证
定位到根因之后,改动其实不大:
- 在上传逻辑里把大文件切成分片,单次请求的数据量降到底部。
- 增加应用层重试机制,失败后指数退避重试。
- 在弱网环境下主动降低并发数,避免多条流同时重传加剧拥塞。
改动上线后,我们在模拟弱网环境里再用 RVIT 抓包验证,确认重传率明显下降,超时问题不再复现。整个排查链路也就闭环了。
这个案例最值得记住的并不是某一条命令或某个过滤条件,而是排查的顺序:先由日志和数据确认问题发生的环节,再用抓包验证网络层的真实行为,最后让代码改动去回应网络行为。没有第一步,直接抓包很容易被海量数据淹没;没有第二步,改动就是在猜。
6. 把抓包能力沉淀成日常工程实践
6.1 在 URLSession 层建立可观测指标
抓包厉害,但你不能每次都靠抓包解决所有问题。尤其线上用户环境千奇百怪,你不可能一个个去抓。我这几年的心得是:把抓包思维下沉到代码里,在 URLSession 层建立一套“准抓包”的可观测指标。
具体做法很简单。iOS 的 URLSession 提供了taskMetrics回调,里面包含了完整的请求生命周期:DNS 解析开始/结束时间、TCP 连接开始时间、TLS 握手开始/结束时间、首字节时间、总耗时。把这些时间点打点上报,线上问题即使不抓包,也能很清楚地看到瓶颈在哪。
我通常会把以下指标沉淀成一条网络日志:
- DNS 解析耗时(判断本地 DNS 是否异常)
- TCP 连接建立耗时(判断网络连通性)
- TLS 握手耗时(判断证书校验和协商速度)
- 请求发送耗时、响应首字节耗时(判断服务端处理速度)
- 是否发生连接复用、是否走了 IPv6
- 网络类型(Wi-Fi / 蜂窝 / 运营商)
日志里要脱敏,token、手机号、请求体一律不写。只记录时间点和状态码这些“网络证据”。这套东西做好之后,很多线上问题根本不用抓包,看指标就能定位到八九不离十。
6.2 自动化回归与日志脱敏
如果你所在的团队有自动化测试,可以更进一步:在回归环境里跑关键业务流程时,用 RVIT 抓固定时长的包,自动分析这几个指标:
- 有没有非预期的
RST包。 - HTTP 请求有没有走明文。
- TCP 重传率是否超过阈值。
- UDP 包是否存在大量乱序。
我在实际操作中会写一个简单的脚本,用tshark读取 pcap 文件,统计上述指标,输出成一份简短报告。这个报告不是给人逐条看的,而是用来“报警”:连续几条指标异常,就说明最近一次的改动可能影响了网络层行为,需要人工介入。
日志脱敏这事再强调一遍。抓包里经常能看到完整的 token、session、甚至是用户头像的 URL。把这些数据落到本地文件或者上传到日志系统,都是有隐私风险的。我自己的原则是:能不打日志就不打,必须打就只留脱敏后的信息,比如只保留 URL path,不保留 query 参数。
6.3 什么情况下不该依赖抓包
最后得说点反直觉的东西:不是所有问题都该用抓包解决。
抓包解决的是“网络协议层的行为”,也就是“包发没发出去、发到哪了、对端怎么回的”。但如果你遇到的是“UI 卡顿”“内存上涨”“数据库读写慢”“业务逻辑条件分支错误”这类问题,抓包根本帮不上忙,硬抓只会浪费时间。
我见过的最典型低效场景是:一个按钮点了没反应,开发同学不去看业务日志,先去抓包,抓到发现根本没有网络请求——然后呢?还是要回头查代码。所以我的习惯是:先问自己“这个问题是否涉及跨设备的信息传递”,如果不涉及,就别抓包;如果涉及,再确定是 TCP 连接问题还是 UDP 内容问题,然后选择对应的抓包方案。
6.4 一个我现在仍在用的检查习惯
项目上线前,我会固定跑一轮“流量巡检”:在测试机上走一遍冷启动、登录、核心业务、退出登录的流程,同时在 Mac 上用tcpdump -i rvi0 -w pre_release.pcap把全程包抓下来。然后快速扫一眼:
- 有没有域名解析失败。
- 有没有多次 TLS 重协商。
- 有没有明显超过基线水平的重传。
- 有没有意外发出的明文 UDP 流量。
这套巡检我已经坚持了挺长时间,确实提前拦下过不少问题。抓包本身不难,难的是把它变成一种下意识的工作习惯。工具是放大器,真正值钱的是你对 TCP 状态机、对 UDP 语义、对 App 自身网络模型的判断力。把那套判断力练起来,以后再遇到“我也不知道为什么就超时了”的问题,心里会稳稳地有数。