做Wi-Fi优化这几年,我最大的体会是:不管上层协议怎么演进,底层数据帧能不能顺利发出去,最终都得看MAC层的分布式协调功能(DCF)脸色。这是我这个系列的第9篇,专门把DCF的基本访问流程从头到尾拆一遍——从信道侦听、DIFS等待、随机退避,到ACK确认和重传机制,配合参数计算和现场抓包经验,一次讲透。适合刚接触无线协议开发的工程师、做无线网络优化的运维,以及所有被“Wi-Fi速度怎么又上不去”折磨过的人。
1. 为什么Wi-Fi不能“边发边听”:DCF机制的设计逻辑
1.1 无线信道和有线信道的本质差异
要理解DCF,先得明白一个基本事实:无线信道和有线信道有本质区别。有线以太网用的是CSMA/CD(载波侦听多址访问/冲突检测),节点在发送数据的同时还能监测线路上的电压变化,一旦发现冲突,立刻停止发送并广播一个拥塞信号,让所有节点都知道“刚才撞车了”。这套机制依赖一个前提:节点发送信号时,“耳朵”和“嘴巴”可以同时工作,而且自己发出的信号不会完全盖住别人的信号。
无线信道做不到这一点。802.11设备是半双工的,天线在发送时几乎无法同时接收,而且无线电信号是发一条,覆盖范围内所有节点都能收到。更麻烦的是,自己发射的信号功率远远大于收到的远端信号,直接“自掩蔽”了。所以在无线环境里,发送方压根没有能力在发送过程中检测冲突,CSMA/CD这套思路移植不过来,只能用CSMA/CA——把“检测冲突”变成“避免冲突”。
这个差异听起来简单,但它决定了整个802.11 MAC层的基本行为方式:所有节点共享一个无线媒介,谁想发数据,都得先“听一听”信道是否空闲,空闲了才允许发,而且发完还必须等对方回执。这一整套规则,就是DCF。
1.2 DCF的两大支柱:CSMA/CA与ACK确认
DCF在802.11标准里是默认的媒体访问方式,也是所有更高级机制(比如802.11e的EDCA、802.11ax的OFDMA)的基础。它主要由三块构成:载波侦听机制、随机退避机制、以及确认/重传机制。载波侦听又分物理载波侦听和虚拟载波侦听,后面我会详细展开。
为什么说ACK确认是支柱之一?因为无线链路是“发出去就不管”的不可靠传输,发送方没法知道自己发出去的帧到底有没有被正确接收。数据帧可能被噪声打坏,可能因为冲突被淹没,也可能接收方就算了CRC检查发现错误后选择沉默。如果发送方不做任何确认就直接认为发送成功,上层就会拿到大量错误数据。所以DCF规定:接收方收到正确数据帧后,必须等待一个极短的时间间隔,然后回复一个ACK帧;发送方只有在收到ACK之后,才认定这次发送成功。没收到,就当作失败处理,走重传流程。
所以别小看DCF,它本质上是一套“先听后说、说了要确认、没说清就重来”的完整协议,后面所有细节都是围绕这三件事展开的。
2. 基本访问流程逐步拆解:从侦听到ACK全链路
2.1 发送前的三道关卡:物理侦听、NAV虚拟侦听与DIFS
我们假设一个站点A要向站点B发送一个数据帧,正常情况下,A拿到这个帧之后不是立即发送,而是先过三道关卡。
第一关是物理载波侦听。站点A的无线芯片要检测当前信道上有没有能量活动,也就是CCA(Clear Channel Assessment,空闲信道评估)。如果检测到信道上已经有信号能量超过阈值,就说明信道忙,A要继续等待;只有信道空闲,才允许进入下一步。
第二关是虚拟载波侦听,对应的是NAV(Network Allocation Vector,网络分配向量)。802.11每个帧的MAC头里都有一个Duration/ID字段,这个字段会告诉其他站点“接下来信道会被占用多长时间”。站点A在侦听信道时,会把听到的所有帧里的Duration值记下来,维护一个本地倒计时器,这个计时器就叫NAV。只要NAV不为0,A就认为信道被占用了,哪怕此时物理信道看起来静悄悄的,也不能发。这就像开会时主持人说“这段讨论需要10分钟”,其他人在这个时间里即使没听到发言内容,也知道不该插嘴。
第三关是等待DIFS(DCF Interframe Space,DCF帧间间隔)。就算物理侦听和NAV都显示信道空闲,A也不能立刻发送,必须再等一段固定时间DIFS。为什么要多等这一下?因为DIFS的长度比ACK、CTS这类响应帧的等待间隔SIFS要长,这是802.11用来区分帧优先级的关键手段。高优先级的控制帧(比如ACK)只需要等SIFS就能发,普通数据帧则需要等更长的DIFS——这样即使多个节点同时想发数据,ACK也有机会“插队”先发出去。
只有这三关全部通过,A才能进入下一步:判断自己是否需要做随机退避。
2.2 随机退避在防什么:BEB算法的工作过程
直接说结论:随机退避(Backoff)机制,防的就是多个站点同时抢到信道后一起发送造成冲突。
设想一个场景:两个站点A和C都侦听信道,发现信道空闲了,然后各自等待DIFS,如果它们等待DIFS后同时发送,数据帧在空中碰撞,接收方一个都收不到。为了避免这种“整齐划一”的拥抢,DCF规定:站点在DIFS结束后,不能立刻发送,而要先从一个范围(竞争窗口,Contention Window,CW)里随机取一个退避计数值,然后每个时隙(Slot Time)递减一次,只有计数值递减到0才能发送。
这就是BEB(Binary Exponential Backoff,二进制指数退避)的基本思路。具体规则是:
- 站点从范围 [0, CW] 内均匀随机生成一个整数,作为退避计数器初始值。
- 信道每空闲一个时隙,计数器减1;如果信道变忙,计数器暂停,等待信道再次空闲并经过DIFS后继续递减。
- 计数器减到0时,站点立即发送数据帧。
如果这次发送失败(比如没收到ACK),竞争窗口CW会翻倍,然后再次从新的范围里随机取值重新退避。CW的初始值(CWmin)和上限(CWmax)因协议版本而异,比如802.11a/g中CWmin为15,CWmax为1023。翻倍的目的是让多次失败的站点自动向后“躲”,给其他站点更多发送机会——这大大降低了持续冲突的概率。
这里有个很多初学者容易忽略的细节:站点并不是每次发送前都做退避的。如果站点A之前一直空闲,队列中刚来一个新包,并且它完成DIFS等待后信道仍然空闲,是可以直接发送的,不需要退避。但如果站点A刚发完一个包,队列中还有下一个包等着发,那么即使信道空闲,也必须执行一次退避后才能发送下一包。这个规则叫“post-backoff”,目的是防止同一个站点连续霸占信道,破坏公平性。
2.3 数据帧发出之后:ACK定时器与重传判断
站点A在退避计数器归零后,正式把数据帧发出去。从这一刻起,A会启动一个ACK定时器,然后等待接收方B回来的确认。这个ACKTimeout在驱动里通常设置为几十微秒的量级,它必须略大于SIFS加上ACK帧的传输时间,给接收方留足处理余量。
接收方B这边收到数据帧之后,先做两件事:一是检查帧的FCS(帧校验序列),确认数据在传输过程中没有被损坏;二是检查帧的目的MAC地址是否是自己。如果校验通过、地址匹配,B会等待一个SIFS(比DIFS短得多),然后立刻发回一个ACK帧。SIFS这么短的用意很清晰:ACK是最高优先级的响应帧,必须在其他普通数据帧抢到信道之前发出去,这样才能保证发送方A及时收到确认。
如果A在ACKTimeout内没有收到ACK,就认为发送失败,进入重传流程。重传时,竞争窗口CW翻倍,重新生成随机退避值,然后重新走完整的信道访问流程。在重传过程中,A发的数据帧会带上Retry标记,接收方B如果发现这个帧的序列号之前已经收过了,就会直接丢弃重复帧,不会重复向上层递交——这个去重机制在无线网络里非常重要,因为重传带来的重复帧是常态。
这里再强调一个容易踩坑的点:重传不是无限进行的。802.11标准规定了重试上限,短帧(长度不超过RTS阈值的帧)默认最多重试7次,长帧默认最多重试4次。超过限制后,这个帧就会被丢弃,由上层协议(比如TCP)去处理。所以大家抓包的时候可能会看到某个帧连着重试好几次最后不了了之,这不一定代表链路彻底断了,可能只是到了重试上限。
3. 关键时间参数与开销计算:为什么实际速率总打折
3.1 帧间间隔优先级对照与DCF参数表
前面提到了SIFS、DIFS这些帧间间隔,它们是理解DCF的基础。802.11里定义了多种帧间间隔,优先级从高到低大致如下:
| 帧间间隔 | 时长规则 | 典型用途 |
|---|---|---|
| SIFS | 最短,固定值 | ACK确认帧、CTS响应帧、分片后的下一片段 |
| PIFS | SIFS + 1个时隙 | 点协调功能中AP获取媒体控制权 |
| DIFS | SIFS + 2个时隙 | 普通数据帧参与竞争发送前的等待时间 |
| EIFS | 需单独计算 | 收到错误帧之后的额外等待,避免撞上后续响应帧 |
EIFS比较特殊。当一个站点收到一个无法正确解析的帧时,它不知道该帧里的Duration字段是多少,也就没法正确设置NAV,于是它必须等更长时间再发,避免撞到那个坏帧对应的后续ACK流程。EIFS大约等于SIFS + DIFS + 一个最低速率ACK帧的传输时间,具体数值随基本速率不同而变化。
在不同协议版本下,这些时间参数差异很大。一张表看明白:
| 参数 | 802.11b | 802.11a / 802.11g短时隙 | 802.11n/ac/ax(20MHz) |
|---|---|---|---|
| 时隙 Slot Time | 20 µs | 9 µs | 9 µs |
| SIFS | 10 µs | 16 µs | 16 µs |
| DIFS | 50 µs | 34 µs | 34 µs |
| CWmin | 31 | 15 | 15 |
| CWmax | 1023 | 1023 | 1023 |
注意,802.11n/ac在2.4GHz频段如果网络中存在802.11b老设备,时隙可能会被迫退回20µs,DIFS变成50µs,整个网络的退避时间会明显变长。这个“老设备拖垮全网”的现象在现网里很常见,我在后面第5节会再展开。
3.2 一次完整传输的“空气时间”计算实例
理解了参数,我们用实际数字算一下,看看一个1500字节的数据包在802.11g(短时隙)网络里,从开始到确认完成,到底要占用多少信道时间。这能解释为什么Wi-Fi的实际吞吐永远达不到物理层速率。
假设物理层速率54Mbps,数据帧总长度大约1526字节(14字节MAC头 + 8字节LLC/SNAP头 + 1500字节IP包 + 4字节FCS),ACK帧按最低基本速率发送(比如6Mbps,OFDM方式),计算过程如下:
- DIFS等待:34 µs
- 平均退避时间:竞争窗口初始为15,平均退避计数值为15/2 ≈ 7.5个时隙,乘以9 µs,约67.5 µs
- 数据帧发送时间:1526×8 / 54Mbps ≈ 226 µs,加上OFDM前导码和物理层头约20 µs,共246 µs
- SIFS等待:16 µs(802.11g)
- ACK帧发送时间:ACK帧18字节(14字节MAC头 + 4字节FCS),按6Mbps发送,传输时间约24 µs,加上20 µs前导,共44 µs
把这些加起来:34 + 67.5 + 246 + 16 + 44 ≈ 407.5 µs。也就是说,发送1500字节数据需要约408 µs的信道占用时间。实际有效速率 = 1500×8 / 407.5µs ≈ 29.4Mbps,只有54Mbps物理速率的一半左右。
这就是无线网络“理论速率打对折”的秘密。开销主要来自三块:协议前导码、帧间间隔、随机退避。算完这笔账你就会明白,为什么室内实测能跑到物理速率一半就算不错了。如果再加上一层加密(比如WPA2),或者信道上有大量管理帧广播,实际吞吐还会进一步下降。
这个计算思路非常实用,比如你在现场评估一个AP的容量,完全可以用同样的方式估算不同速率集下每个终端的极限吞吐,进而判断是物理层速率不够,还是MAC层冲突太多。
4. 隐藏节点问题与RTS/CTS辅助流程
4.1 隐藏节点和暴露节点的成因
DCF的基本访问流程有个著名痛点:隐藏节点(Hidden Node)。场景是这样的:站点A在房间一头,站点B在另一头,中间还隔着一个AP。A能听到AP,B也能听到AP,但A可能听不到B,B也可能听不到A。当A和B同时向AP发送数据时,它们互相感知不到对方的存在,自然也不会退避,结果两个帧在AP处碰撞,双双损坏,AP收不到任何一帧。
这是无线网络里非常隐蔽的问题,直接表现就是:单个终端测速正常,多个终端同时用的时候丢包和重传率陡增,而且这种冲突A和B还都浑然不知,直到超时重传才发现。
暴露节点(Exposed Node)则是另一个方向的问题。站点A在向B发送,附近的站点C能听到A的信号,但C其实是想向D发送的,而且C和D的通信并不影响A和B的通信。可是因为CSMA/CA的原则是“听到信道忙就等待”,C只能干等,浪费了信道机会。暴露节点不如隐藏节点严重,因为它只是降低信道利用效率,不会直接造成冲突。
4.2 开启RTS/CTS的代价与阈值选择思路
为了缓解隐藏节点问题,DCF提供了一个可选机制:RTS/CTS握手。发送方A在发送大数据帧之前,先发一个RTS(Request To Send,请求发送)帧,里面包含发送地址、接收地址,以及预计占用信道的时间Duration。接收方B收到RTS后,如果信道条件允许,就回一个CTS(Clear To Send,允许发送)帧,CTS同样携带Duration信息,告诉大家“接下来我会接收数据,大家都安静”。
这个握手的妙处在于:隐藏节点就算听不到A的RTS,也能听到B的CTS,收到CTS后它会更新自己的NAV,保持静默,直到数据帧和ACK都传输完毕。这样就把冲突窗口从“整个数据帧”压缩到了“RTS帧大小”。RTS帧很短,即使RTS冲突了,损失的信道时间也远小于大数据帧冲突。
但RTS/CTS不是免费的。每次数据发送前都要多交换两个控制帧,对于小数据帧来说,开销甚至比数据本身还大,得不偿失。所以802.11引入了一个RTS阈值(RTS Threshold)参数,默认通常在2347字节左右。也就是说,只有帧长度超过这个阈值时,才会先走RTS/CTS流程;长度小于等于阈值的帧,直接用基本访问流程发送。
实际调优时,如果现场存在严重的隐藏节点问题,可以把RTS阈值调低,让更多帧走RTS/CTS握手。我自己的经验是,开启RTS/CTS后吞吐通常会有一定损失,但如果重传率因此大幅下降,总体效果往往是正的。判断标准很简单:先抓包看重传率,如果数据帧重传率超过10%,且确认存在隐藏节点,就值得尝试把RTS阈值调到1000字节左右试验一下。
5. 现场排查经验:抓包判断DCF工作是否正常
5.1 监听模式抓包与Wireshark关键过滤
纸上谈兵再多,不如直接看线上数据。做Wi-Fi调优,监听模式抓包是基本功。在Linux环境下,可以用支持monitor模式的无线网卡开启抓包:
# 查看无线网卡名称 iw dev # 将网卡切换为monitor模式 sudo ip link set wlan0 down sudo iw dev wlan0 set type monitor sudo ip link set wlan0 up # 抓包保存到文件 sudo tcpdump -i wlan0 -w dcf_test.pcap用Wireshark打开抓包文件后,我常用的过滤条件有这几个:
# 只看所有数据帧 wlan.fc.type == 2 # 只看重传帧 wlan.fc.retry == 1 # 只看ACK帧 wlan.fc.type_subtype == 0x1d # 只看某一个源MAC的帧和对应的ACK wlan.ta == aa:bb:cc:dd:ee:ff # 同时过滤重传和特定接收方 wlan.fc.retry == 1 && wlan.ra == aa:bb:cc:dd:ee:ff看抓包结果时,我习惯按时间排序,找“一个数据帧后面紧跟着一个ACK”的序列对。如果经常出现数据帧发出去之后,隔了很久才看到ACK,或者干脆没有ACK,过了一会儿看到同一个序列号的数据帧带着Retry标记重发了,那说明基本访问流程有问题——信道质量差、隐藏节点冲突,或者AP侧处理不过来。
5.2 常见异常现象速查表与调试建议
下面这张表,是我在实际项目中反复用到的排查速查表,先判断现象,再按优先级逐项排查。
| 现象 | 可能原因 | 排查与调整建议 |
|---|---|---|
| 重传率高、有效吞吐低 | 隐藏节点、同频干扰、信号弱 | 抓包确认重传帧源;检查RSSI和噪声底噪;尝试启用RTS/CTS、降低信道负载或换信道 |
| 数据帧发出后长时间无ACK | 接收方信号差、ACK丢失、接收方队列拥塞 | 查接收方侧抓包是否收到数据帧;检查AP/终端的CPU负载;调低发射速率 |
| 小包正常,大包频繁失败 | 长帧更容易受干扰或冲突,RTS阈值设置不合理 | 调低RTS阈值强制大帧走RTS/CTS;检查是否有多AP同频干扰 |
| 某个终端长期发送机会少 | 捕获效应、退避参数异常、老旧协议设备拖慢全网 | 检查该终端关联速率和RSSI;限制或淘汰旧协议设备;开启Airtime公平性调度 |
| 全网整体都慢,时隙明显偏长 | 网络中存在802.11b设备,保护机制把时隙退回20µs | 禁用802.11b基本速率;在AP侧关闭低速率兼容;确认后再测试吞吐 |
这里单独说下捕获效应(Capture Effect)。它指的是两个终端同时发送,接收方虽然收到了叠加信号,但其中一路信号强度显著高于另一路,接收方可以成功解调出较强的那路,完全无视较弱的那路。于是较强的终端“赢了”,较弱的终端却因为发送失败触发退避,退避后再次发送还是输,进入持续饥饿状态。这种情况在抓包里表现为:某几个终端长期重传、时延极高,而另一两个终端占用大量信道资源。
我调试过一个写字楼场景,同一个AP下20多个终端,其中有几台打印机和一个老旧的802.11b设备在线时,整个网络的时隙参数被迫退回20µs,所有终端的退避时间直线上升,视频会议卡顿明显。最终在AP侧关掉了802.11b低速率支持,时隙恢复正常,问题立刻缓解。这类问题上到协议原理是DCF的参数退化,下到操作只是改两个配置项,但如果不理解DCF,根本不知道问题出在时隙参数上。
另外还有一个经验值得分享:不要一看到重传就觉得是设备坏了。无线链路上有一定比例的重传是正常的,关键看比例和分布。如果重传是零星分散的,大概率是环境噪声;如果重传集中出现在某个特定终端上,大概率是该终端的信号问题或位置问题;如果全网所有终端都高重传,那要考虑AP本身或信道环境的问题。判断的思路比单点数据更重要。
这套DCF基本访问流程,理论内容读起来枯燥,但我在实际项目里被它救了不止一次。无论做AP研发、终端适配,还是企业无线运维,只要能把信道侦听、随机退避、ACK确认这条链路想明白,很多现场“玄学”问题就能转变成可解释、可复现、可测量的工程问题。最后再给个建议:遇到Wi-Fi性能问题,先去抓包看基本访问流程是否健康,再动信道、功率、频宽这些参数,这个顺序能帮你少走很多弯路。