☰
CSMA/CD与CSMA/CA MATLAB仿真:退避、ACK与吞吐率曲线实现
2026/10/26 10:29:54 网站建设 项目流程

简介:这份MATLAB仿真代码包围绕CSMA/CA与CSMA/CD两种介质访问控制协议展开,面向学习无线局域网、以太网及通信网络课程的学生、教师与工程技术人员,帮助其在可运行脚本中理解载波监听、冲突避免、冲突检测与随机退避的完整流程。压缩包共16个文件,均为.m脚本,体积约17KB,涵盖协议主流程、节点增减、退避时间设置、帧的压入弹出、发送记录与图形化显示等模块,注释较详细,便于逐行阅读和二次修改。目前已有6667人学习下载,说明其作为教学示例具有较好的参考价值。读者可借助参数调整观察不同节点数量下信道竞争、碰撞概率与网络效率的变化,并通过可视化结果对比两种协议在预约信道、确认接收和冲突后重试等环节的差异,从而把课堂概念落到可复现的仿真实验上,适合课程设计、自学与协议原理验证。

1. 把协议抄成 while 循环跑不通,问题往往不在协议本身

很多人第一次做 CSMA/CD 与 CSMA/CA 的 MATLAB 仿真,都是照着流程图画一个 while 循环,跑完得出一条吞吐率曲线,然后和教科书上那个漂亮的理论值一比,差得离谱,还找不出差在哪。这套带详细注释的仿真源代码,解决的正是这一整套麻烦:它把监听信道、碰撞检测、二进制指数退避、帧间隔与 ACK 确认这些环节,拆成一个个独立函数,每个函数开头都写清了输入输出和物理含义,变量名贴着协议术语走,改一个参数就能立刻看到曲线怎么变。

它适合两类人。一类是网络课要交仿真作业、想真正弄明白「为什么 CSMA/CD 在负载一高就崩」的学生;另一类是要给自研接入协议做对照基线、需要一个能跑、能改、结果经得起追问的参考实现的开发者。整套代码只用 MATLAB 基础语法,不依赖任何工具箱,从主脚本到工具函数层次分明,读起来不像黑匣子。

2. 项目骨架与仿真模型:两套协议共用一套信道抽象

2.1 目录结构:协议逻辑和信道模型是分开的两层

拿到这套源代码,先别急着点运行,把目录扫一遍,心里就有底了。

csma_sim/ ├── main_csma_cd.m % CSMA/CD 仿真入口:扫参 + 出图 ├── main_csma_ca.m % CSMA/CA 仿真入口 ├── config/ │ ├── cfg_cd.m % CD 侧全部可调参数,返回一个 struct │ └── cfg_ca.m % CA 侧参数:DIFS / SIFS / CWmin / CWmax / retry_limit ├── core/ │ ├── gen_arrivals.m % 泊松到达序列生成 │ ├── channel_busy.m % 信道忙闲查询 │ ├── collide_during.m % 发送窗口内的碰撞检测 │ ├── backoff_beb.m % 二进制指数退避 │ ├── backoff_cw.m % 竞争窗口退避 │ └── stats_collect.m % 吞吐率、碰撞率、平均时延统计 ├── utils/ │ ├── slot_time.m % 时隙换算 │ └── plot_result.m % 统一出图,避免两套脚本风格不一致 └── results/ % .mat 与 .png 落盘位置

这个分层的意义在于:两个协议在物理层面对的是同一条信道,只是接入规则不同。所以信道占用被抽象成一张「发送日志表」tx_log,每行一条[t_start, t_end, node_id],谁来查询都查这张表。写新协议时只要往表里登记区间,channel_busy和collide_during一行都不用改,这是这套代码最值得学的一处设计。

config单独抽出来也是同样的道理。参数集中在cfg_cd.m和cfg_ca.m两个函数里,主脚本只负责调用,扫参时改cfg字段就行,不用去正文里翻魔法数字。我见过太多仿真代码把 0.0001 这种时延直接写在循环里,过两周自己都看不懂。

2.2 时间模型:所有模块都围绕 slot 展开

两个协议的时间基准不一样,这点必须先在脑子里摆正,否则后面算出来的吞吐率全是错的。

CSMA/CD 的退避时隙是2τ(τ 是单程传播时延),因为一次冲突最迟要在2τ内被双方检测到,这段窗口就是碰撞窗口。CSMA/CA 的时隙则短得多,是固定的slot = 9μs这种量级,退避计数器每经过一个空闲时隙减一。两套协议共用slot_time.m做换算,但传进去的参数不同。

