我一直觉得,搞 Wi-Fi 通信的开发者,手里不能只有串口日志和 AT 指令回显。很多时候设备报“连接失败”或者“数据发不出去”,从日志里看一切正常,但真正的问题出在空气里——电磁波上承载的那些 802.11 帧说了什么,你根本不知道。这次这篇“WIFI通信协议全解析14”,就用 Wireshark 把 ESP32 的通信数据原封不动嗅探出来,带你看懂“WiFi 的语言”。这不是什么高端黑客技巧,而是嵌入式开发和网络调试的基本功,适合玩 ESP32、做 IoT 项目、或者单纯想搞懂 Wi-Fi 协议栈的开发者参考。1. 为什么说嗅探是读懂 WiFi 语言最直接的方式1.1 空口数据与传统抓包的本质区别很多人一开始会问:我电脑上装个 Wireshark,直接就能抓 Wi-Fi 包吗?能抓,但抓到的不是“WiFi 的语言”,而是“网卡处理完之后吐给你的翻译稿”。常规模式下,无线网卡驱动会先做一层筛选:不是发给本机的帧直接丢掉,是发给本机的帧才把 802.11 头部剥掉,再把里面的 TCP/IP 数据包往系统协议栈里送。Wireshark 看到的已经是剥离了 WiFi 帧头的数据,相当于你只看到了快递里的商品,没看到快递单上写了什么。而空口嗅探完全不同。它把无线网卡切换到监听模式,让网卡放弃一切筛选和地址过滤,把物理层收到的每一个 802.11 帧都完整保存下来。这里面包括:Beacon 帧——AP 每 100ms 左右广播一次,“我是谁、我在哪个信道、支持什么速率”;Probe Request / Response——设备主动扫描时喊一嗓子“这附近有网络吗”,AP 回应“有,你连我吧”;Association Request / Response——设备决定加入某个 AP 时相互确认身份和参数的对话;数据帧——真正的业务数据,但注意在加密网络里载荷是看不见的,能看的是头部信息。对于 ESP32 这类嵌入式设备,串口日志只能告诉你“我要连 router_a”,但空口抓包能告诉你:它发出 Probe Request 时到底扫描了哪些信道、AP 回了什么能力集、关联时协商了哪些参数、后续每帧数据相隔多久。这些东西才是定位 Wi-Fi 通信问题的一手证据。1.2 Wireshark 为什么是首选工具同类工具不是没有,比如 Omnipeek、tcpdump 配合 monitor 模式也能干,但 Wireshark 在“看懂”这件事上做得最好,具体有三点:第一,它对 802.11 协议族的解码非常完整,管理帧里的每一个 Tag(SSID、Supported Rates、HT Capabilities、802.11ax 的 HE 字段等)都能按字段拆开,鼠标点一点就能看每个 bit 的含义。第二,它有一套强大的过滤语言,可以快速从一整包文件里筛出你要的那台 ESP32 的帧。第三,它是纯图形界面,交互式展开、时间序列分析、IO 图表这些功能对新手非常友好。不过我要说句公道话:Wireshark 的解码能力强,不代表它是万能的。它只是个解剖台,把帧拆开给你看,但“为什么会出现这个帧”“这个帧说明什么问题”,依然需要你对 802.11 协议本身有基本了解。也就是说,Wireshark 帮你“看见”,但“看懂”还得靠你自己。1.3 抓包方案的总体设计思路针对 ESP32 这个对象,我的抓包方案一般分三种场景,清晰区分能达到的目的:场景 A:ESP32 作为 Station 连接附近的路由器。这时抓包能看到完整的扫描、关联、加密握手、正常通信流程。适合排查“连不上 Wi-Fi”“频繁掉线”“数据发送延迟大”这类问题。场景 B:ESP32 作为 SoftAP,自己发射热点。这时抓包能看到 ESP32 自己发出去的 Beacon 帧和管理帧,非常直观,适合做协议教学和参数验证。场景 C:两台 ESP32 之间做 Wi-Fi 直连或组网,配合抓包分析它们之间的管理帧交互。这篇文章里的实操部分,我会同时覆盖场景 A 和场景 B,因为这两个场景最容易上手,也最能帮你建立对“WiFi 语言”的直观认知。2. 抓包前的环境准备:网卡、驱动与监听模式2.1 无线网卡选型与监听模式原理要监听空口数据,第一道坎是网卡。普通笔记本内置网卡能不能开监听模式?部分可以,但多数 Windows 驱动不支持,macOS 更是基本没戏。我的经验是:亲手试过才最稳。我建议选用带 USB 接口的外置无线网卡方案。这类网卡驱动在 Linux 下相对容易切换监听模式,对 802.11 帧的保留也比较完整,包括前面我提到的管理帧、控制帧、数据帧都能收到。核心硬件选型思路是:优先找那些在开源驱动列表里标注支持 monitor mode 的芯片方案,获取方式不做具体推荐,但市面上常见 USB 无线网卡大多都有对应方案。我平时备两片网卡:一片是自用的普通连接网卡,一片专门拿来监听。原因很简单——监听时网卡不能同时正常上网,专门备一块不影响日常开发。提示:监听模式和普通模式的区别,最核心的就是网卡驱动是否执行地址过滤。普通模式下,驱动只把目的地址匹配本机或组播/广播的帧上报;监听模式下,网卡把射频前端收到的所有帧全部上报。所以监听模式也叫 RFMON,Radio Frequency Monitor,它更像是“无线电收音机”,不是“对讲机”。2.2 三步开启监听模式(以 Linux 环境为例)相对省心的环境是 Linux,多数发行版对无线网卡的监听模式支持比较完善。以下命令需要 root 权限,操作前确保网卡没有被系统网络管理服务占用。第一步,确认接口名。插上网卡后运行iw dev,输出里能找到类似phy#0、Interface wlan0这样的信息,记住接口名,后面全用这个名字。第二步,切换监听模式。执行:sudo ip link set wlan0 down sudo iw dev wlan0 set monitor none sudo ip link set wlan0 up这里有一个细节:iw dev wlan0 set monitor none后面的none表示不附加任何过滤参数。有些网卡驱动支持fcsfail、control等额外参数,新手不建议加,先保证能干净地收到帧再说。第三步,固定信道。Wi-Fi 在 2.4GHz 频段有多个信道,网卡在同一时刻只能监听一个信道。先知道 ESP32 连的路由器工作在哪个信道,然后用:sudo iw dev wlan0 set channel 6把监听信道固定住。信道不对的话,你会惊讶地发现明明设备在拼命运数据,Wireshark 里却安静得像个假抓包。如果你用的不是 Linux,Windows 下要额外安装对应网卡的抓包驱动,macOS 则基本只能靠特殊方式处理,不太适合入门。所以我的建议是:新手做这个实验,优先整个 Linux 环境,最简单的方案是配一台小型 Linux 主机或虚拟机加 USB 网卡直通。2.3 Wireshark 关键显示设置启动 Wireshark,在接口列表里选择刚才进入监听模式的网卡接口(通常是 wlan0 或者以 mon 开头的虚拟接口),双击开始抓包。看到满屏幕的 Beacon 帧就说明监听成功了——周围所有 AP 的广播都进了你的抓包文件,这就是监听模式和普通模式一眼就能看出来的差异。不过这时候你看到的管理帧可能是乱糟糟的一堆,因为 Wireshark 默认会用普通的以太网解析逻辑去解析 802.11 帧头,偶尔会出现解析不对的情况。如果看到帧头字段明显异常,可以右键那条记录,选择“Decode As”,把协议强制指定为 IEEE 802.11,网络层还是保留原来的 IPv4 或 IPv6。接下来顺手做两个设置,能让后面省很多事:显示列调整:右键任意列头,添加Source和Destination列,这样每条记录的源和目的 MAC 地址能直接显示出来。在空口抓包里,这两个字段对定位设备非常关键。时间显示格式:进入 View -> Time Display Format,改成Seconds Since Previous Captured Frame。后面分析设备是否规律发包时,这个时间差列一眼就能看出节拍对不对。做完这些,抓包环境就绪了。下面进入真正的实操环节。3. 完整实操:用 Wireshark 抓取并看懂 ESP32 的通信全程3.1 最小实验场景搭建为了让抓包过程可控,我搭了一个最小实验台:一个 ESP32 开发板,烧录一个最简单的 TCP Client Demo;一台家用无线路由器(关掉其它无关终端混入干扰),以及监听用的 USB 网卡 + Wireshark。烧录之前,先在程序里把 ESP32 的工作模式配置清楚,我这里给出一种常见的初始化结构:#include <string.h> #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "esp_wifi.h" #include "esp_event.h" #include "esp_log.h" #include "nvs_flash.h" static const char *TAG = "wifi-sniffer-demo"; static void wifi_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data) { if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) { esp_wifi_connect(); } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_CONNECTED) { ESP_LOGI(TAG, "connected to AP"); } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) { ESP_LOGI(TAG, "disconnected, retrying..."); esp_wifi_connect(); } } static void wifi_init_sta(void) { wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); esp_wifi_init(&cfg); esp_event_handler_instance_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &wifi_event_handler, NULL, NULL); wifi_config_t wifi_config = { .sta = { .ssid = "DEMO_AP", .password = "demo_password", .threshold.authmode = WIFI_AUTH_WPA2_PSK, }, }; esp_wifi_set_mode(WIFI_MODE_STA); esp_wifi_set_config(WIFI_IF_STA, &wifi_config); esp_wifi_start(); } void app_main(void) { nvs_flash_init(); wifi_init_sta(); }这段代码的核心意图是让 ESP32 只做一件事:上电后初始化 Wi-Fi 协议栈,然后以 Station 模式扫描并连接指定的 SSID。为了观察效果,我还建议在连接成功之后,周期性地向局域网内某个固定 IP 发送 UDP 包,数量不需要多,一秒一个就够,这样空口抓包里能看到明显的周期性数据帧。3.2 从复位到连接:一次完整的抓包现场记录实验环境就绪之后,我把 USB 网卡固定在 ESP32 开发板附近两米以内,固定监听信道到路由器所在信道,开始抓包。然后给 ESP32 上电复位。Wireshark 里的帧就开始噌噌往外冒,种类和顺序是一个标准的 Wi-Fi 连接流程:第一步是扫描阶段。ESP32 发出一串 Probe Request 帧,目的地址是广播地址 FF:FF:FF:FF:FF:FF,SSID 字段要么是空(表示“扫描所有网络”),要么直接写着目标 SSID 的名称(表示“精准扫描某个网络”)。我用的是“DEMO_AP”,所以能看到带有指定 SSID 的 Probe Request。第二步是 AP 回应。路由器收到 Probe Request 后,回一个 Probe Response。注意这个帧的目的地址变成了 ESP32 的 MAC 地址,里面携带了该 AP 的能力集、支持的速率、加密方式等信息。第三步是认证和关联。ESP32 发起 Association Request,表示“我确认要加入你”。AP 回应 Association Response,里面带着协商结果和一个关联 ID(AID)。到这里,管理层面的关系就建立好了。第四步是加密握手。如果网络是 WPA2-PSK,接下来会有四次 EAPOL 握手帧,抓包里过滤eapol就能看到。这个过程协商出真正的会话密钥,之后的数据帧都使用这个密钥加密。第五步是正常通信。连接完成后,ESP32 开始周期性发送 UDP 数据包。这些数据帧的特征是帧控制字段类型为 Data(Type=2),并且目的地址是路由器的 MAC 地址,而不是广播地址。我在实际操作中经常用过滤表达式直接锁定 ESP32,一条就够了:wlan.addr == 34:85:18:aa:bb:cc把aa:bb:cc替换成你手里开发板的实际 MAC 地址。Wireshark 里所有源地址或目的地址命中这个 MAC 的帧都会被选出来,形成一条独立的连接时间线。注意:MAC 地址在 ESP32 上可以用esp_read_mac()读取,也可以用esptool读 efuse 内容,或者直接在路由器后台“已连接设备列表”里找。如果你把过滤条件写错了,最常见的表现是:抓包文件里一直有流量,但过滤窗口一个帧都没有。3.3 抓到的帧怎么看:关键字段逐一拆解拿到帧之后,重点看哪些字段?我按“看帧先看头”的思路给你拆一遍。所有 802.11 帧都以一个固定头部开头,其中最重要的两个部分是帧控制字段和地址序列。帧控制字段里,Type 决定大类(管理帧、控制帧、数据帧),Subtype 决定具体用途。我给你一个最常见的速查表:类型Subtype 值用什么过滤含义管理帧8wlan.fc.type_subtype == 0x08Beacon,AP 周期性广播管理帧4wlan.fc.type_subtype == 0x04Probe Request,设备主动扫描管理帧5wlan.fc.type_subtype == 0x05Probe Response,AP 响应扫描管理帧0wlan.fc.type_subtype == 0x00Association Request,关联请求管理帧1wlan.fc.type_subtype == 0x01Association Response,关联响应数据帧0wlan.fc.type == 2普通数据帧,载荷可能加密控制帧特定wlan.fc.type == 1ACK、RTS、CTS 等地址序列里有三到四个 MAC 地址字段,分别表示接收端、发送端、BSSID 和(有些帧才有)源/目的端。在数据帧里通常能看到:接收端(RA):通常是路由器 MAC;发送端(TA):通常是 ESP32 MAC;BSSID:路由器 BSSID,基本等于 AP 的 MAC。为什么普通网络抓包看不到这几个地址?因为普通网卡驱动已经把它们剥掉只留 IP 层了。看空口抓包,这几个地址就是判断“这个帧到底是谁发给谁的”最重要的依据。再说 Beacon 帧。Beacon 非常值得单独拆一次,因为它是所有 Wi-Fi 网络“存在感”的来源。展开一个 Beacon 帧,你会看到:Beacon Interval:通常为 100ms,表示 AP 每隔这么长时间广播一次;Capability Info:包括 ESS、Privacy 等标志位,Privacy 置 1 表示网络启用了加密;Tagged Parameters:这一部分藏着 SSID 名称、支持的速率列表、信道号、HT Capabilities、Vendor Specific 字段。我用一个生活化类比:Beacon 帧相当于店铺门口循环播放的“营业中”广播。它每隔一段时间喊一次,告诉大家“这家店在,我用的是 WPA2 加密,支持这些支付方式”。而 Probe Request 就是顾客走到门口问一句“你们这里卖什么?”,Probe Response 则是店员出来说“我们卖这些,价格是这些”。对于 ESP32 的调试,最实用的信息其实在“时间”维度上。展开数据帧列表,看 Time 列的 delta 值,如果 ESP32 设定一秒发一个 UDP 包,那么相邻数据帧的间隔应该稳定在 1000ms 左右。如果出现大的抖动或者丢帧,说明空中信道拥挤、AP 队列拥塞或者 ESP32 在重传,这些都是性能排查的重要线索。我看到过不少朋友第一次抓到 ESP32 的数据帧时,会犯一个“想当然”的错误:以为数据帧载荷里能看到自己发的 UDP 报文内容。在开放网络(无加密)下确实可以,但在 WPA/WPA2 加密网络下,数据帧的载荷是一段密文,Wireshark 只显示为Data字段,里面是 EAPOL 协商出来的 PTK 加密后的结果。能看到的只有帧头、长度、时间戳、速率这些元数据。所以空口抓包的重点从来不是“看明文内容”,而是“看通信过程和时间特征”。4. 常见问题与排查技巧实录4.1 抓不到包时的排查路径我把这几年实际踩过的坑整理成一份排查路径,按出现频率排序,逐条对号入座。现象可能原因解决办法接口列表没有监听接口网卡驱动不支持 monitor 模式换 USB 网卡方案,确认芯片方案在开源驱动的支持列表里网卡切换到 monitor 模式后抓到的全是 Beacon监听信道不对先确认 ESP32 关联的路由器信道,用 iw 固定信道抓包文件里确实有 ESP32 的帧但看不到过滤表达式 MAC 写错从路由器后台或者 ESP32 日志里抄准确的 MAC能看到管理帧但一直没有数据帧ESP32 还没连接成功,或者连接后没有业务流量检查串口日志里 STA_CONNECTED 事件是否触发Beacon 帧 CRC/ FCS 错误很多环境干扰强、网卡离 AP 太远靠近设备,采用高增益天线,或者降低周边同频干扰Wireshark 显示帧头解析成乱码没有正确解码 802.11右键 Decode As,选择 IEEE 802.11最坑的一次,我印象很深:ESP32 明明通过串口打印已经连上 AP 了,但抓包里怎么都找不到数据帧。折腾半天发现,是我把固定监听信道设成了路由器当前工作信道旁边的那个。Wi-Fi 信号不跨信道可见,监听信道错了,哪怕设备在离你 1 米的地方发数据,你的网卡也听不到。另一个非常影响体验的坑是系统网络服务管理工具抢占网卡。在 Linux 下,如果你开启了网络管理服务,它可能会在你设完 monitor 模式后自动把网卡“拉回”普通模式。解决方法是先把接口从网络管理服务里排除,或者直接用命令行禁用对该接口的管理,再进入监听模式。4.2 过滤表达式速查表与使用技巧Wireshark 的过滤表达式是阅读空口抓包的核心技能。我的习惯是,每次抓包只建立一个“目标设备视角”的窗口,把与当前任务无关的帧全部过滤掉。下面是我常驻收藏的几个表达式:需求过滤表达式只看某台设备的收发帧wlan.addr == 34:85:18:aa:bb:cc只看 Beacon 帧wlan.fc.type_subtype == 0x08只看 Probe Request / Responsewlan.fc.type_subtype == 0x04 || wlan.fc.type_subtype == 0x05只看数据帧wlan.fc.type == 2只看加密握手帧eapol只看某个 BSSID 的帧wlan.bssid == 34:85:18:00:11:22看某个信道上的所有帧wlan_radio.channel == 6过滤表达式能组合使用,比如“我要看 ESP32 发出的数据帧”,可以用:wlan.addr == 34:85:18:aa:bb:cc && wlan.fc.type == 2这样过滤之后,剩下的帧就全是你目标设备的数据通信记录了,再配合时间列看通信间隔,非常干净。组合过滤里需要注意的一点是:wlan.addr会同时匹配 TA 和 RA,所以“只看 ESP32 发给别人的帧”和“只看别人发给 ESP32 的帧”要分开看地址字段。前者用wlan.ta == MAC,后者用wlan.ra == MAC。这个问题在分析 AP 和 STA 交互时特别值得区分。4.3 进阶方向:结合 EAPOL 握手与离线分析如果你已经把前面的基础抓包玩得比较熟了,可以往两个方向进阶。第一个方向是分析 EAPOL 握手,也就是 WPA2/WPA3 的密钥协商过程。过滤eapol之后,能看到四次握手的每一帧消息类型(Message 1~4)。如果你要排查“ESP32 连接稳定但网络层却不通”这种疑难杂症,重点看 EAPOL 握手是否完整、PTK 是否成功生成。只要握手少了一轮,后面的数据帧加密密钥就不一致,表现就是能连上但数据完全不通。第二个方向是保存 pcapng 文件做离线分析。实时抓包时注意力容易分散,把抓包文件存下来,再回放分析,能反复研究时间线。我经常做的一步是:把 UDP 数据包间隔导出成 CSV 或者直接用 Wireshark 的 IO Graph 功能画时间分布图。如果图形上出现周期性的大缺口,那基本可以断定空中信道存在被占用或者 AP 侧丢包。这个分析思路对于 ESP32 设备做低功耗间歇上报场景尤其有用。顺便补充一个实用技巧:Wireshark 支持对加密网络做密钥导流。如果你知道 ESP32 和 AP 握手时的 PSK 以及通信双方的 MAC,可以在 Wireshark 里配置解密密钥(Edit -> Preferences -> Protocols -> IEEE 802.11 -> Decryption Keys)。配好之后,数据帧里的加密载荷会被实时解密,你就能直接看到里面封装的 TCP/UDP 报文内容。这个功能在调试自有设备时非常有用,但要注意密钥必须准确对应到当前会话,否则解出来的全是乱码。5. 写在最后:一点个人体会和一个可复用的技巧整个实操流程走下来,我的感受是:会用串口和 AT 指令,只能算“会用 Wi-Fi 模块”;能把 Wireshark 里的 802.11 帧讲清楚,才算开始“读懂 Wi-Fi 的语言”。尤其是做 IoT、智能家居这类依赖无线连接的项目,空口抓包应该作为调试标配手段,而不是出了问题才想起来查。最后分享一个我每次都会用的小技巧:抓包前在 ESP32 日志里打印自己的 MAC 地址;然后在 Wireshark 里新建一个“目标设备”显示过滤器;再开始抓包。这样整个调试过程非常清爽——所有无关的广播、Beacon、邻居终端的流量全部被过滤,剩下的每一帧都跟你自己的设备直接相关,定位问题至少要快一倍。希望这篇能帮你在 Wi-Fi 调试的路上少走几个弯路,真去动手抓一次包,你会发现原来那些藏在空气里的“语言”,也没有那么神秘。
我一直觉得,搞 Wi-Fi 通信的开发者,手里不能只有串口日志和 AT 指令回显。很多时候设备报“连接失败”或者“数据发不出去”,从日志里看一切正常,但真正的问题出在空气里——电磁波上承载的那些 802.11 帧说了什么,你根本不知道。这次这篇“WIFI通信协议全解析14”,就用 Wireshark 把 ESP32 的通信数据原封不动嗅探出来,带你看懂“WiFi 的语言”。这不是什么高端黑客技巧,而是嵌入式开发和网络调试的基本功,适合玩 ESP32、做 IoT 项目、或者单纯想搞懂 Wi-Fi 协议栈的开发者参考。1. 为什么说嗅探是读懂 WiFi 语言最直接的方式1.1 空口数据与传统抓包的本质区别很多人一开始会问:我电脑上装个 Wireshark,直接就能抓 Wi-Fi 包吗?能抓,但抓到的不是“WiFi 的语言”,而是“网卡处理完之后吐给你的翻译稿”。常规模式下,无线网卡驱动会先做一层筛选:不是发给本机的帧直接丢掉,是发给本机的帧才把 802.11 头部剥掉,再把里面的 TCP/IP 数据包往系统协议栈里送。Wireshark 看到的已经是剥离了 WiFi 帧头的数据,相当于你只看到了快递里的商品,没看到快递单上写了什么。而空口嗅探完全不同。它把无线网卡切换到监听模式,让网卡放弃一切筛选和地址过滤,把物理层收到的每一个 802.11 帧都完整保存下来。这里面包括:Beacon 帧——AP 每 100ms 左右广播一次,“我是谁、我在哪个信道、支持什么速率”;Probe Request / Response——设备主动扫描时喊一嗓子“这附近有网络吗”,AP 回应“有,你连我吧”;Association Request / Response——设备决定加入某个 AP 时相互确认身份和参数的对话;数据帧——真正的业务数据,但注意在加密网络里载荷是看不见的,能看的是头部信息。对于 ESP32 这类嵌入式设备,串口日志只能告诉你“我要连 router_a”,但空口抓包能告诉你:它发出 Probe Request 时到底扫描了哪些信道、AP 回了什么能力集、关联时协商了哪些参数、后续每帧数据相隔多久。这些东西才是定位 Wi-Fi 通信问题的一手证据。1.2 Wireshark 为什么是首选工具同类工具不是没有,比如 Omnipeek、tcpdump 配合 monitor 模式也能干,但 Wireshark 在“看懂”这件事上做得最好,具体有三点:第一,它对 802.11 协议族的解码非常完整,管理帧里的每一个 Tag(SSID、Supported Rates、HT Capabilities、802.11ax 的 HE 字段等)都能按字段拆开,鼠标点一点就能看每个 bit 的含义。第二,它有一套强大的过滤语言,可以快速从一整包文件里筛出你要的那台 ESP32 的帧。第三,它是纯图形界面,交互式展开、时间序列分析、IO 图表这些功能对新手非常友好。不过我要说句公道话:Wireshark 的解码能力强,不代表它是万能的。它只是个解剖台,把帧拆开给你看,但“为什么会出现这个帧”“这个帧说明什么问题”,依然需要你对 802.11 协议本身有基本了解。也就是说,Wireshark 帮你“看见”,但“看懂”还得靠你自己。1.3 抓包方案的总体设计思路针对 ESP32 这个对象,我的抓包方案一般分三种场景,清晰区分能达到的目的:场景 A:ESP32 作为 Station 连接附近的路由器。这时抓包能看到完整的扫描、关联、加密握手、正常通信流程。适合排查“连不上 Wi-Fi”“频繁掉线”“数据发送延迟大”这类问题。场景 B:ESP32 作为 SoftAP,自己发射热点。这时抓包能看到 ESP32 自己发出去的 Beacon 帧和管理帧,非常直观,适合做协议教学和参数验证。场景 C:两台 ESP32 之间做 Wi-Fi 直连或组网,配合抓包分析它们之间的管理帧交互。这篇文章里的实操部分,我会同时覆盖场景 A 和场景 B,因为这两个场景最容易上手,也最能帮你建立对“WiFi 语言”的直观认知。2. 抓包前的环境准备:网卡、驱动与监听模式2.1 无线网卡选型与监听模式原理要监听空口数据,第一道坎是网卡。普通笔记本内置网卡能不能开监听模式?部分可以,但多数 Windows 驱动不支持,macOS 更是基本没戏。我的经验是:亲手试过才最稳。我建议选用带 USB 接口的外置无线网卡方案。这类网卡驱动在 Linux 下相对容易切换监听模式,对 802.11 帧的保留也比较完整,包括前面我提到的管理帧、控制帧、数据帧都能收到。核心硬件选型思路是:优先找那些在开源驱动列表里标注支持 monitor mode 的芯片方案,获取方式不做具体推荐,但市面上常见 USB 无线网卡大多都有对应方案。我平时备两片网卡:一片是自用的普通连接网卡,一片专门拿来监听。原因很简单——监听时网卡不能同时正常上网,专门备一块不影响日常开发。提示:监听模式和普通模式的区别,最核心的就是网卡驱动是否执行地址过滤。普通模式下,驱动只把目的地址匹配本机或组播/广播的帧上报;监听模式下,网卡把射频前端收到的所有帧全部上报。所以监听模式也叫 RFMON,Radio Frequency Monitor,它更像是“无线电收音机”,不是“对讲机”。2.2 三步开启监听模式(以 Linux 环境为例)相对省心的环境是 Linux,多数发行版对无线网卡的监听模式支持比较完善。以下命令需要 root 权限,操作前确保网卡没有被系统网络管理服务占用。第一步,确认接口名。插上网卡后运行iw dev,输出里能找到类似phy#0、Interface wlan0这样的信息,记住接口名,后面全用这个名字。第二步,切换监听模式。执行:sudo ip link set wlan0 down sudo iw dev wlan0 set monitor none sudo ip link set wlan0 up这里有一个细节:iw dev wlan0 set monitor none后面的none表示不附加任何过滤参数。有些网卡驱动支持fcsfail、control等额外参数,新手不建议加,先保证能干净地收到帧再说。第三步,固定信道。Wi-Fi 在 2.4GHz 频段有多个信道,网卡在同一时刻只能监听一个信道。先知道 ESP32 连的路由器工作在哪个信道,然后用:sudo iw dev wlan0 set channel 6把监听信道固定住。信道不对的话,你会惊讶地发现明明设备在拼命运数据,Wireshark 里却安静得像个假抓包。如果你用的不是 Linux,Windows 下要额外安装对应网卡的抓包驱动,macOS 则基本只能靠特殊方式处理,不太适合入门。所以我的建议是:新手做这个实验,优先整个 Linux 环境,最简单的方案是配一台小型 Linux 主机或虚拟机加 USB 网卡直通。2.3 Wireshark 关键显示设置启动 Wireshark,在接口列表里选择刚才进入监听模式的网卡接口(通常是 wlan0 或者以 mon 开头的虚拟接口),双击开始抓包。看到满屏幕的 Beacon 帧就说明监听成功了——周围所有 AP 的广播都进了你的抓包文件,这就是监听模式和普通模式一眼就能看出来的差异。不过这时候你看到的管理帧可能是乱糟糟的一堆,因为 Wireshark 默认会用普通的以太网解析逻辑去解析 802.11 帧头,偶尔会出现解析不对的情况。如果看到帧头字段明显异常,可以右键那条记录,选择“Decode As”,把协议强制指定为 IEEE 802.11,网络层还是保留原来的 IPv4 或 IPv6。接下来顺手做两个设置,能让后面省很多事:显示列调整:右键任意列头,添加Source和Destination列,这样每条记录的源和目的 MAC 地址能直接显示出来。在空口抓包里,这两个字段对定位设备非常关键。时间显示格式:进入 View -> Time Display Format,改成Seconds Since Previous Captured Frame。后面分析设备是否规律发包时,这个时间差列一眼就能看出节拍对不对。做完这些,抓包环境就绪了。下面进入真正的实操环节。3. 完整实操:用 Wireshark 抓取并看懂 ESP32 的通信全程3.1 最小实验场景搭建为了让抓包过程可控,我搭了一个最小实验台:一个 ESP32 开发板,烧录一个最简单的 TCP Client Demo;一台家用无线路由器(关掉其它无关终端混入干扰),以及监听用的 USB 网卡 + Wireshark。烧录之前,先在程序里把 ESP32 的工作模式配置清楚,我这里给出一种常见的初始化结构:#include <string.h> #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "esp_wifi.h" #include "esp_event.h" #include "esp_log.h" #include "nvs_flash.h" static const char *TAG = "wifi-sniffer-demo"; static void wifi_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data) { if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) { esp_wifi_connect(); } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_CONNECTED) { ESP_LOGI(TAG, "connected to AP"); } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) { ESP_LOGI(TAG, "disconnected, retrying..."); esp_wifi_connect(); } } static void wifi_init_sta(void) { wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); esp_wifi_init(&cfg); esp_event_handler_instance_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &wifi_event_handler, NULL, NULL); wifi_config_t wifi_config = { .sta = { .ssid = "DEMO_AP", .password = "demo_password", .threshold.authmode = WIFI_AUTH_WPA2_PSK, }, }; esp_wifi_set_mode(WIFI_MODE_STA); esp_wifi_set_config(WIFI_IF_STA, &wifi_config); esp_wifi_start(); } void app_main(void) { nvs_flash_init(); wifi_init_sta(); }这段代码的核心意图是让 ESP32 只做一件事:上电后初始化 Wi-Fi 协议栈,然后以 Station 模式扫描并连接指定的 SSID。为了观察效果,我还建议在连接成功之后,周期性地向局域网内某个固定 IP 发送 UDP 包,数量不需要多,一秒一个就够,这样空口抓包里能看到明显的周期性数据帧。3.2 从复位到连接:一次完整的抓包现场记录实验环境就绪之后,我把 USB 网卡固定在 ESP32 开发板附近两米以内,固定监听信道到路由器所在信道,开始抓包。然后给 ESP32 上电复位。Wireshark 里的帧就开始噌噌往外冒,种类和顺序是一个标准的 Wi-Fi 连接流程:第一步是扫描阶段。ESP32 发出一串 Probe Request 帧,目的地址是广播地址 FF:FF:FF:FF:FF:FF,SSID 字段要么是空(表示“扫描所有网络”),要么直接写着目标 SSID 的名称(表示“精准扫描某个网络”)。我用的是“DEMO_AP”,所以能看到带有指定 SSID 的 Probe Request。第二步是 AP 回应。路由器收到 Probe Request 后,回一个 Probe Response。注意这个帧的目的地址变成了 ESP32 的 MAC 地址,里面携带了该 AP 的能力集、支持的速率、加密方式等信息。第三步是认证和关联。ESP32 发起 Association Request,表示“我确认要加入你”。AP 回应 Association Response,里面带着协商结果和一个关联 ID(AID)。到这里,管理层面的关系就建立好了。第四步是加密握手。如果网络是 WPA2-PSK,接下来会有四次 EAPOL 握手帧,抓包里过滤eapol就能看到。这个过程协商出真正的会话密钥,之后的数据帧都使用这个密钥加密。第五步是正常通信。连接完成后,ESP32 开始周期性发送 UDP 数据包。这些数据帧的特征是帧控制字段类型为 Data(Type=2),并且目的地址是路由器的 MAC 地址,而不是广播地址。我在实际操作中经常用过滤表达式直接锁定 ESP32,一条就够了:wlan.addr == 34:85:18:aa:bb:cc把aa:bb:cc替换成你手里开发板的实际 MAC 地址。Wireshark 里所有源地址或目的地址命中这个 MAC 的帧都会被选出来,形成一条独立的连接时间线。注意:MAC 地址在 ESP32 上可以用esp_read_mac()读取,也可以用esptool读 efuse 内容,或者直接在路由器后台“已连接设备列表”里找。如果你把过滤条件写错了,最常见的表现是:抓包文件里一直有流量,但过滤窗口一个帧都没有。3.3 抓到的帧怎么看:关键字段逐一拆解拿到帧之后,重点看哪些字段?我按“看帧先看头”的思路给你拆一遍。所有 802.11 帧都以一个固定头部开头,其中最重要的两个部分是帧控制字段和地址序列。帧控制字段里,Type 决定大类(管理帧、控制帧、数据帧),Subtype 决定具体用途。我给你一个最常见的速查表:类型Subtype 值用什么过滤含义管理帧8wlan.fc.type_subtype == 0x08Beacon,AP 周期性广播管理帧4wlan.fc.type_subtype == 0x04Probe Request,设备主动扫描管理帧5wlan.fc.type_subtype == 0x05Probe Response,AP 响应扫描管理帧0wlan.fc.type_subtype == 0x00Association Request,关联请求管理帧1wlan.fc.type_subtype == 0x01Association Response,关联响应数据帧0wlan.fc.type == 2普通数据帧,载荷可能加密控制帧特定wlan.fc.type == 1ACK、RTS、CTS 等地址序列里有三到四个 MAC 地址字段,分别表示接收端、发送端、BSSID 和(有些帧才有)源/目的端。在数据帧里通常能看到:接收端(RA):通常是路由器 MAC;发送端(TA):通常是 ESP32 MAC;BSSID:路由器 BSSID,基本等于 AP 的 MAC。为什么普通网络抓包看不到这几个地址?因为普通网卡驱动已经把它们剥掉只留 IP 层了。看空口抓包,这几个地址就是判断“这个帧到底是谁发给谁的”最重要的依据。再说 Beacon 帧。Beacon 非常值得单独拆一次,因为它是所有 Wi-Fi 网络“存在感”的来源。展开一个 Beacon 帧,你会看到:Beacon Interval:通常为 100ms,表示 AP 每隔这么长时间广播一次;Capability Info:包括 ESS、Privacy 等标志位,Privacy 置 1 表示网络启用了加密;Tagged Parameters:这一部分藏着 SSID 名称、支持的速率列表、信道号、HT Capabilities、Vendor Specific 字段。我用一个生活化类比:Beacon 帧相当于店铺门口循环播放的“营业中”广播。它每隔一段时间喊一次,告诉大家“这家店在,我用的是 WPA2 加密,支持这些支付方式”。而 Probe Request 就是顾客走到门口问一句“你们这里卖什么?”,Probe Response 则是店员出来说“我们卖这些,价格是这些”。对于 ESP32 的调试,最实用的信息其实在“时间”维度上。展开数据帧列表,看 Time 列的 delta 值,如果 ESP32 设定一秒发一个 UDP 包,那么相邻数据帧的间隔应该稳定在 1000ms 左右。如果出现大的抖动或者丢帧,说明空中信道拥挤、AP 队列拥塞或者 ESP32 在重传,这些都是性能排查的重要线索。我看到过不少朋友第一次抓到 ESP32 的数据帧时,会犯一个“想当然”的错误:以为数据帧载荷里能看到自己发的 UDP 报文内容。在开放网络(无加密)下确实可以,但在 WPA/WPA2 加密网络下,数据帧的载荷是一段密文,Wireshark 只显示为Data字段,里面是 EAPOL 协商出来的 PTK 加密后的结果。能看到的只有帧头、长度、时间戳、速率这些元数据。所以空口抓包的重点从来不是“看明文内容”,而是“看通信过程和时间特征”。4. 常见问题与排查技巧实录4.1 抓不到包时的排查路径我把这几年实际踩过的坑整理成一份排查路径,按出现频率排序,逐条对号入座。现象可能原因解决办法接口列表没有监听接口网卡驱动不支持 monitor 模式换 USB 网卡方案,确认芯片方案在开源驱动的支持列表里网卡切换到 monitor 模式后抓到的全是 Beacon监听信道不对先确认 ESP32 关联的路由器信道,用 iw 固定信道抓包文件里确实有 ESP32 的帧但看不到过滤表达式 MAC 写错从路由器后台或者 ESP32 日志里抄准确的 MAC能看到管理帧但一直没有数据帧ESP32 还没连接成功,或者连接后没有业务流量检查串口日志里 STA_CONNECTED 事件是否触发Beacon 帧 CRC/ FCS 错误很多环境干扰强、网卡离 AP 太远靠近设备,采用高增益天线,或者降低周边同频干扰Wireshark 显示帧头解析成乱码没有正确解码 802.11右键 Decode As,选择 IEEE 802.11最坑的一次,我印象很深:ESP32 明明通过串口打印已经连上 AP 了,但抓包里怎么都找不到数据帧。折腾半天发现,是我把固定监听信道设成了路由器当前工作信道旁边的那个。Wi-Fi 信号不跨信道可见,监听信道错了,哪怕设备在离你 1 米的地方发数据,你的网卡也听不到。另一个非常影响体验的坑是系统网络服务管理工具抢占网卡。在 Linux 下,如果你开启了网络管理服务,它可能会在你设完 monitor 模式后自动把网卡“拉回”普通模式。解决方法是先把接口从网络管理服务里排除,或者直接用命令行禁用对该接口的管理,再进入监听模式。4.2 过滤表达式速查表与使用技巧Wireshark 的过滤表达式是阅读空口抓包的核心技能。我的习惯是,每次抓包只建立一个“目标设备视角”的窗口,把与当前任务无关的帧全部过滤掉。下面是我常驻收藏的几个表达式:需求过滤表达式只看某台设备的收发帧wlan.addr == 34:85:18:aa:bb:cc只看 Beacon 帧wlan.fc.type_subtype == 0x08只看 Probe Request / Responsewlan.fc.type_subtype == 0x04 || wlan.fc.type_subtype == 0x05只看数据帧wlan.fc.type == 2只看加密握手帧eapol只看某个 BSSID 的帧wlan.bssid == 34:85:18:00:11:22看某个信道上的所有帧wlan_radio.channel == 6过滤表达式能组合使用,比如“我要看 ESP32 发出的数据帧”,可以用:wlan.addr == 34:85:18:aa:bb:cc && wlan.fc.type == 2这样过滤之后,剩下的帧就全是你目标设备的数据通信记录了,再配合时间列看通信间隔,非常干净。组合过滤里需要注意的一点是:wlan.addr会同时匹配 TA 和 RA,所以“只看 ESP32 发给别人的帧”和“只看别人发给 ESP32 的帧”要分开看地址字段。前者用wlan.ta == MAC,后者用wlan.ra == MAC。这个问题在分析 AP 和 STA 交互时特别值得区分。4.3 进阶方向:结合 EAPOL 握手与离线分析如果你已经把前面的基础抓包玩得比较熟了,可以往两个方向进阶。第一个方向是分析 EAPOL 握手,也就是 WPA2/WPA3 的密钥协商过程。过滤eapol之后,能看到四次握手的每一帧消息类型(Message 1~4)。如果你要排查“ESP32 连接稳定但网络层却不通”这种疑难杂症,重点看 EAPOL 握手是否完整、PTK 是否成功生成。只要握手少了一轮,后面的数据帧加密密钥就不一致,表现就是能连上但数据完全不通。第二个方向是保存 pcapng 文件做离线分析。实时抓包时注意力容易分散,把抓包文件存下来,再回放分析,能反复研究时间线。我经常做的一步是:把 UDP 数据包间隔导出成 CSV 或者直接用 Wireshark 的 IO Graph 功能画时间分布图。如果图形上出现周期性的大缺口,那基本可以断定空中信道存在被占用或者 AP 侧丢包。这个分析思路对于 ESP32 设备做低功耗间歇上报场景尤其有用。顺便补充一个实用技巧:Wireshark 支持对加密网络做密钥导流。如果你知道 ESP32 和 AP 握手时的 PSK 以及通信双方的 MAC,可以在 Wireshark 里配置解密密钥(Edit -> Preferences -> Protocols -> IEEE 802.11 -> Decryption Keys)。配好之后,数据帧里的加密载荷会被实时解密,你就能直接看到里面封装的 TCP/UDP 报文内容。这个功能在调试自有设备时非常有用,但要注意密钥必须准确对应到当前会话,否则解出来的全是乱码。5. 写在最后:一点个人体会和一个可复用的技巧整个实操流程走下来,我的感受是:会用串口和 AT 指令,只能算“会用 Wi-Fi 模块”;能把 Wireshark 里的 802.11 帧讲清楚,才算开始“读懂 Wi-Fi 的语言”。尤其是做 IoT、智能家居这类依赖无线连接的项目,空口抓包应该作为调试标配手段,而不是出了问题才想起来查。最后分享一个我每次都会用的小技巧:抓包前在 ESP32 日志里打印自己的 MAC 地址;然后在 Wireshark 里新建一个“目标设备”显示过滤器;再开始抓包。这样整个调试过程非常清爽——所有无关的广播、Beacon、邻居终端的流量全部被过滤,剩下的每一帧都跟你自己的设备直接相关,定位问题至少要快一倍。希望这篇能帮你在 Wi-Fi 调试的路上少走几个弯路,真去动手抓一次包,你会发现原来那些藏在空气里的“语言”,也没有那么神秘。