function s = slot_time(mode, cfg) % SLOT_TIME 返回协议对应的时隙长度(秒) % mode : 'cd' 或 'ca' % cfg : 配置结构体 switch lower(mode) case 'cd' % CSMA/CD:碰撞窗口 2τ,退避计时也以 2τ 为单位 s = 2 * cfg.tau; case 'ca' % CSMA/CA:时隙是协议常量,与传播时延无关 s = cfg.slot; otherwise error('slot_time:badMode', '未知协议模式: %s', mode); end

参数说明:cfg.tau是单程传播时延,有线场景下按 5μs/km 量级估;cfg.slot是无线场景的固定时隙。这两者在代码里绝不混用,stats_collect统计时会再调一次slot_time,保证口径一致。

提示:如果你改完参数发现吞吐率随节点数不降反升,第一件事就是回头检查这两个时隙有没有被写串,这是最高频的一类返工。

2.3 关键配置项:哪些参数会直接改变结论

配置项看着琐碎,但每一个都对应一个物理量,改之前最好知道它会带来什么影响。

参数含义CD 典型值CA 典型值影响
N竞争节点数5~505~50线性抬高负载,最敏感
lambda每节点帧到达率(帧/秒)10~20010~200决定信道利用率区间
t_frame帧传输时间由帧长/速率算由帧长/速率算与 τ 的比值决定效率上限
tau单程传播时延微秒级微秒级决定碰撞窗口大小
DIFS/SIFS帧间隔无数十微秒只影响 CA,调错直接时序错乱
CWmin/CWmax竞争窗口上下界无15/1023决定 CA 的冲突退让能力
max_attempt最大重传次数167超限丢帧,影响时延尾部

表格里最容易被忽略的是t_frame / tau这个比值。CSMA/CD 的效率本质上被它卡死:帧越短、传播时延越大,碰撞窗口占的比例越高,效率越低。仿真跑出来的曲线难看,很多时候不是代码写错了,是这个比值本身就不合理。

3. CSMA/CD 核心链路:监听、碰撞检测与二进制指数退避

3.1 帧到达生成:泊松到达怎么落到时间轴上

网络仿真的输入是业务流量,业务流量的常见建模就是泊松过程——帧间隔服从指数分布。这一步写错,后面所有统计都是垃圾,所以单独拿出来讲。

function arrivals = gen_arrivals(cfg) % GEN_ARRIVALS 生成 N 个节点在 [0, T] 内的帧到达时刻 % cfg.T : 仿真总时长(s) % cfg.N : 节点数 % cfg.lambda : 单节点到达率(帧/秒) % arrivals : N×1 的 cell,每个元素是该节点的到达时刻向量(升序) arrivals = cell(cfg.N, 1); for n = 1:cfg.N t = 0; buf = []; while true % 指数分布采样:-ln(U)/lambda,U 为 (0,1) 均匀随机数 gap = -log(rand()) / cfg.lambda; t = t + gap; if t > cfg.T, break; end buf(end+1) = t; %#ok<AGROW> end arrivals{n} = buf; end

逻辑说明:对每个节点独立采样指数分布的帧间隔并累加,超过仿真时长cfg.T就截断。-log(U)/lambda是指数分布的标准逆变换采样,比exprnd快,也不依赖统计工具箱。参数上,lambda的单位要和cfg.T保持同一量纲,都是秒;节点数N与lambda的乘积就是总到达率,它就是负载的横轴。

注意:循环里不要用rand('seed', x)这种老式写法,统一在入口脚本里rng(seed)一次,否则每次调用都把种子重置,生成的到达序列会周期性重复,曲线会出现莫名其妙的台阶。

3.2 发送主循环:监听、发送、碰撞检测三件事

单节点的 CSMA/CD 发送过程,本质就是一个「等空闲 → 发 → 看有没有撞 → 撞了退避重来」的循环。代码不复杂,但每一步的顺序不能乱。

function [t_start, t_end, n_collision] = csma_cd_node(node, cfg, t_arrive) % CSMA_CD_NODE 单节点的 CSMA/CD 一次完整发送过程(1-坚持式) % node : 节点编号,用于把自己从占用日志里剔除 % cfg : 全局配置 % t_arrive : 本帧到达时刻(s) tau = cfg.tau; slot = 2 * tau; % 碰撞窗口=退避时隙 t_now = t_arrive; attempt = 0; % 已发生的冲突次数 while true % 1) 监听:等到信道空闲 [busy, t_free] = channel_busy(t_now, cfg); while busy t_now = t_free; % 事件驱动,直接跳到释放时刻 [busy, t_free] = channel_busy(t_now, cfg); end % 2) 1-坚持式:一检测到空闲立即发送 t_start = t_now; t_end = t_start + cfg.t_frame; % 3) 边发边听:在 2τ 窗口内检测碰撞 if ~collide_during(t_start, t_end, node, cfg) n_collision = attempt; return; % 发送成功 end % 4) 发生碰撞:二进制指数退避后重来 attempt = attempt + 1; if attempt > cfg.max_attempt t_start = NaN; t_end = NaN; % 超过重传上限,丢弃该帧 n_collision = attempt; return; end r = backoff_beb(attempt, cfg); % 返回 0 ~ 2^k-1 个时隙 t_now = t_end + r * slot; % 退避从检测到碰撞后开始计时 end

逻辑说明:channel_busy返回的不只是忙闲标志,还有t_free,也就是这段占用最早的释放时刻。有了它,循环可以一次性跳过整段占用,而不是每次加一个固定步长去试探,这对仿真速度影响非常大——固定步长轮询是新手最容易踩的性能陷阱。

参数说明:cfg.t_frame是帧传输时间,等于帧长除以信道速率;cfg.max_attempt一般取 16,对应以太网的标准重传上限。r * slot里slot = 2τ,所以退避的物理含义是「等 r 个碰撞窗口」。

这里还有个细节:1-坚持式在信道一空闲就立刻发,多个节点同时等待时冲突概率很高;改成 p-坚持式只需在发送前加一次rand() < p判断,p 取 0.1~0.5 时中等负载下的表现会明显改善。代码里留了这个开关的位置,注释里也写明了,自己动手加三行就行。

3.3 二进制指数退避:k = min(i, 10) 这一步为什么不能省

退避函数本身只有几行,但取整方式一旦写错,曲线立刻失真。

function r = backoff_beb(attempt, cfg) % BACKOFF_BEB 二进制指数退避,返回本次应等待的时隙数 % attempt : 当前是第几次冲突(从 1 开始) % cfg.backoff_limit : 指数上限,标准取 10 k = min(attempt, cfg.backoff_limit); r = randi([0, 2^k - 1]); % 在第 i 次冲突后,从 0~2^k-1 中均匀取一个

逻辑说明:第 i 次冲突后,退避区间扩大到2^min(i,10)个时隙,也就是 2、4、8、16……最多到 1024 个时隙为止不再增长。min(attempt, 10)这一层截断极其关键——如果没有它,冲突次数一多,2^k会指数爆炸成天文数字,MATLAB 直接返回Inf,仿真要么卡死要么结果全错。这就是所谓的血泪经验:写退避一定先截断再取幂。

参数说明:cfg.backoff_limit标准值是 10,对应最大 1024 个时隙的退避窗口。想模拟「退避不够积极」的劣化场景,把它改成 5 或 6,会看到高负载端的碰撞率明显抬升,这是做对照实验时很好用的一个旋钮。

碰撞次数与退避窗口的关系,可以直观记成下面这张表:

冲突次数 i退避区间平均等待时隙
10~10.5
20~31.5
30~73.5
40~157.5
≥100~1023511.5

从表里能看出问题所在:冲突到第 10 次,节点平均要等五百多个时隙,时延尾部会拖得非常长,这就是 CSMA/CD 在高负载下时延抖动的根源。仿真跑出来的平均时延曲线在某个负载点突然翘起来,对照这张表就能解释清楚,不用猜。

4. CSMA/CA 核心链路:帧间隔、退避计数与 ACK 时序

4.1 DIFS/SIFS 与退避计数器:只有空闲时隙才减一

CSMA/CA 的退避和 CD 有个本质区别:CD 的退避是一次性睡够时间,CA 的退避是一个倒计数器,只有信道空闲一个时隙才减一,一旦检测到忙就冻结,等下一次空闲再接着减。这是两套协议最容易混淆的地方,代码里必须写对。

function [idle, t_out] = wait_idle(t_in, dur, cfg) % WAIT_IDLE 等待信道连续空闲 dur 秒 % t_in : 起始时刻 % dur : 需要连续空闲的时长(DIFS 或 SIFS) % idle : true 表示等到了 % t_out : 实际等到空闲的时刻 t_out = t_in; while true [busy, t_free] = channel_busy(t_out, cfg); if ~busy % 当前空闲,但要确认接下来 dur 内不被打断 [busy2, ~] = channel_busy(t_out + dur - cfg.eps, cfg); if ~busy2 idle = true; return; end t_out = t_out + cfg.slot; % 被插队,重新计时 else t_out = t_free; % 忙,跳到释放时刻重新等 end end

逻辑说明:wait_idle要保证的是「连续空闲 dur 秒」,不是「此刻空闲」。所以要在t_out + dur处再查一次,防止刚等到一半又被别的节点抢走。cfg.eps是一个极小量,用来避开区间边界的闭开问题。

参数说明:dur传cfg.DIFS或cfg.SIFS。DIFS 用于普通数据帧发送前的等待,SIFS 用于 ACK 这种需要抢占优先级的响应帧。SIFS 比 DIFS 短,所以 ACK 天然优先于其它节点的接入尝试,这个优先级机制就是靠时间差实现的,不需要额外的信令。

4.2 冲突窗口 CW 的增长与复位

CA 里没有碰撞检测,节点无法边发边听,只能靠「发完等 ACK,等不到就当冲突」来间接判断。所以退避窗口的增长策略就成了整个协议的核心。

function [t_start, t_end, cw, retry] = csma_ca_node(cfg, t_arrive) % CSMA_CA_NODE 单节点 DCF 基本接入过程(不含 RTS/CTS) cw = cfg.CWmin; % 初始竞争窗口,标准值 15 retry = 0; t_now = t_arrive; while true % 1) 等信道空闲 DIFS [ok, t_now] = wait_idle(t_now, cfg.DIFS, cfg); if ~ok, continue; end % 2) 退避倒计数:每个空闲时隙减 1,遇忙冻结 backoff = randi([0, cw]); while backoff > 0 if channel_busy(t_now, cfg) [~, t_now] = wait_idle(t_now, cfg.DIFS, cfg); % 冻结后重新等 DIFS else t_now = t_now + cfg.slot; backoff = backoff - 1; end end % 3) 发送数据帧 t_start = t_now; t_end = t_start + cfg.t_data; t_data_end = t_end; % 4) SIFS 之后等 ACK,等不到就翻倍重传 t_ack = t_data_end + cfg.SIFS; if ack_received(t_ack, cfg) return; % 成功,cw 已在下次发送前复位 end retry = retry + 1; if retry > cfg.retry_limit t_start = NaN; t_end = NaN; return; % 超限丢帧 end cw = min(2 * cw + 1, cfg.CWmax); % 窗口翻倍并复位计数器 end

逻辑说明:第 2 步是整个函数的关键。backoff初值在[0, cw]里均匀取,然后每经过一个空闲时隙减一;一旦channel_busy返回真,计数器冻结,重新等 DIFS,等到空闲后从冻结值继续往下减——注意是继续,不是重抽。这一步写错成「重新抽一个随机数」,退避就退化成无记忆过程,冲突率会明显偏高,曲线看起来和理论值差一截。

参数说明:cfg.CWmin标准取 15,cfg.CWmax取 1023,cfg.retry_limit取 7。cw = min(2*cw + 1, cfg.CWmax)这个+1不能省,因为竞争窗口的合法取值是2^n - 1这样的形式(15、31、63……),少加这个 1,窗口序列会整体偏移,长期跑下来冲突率偏高一点点,偏偏又不容易被发现。

4.3 ACK 与重传:没有确认就没有 CA

ack_received的实现方式,各家写法不一样,但逻辑得站得住。常见做法是:数据帧到达接收端后,接收端在 SIFS 之后回一个短 ACK 帧,发送端在这个时刻查询信道占用日志里有没有对应节点的 ACK 记录。

function ok = ack_received(t_ack, cfg) % ACK_RECEIVED 判断 t_ack 时刻是否收到了本节点的 ACK % 实现方式:接收端在正确解出数据帧后,会在 tx_log 里登记一段 SIFS+ACK 的占用 ok = false; log = cfg.tx_log; if isempty(log), return; end idx = find(abs(log(:,1) - t_ack) < cfg.eps & log(:,3) == cfg.rx_of(cfg.cur_node), 1); ok = ~isempty(idx);

逻辑说明:ACK 是否收到,取决于数据帧是否被接收端正确解析,而正确解析的前提是没有发生碰撞。所以在仿真里,碰撞检测的结果会直接决定接收端要不要生成 ACK 记录,ack_received只负责查表。这样的分工让「物理层碰撞」和「MAC 层确认」解耦,加不加 ACK 聚合、要不要模拟 ACK 丢失,都只改一处。

参数说明:cfg.rx_of是发送端到接收端的映射,点对点场景下通常固定;cfg.eps用于浮点时刻比较,取 1e-9 量级。这里必须用容差比较,直接==在浮点时间轴上几乎永远匹配不上,这是 MATLAB 仿真里非常经典的一类问题。

提示:如果你发现仿真里一个 ACK 都没收到,先别怀疑协议逻辑,先在ack_received里打个断点,看tx_log里到底有没有 ACK 记录。大概率是接收端根本没生成,而不是查询写错了。

4.4 隐藏终端与 RTS/CTS 开关

CA 的另一个绕不开的话题是隐藏终端:两个节点互相听不到对方,却都能和同一个接收端通信,载波监听失效,碰撞照撞不误。这套代码里预留了 RTS/CTS 的开关,打开后发送数据前先发一个短 RTS 帧,接收端回 CTS,其它听到 CTS 的节点按 CTS 里携带的时长设置虚拟载波监听。

实现上的改动量其实不大:在csma_ca_node的第 3 步之前插一段 RTS/CTS 交互,成功后把cfg.NAV设成一个截止时刻,channel_busy里把 NAV 也当成占用处理。这套代码把这段留成了注释掉的扩展位,想做成对照实验,自己填进去就行。

打开 RTS/CTS 之后,短帧场景的吞吐率通常会下降——RTS/CTS 本身的开销摆在那儿;但长帧加隐藏终端的场景下,提升非常明显。这个 trade-off 是二次开发时最值得跑出来的一组对照数据。

5. 跑通与排查:参数配置、结果曲线和五个必踩的坑

5.1 三步跑通:路径、参数、出图

环境要求很低,MATLAB R2016b 及以上都能跑,不依赖任何工具箱。步骤就三步。

第一步,把csma_sim整个文件夹加进路径:

addpath(genpath('csma_sim')); % 递归加入所有子目录 cd csma_sim;

第二步,打开config/cfg_cd.m,确认几个核心参数。默认给的是 20 个节点、单节点到达率 50 帧/秒、单程时延 5μs、帧长 1024 字节。跑第一遍建议先不动,先把基线跑出来。

第三步,执行入口脚本:

main_csma_cd; % 生成吞吐率-负载曲线,结果落到 results/ main_csma_ca; % 同样的横轴,方便两条曲线放一起比

跑完之后results/下会有.mat和.png。.mat里存的是原始统计数据,包括每个负载点的吞吐率、碰撞率、平均时延,想自己重画图直接从.mat里取,不用重跑仿真。这一点很实用——仿真跑一次可能几分钟,重画图只要一秒。

5.2 结果怎么读:三条曲线分别说明什么

出图默认三条线:吞吐率 S、碰撞概率 P、平均时延 D,横轴都是归一化负载 G。

吞吐率曲线在低负载段接近线性上升,因为几乎不冲突;到某个拐点之后增长趋缓,最后在高负载段掉头向下。CSMA/CD 的拐点通常出现在 G≈0.5 附近,CSMA/CA 因为多了帧间隔和 ACK 开销,拐点更靠左。如果你的曲线没有掉头这一段,一直平着走,八成是碰撞检测没生效,所有帧都被当成成功发送了。

碰撞概率曲线单调上升,这是正常的。要留意的是它在中低负载段的数值——如果 10 个节点、轻负载下碰撞率就已经超过 0.3,那是退避窗口设小了,或者时隙长度算错了。

平均时延曲线最有意思,低负载时几乎是常数,接近一帧的传输时间;过了拐点之后突然翘起来,尾部拖得很长。前面那张 BEB 退避表就是用来解释这个翘起的。三条线放在一起看,才能判断代码到底跑对没跑对。

5.3 常见问题排查:五个让我返工的坑

坑一:退避时隙取成了 τ 而不是 2τ

现象:吞吐率曲线整体偏高,高负载段也不怎么掉,看着「性能很好」。

原因:CSMA/CD 的碰撞窗口是2τ,退避时隙也必须是2τ。写成τ之后,退避时间只有应有的一半,节点更快地回来重试,冲突在统计上被平滑掉了,曲线自然虚高。

解决:统一从slot_time('cd', cfg)取时隙,不要在业务代码里手写2*tau或tau。所有涉及时间的计算都走这一个函数,改一处全局生效。

坑二:固定步长轮询导致碰撞漏检

现象:节点数一多,碰撞率反而不涨,甚至偶有下降。

原因:监听循环写成了t_now = t_now + 1e-6这种固定步长。步长一旦大于2τ,两次探测之间发生的短暂占用就被跨过去了,碰撞窗口被跳过,检测失效。

解决:改成事件驱动——channel_busy返回释放时刻t_free,直接跳过去。这套代码里的channel_busy就是为这个设计的,别嫌它多返回一个值。

坑三:随机种子没固定,两组曲线不可比

现象:同样的参数跑两遍,曲线形状差 10% 以上,还以为是算法不稳定。

原因:rng没设或者设在了循环内部,每个负载点用的随机序列不同,统计涨落被误当成趋势。

解决:在每组参数开始前设一次种子,并且让种子随参数变化,保证同参数可复现、不同参数相互独立。这一点在扫参时尤其重要。

坑四:CW 翻倍时漏了 +1

现象:CA 的冲突率比理论值高一点点,怎么调都对不齐。

原因:竞争窗口写成cw = 2*cw,而合法取值应该是2^n - 1序列(15、31、63……)。少加 1,每轮窗口都偏小,退避不够充分。

解决:写成cw = min(2*cw + 1, cfg.CWmax),并且把CWmin初始化为 15 而不是 16。

坑五:吞吐率分母用错

现象:S 的数值超过 1,或者低负载段明显偏低。

原因:分母用了「最后一次发送结束时刻」而不是「仿真总时长cfg.T」,或者把空闲期漏算了。吞吐率的定义是成功传输的数据量除以总时间,总时间必须是完整的仿真窗口。

解决:统一在stats_collect里用cfg.T做分母,成功字节数只累加那些真正收到 ACK 的帧。别在业务函数里各算各的。

6. 二次开发:把固定参数改成可扫参的实验平台

这套代码跑通之后,最有价值的改造方向是把它从「一次跑一组参数」变成「一次跑一张参数网格」。我一般会写一个独立的扫参脚本,跟入口脚本平级放,不污染原有逻辑。

N_list = [5 10 20 50]; lam_list = 10:10:200; S = zeros(numel(N_list), numel(lam_list)); for i = 1:numel(N_list) for j = 1:numel(lam_list) cfg = cfg_cd(); % 每次取一份干净的默认配置 cfg.N = N_list(i); cfg.lambda = lam_list(j); cfg.seed = 20240501 + i*100 + j; % 每组独立种子,可复现 rng(cfg.seed); res = run_csma_cd(cfg); % 单次仿真封装成函数 S(i, j) = res.throughput; end end save(fullfile('results', 'sweep_cd.mat'), 'S', 'N_list', 'lam_list');

两个关键点。一是cfg_cd()每次重新调用,返回的是全新的 struct,避免上一轮的脏字段串到下一轮;二是把单次仿真封装成run_csma_cd(cfg),主入口脚本反过来调用它,这样扫参和单跑共用同一套逻辑,不会出现「扫参结果和单跑对不上」的经典翻车。

验证方法也很直接:抽一组参数,扫参结果和单跑结果对比,吞吐率必须完全一致。不一致就说明封装的时候有状态没清干净,大概率是cfg.tx_log没重置。加一个cfg.tx_log = zeros(0,3);就能解决。

再往上一层,是把 RTS/CTS 做成cfg.use_rts开关,跑同一张网格两遍,两组S相减,隐藏终端带来的增益一目了然。这件事我做过的次数不少,每次都能发现一些反直觉的结论——比如节点数少的时候开 RTS/CTS 纯属拖累,节点数一多起来才开始回本。

从那以后我每次改仿真参数前,都会先固定种子、跑一遍基线、把.mat存下来再动手,不然两天后就再也说不清曲线变化是改对了还是随机波动。这套代码的结构本身已经把「可复现」的门槛降得很低,剩下的就是养成习惯。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询