用Matlab实现多址接入协议仿真:从ALOHA到CSMA/TDMA

📅 发布时间:2026/9/15 21:35:09
用Matlab实现多址接入协议仿真:从ALOHA到CSMA/TDMA
简介基于Matlab实现的多址接入协议仿真源码包面向通信工程、电子信息、计算机及相关专业的学习者适合作为课程设计、实验报告或毕业设计的辅助参考资料。资源围绕随机多址接入中的ALOHA、时隙ALOHA、非坚持CSMA等典型协议展开通过源码演示协议流程、参数配置和结果对比帮助理解信道争用、冲突避免与吞吐量分析等核心概念。压缩包内共6个文件全部为.m格式的Matlab脚本总大小仅7KB便于快速下载和按需修改目前已有141人学习下载。源码中既包含基础协议实现也有改进版本可对照观察不同访问策略对网络性能的影响读者在掌握一定Matlab基础后可自行调整参数、叠加噪声或增加节点数进一步验证协议特性。使用时需具备调试和扩展代码的能力并注意将这份源码作为思路参考而非直接照搬全部实现。1. 多址接入协议仿真到底在仿什么先想清楚再动手做无线通信系统仿真的人大概率会遇到这样一个痛点信道模型建得再精细调制解调性能再漂亮一旦把多个用户丢进同一个小区、同一条链路上系统吞吐量立刻崩掉。问题往往不出在物理层而是出在媒体接入控制MAC层的多址接入协议上。标题里这个“基于Matlab实现多址接入协议仿真源码”的项目本质上是想搭一条从协议逻辑到性能指标的标准化验证链路在同一个仿真平台上比较TDMA、FDMA、CDMA、ALOHA、CSMA这类接入机制在不同用户数、不同业务负载下的吞吐量、时延、丢包率和公平性。多址接入协议不是一套算法而是一族“多个节点如何共享同一份信道资源”的规则集合。写这类仿真最忌讳的就是上来就贴代码先跑通。你需要做的第一件事是把“节点数量—到达间隔—退避策略—信道状态”这四个要素抽象成可度量的数学模型再落成Matlab里的离散事件循环。本文会从协议建模切入带你在Matlab中实现一个可扩展的多址接入仿真框架并给出一个可以直接改参数复现的最小代码骨架。适合已经有Matlab基础、想从“单用户链路仿真”转向“多用户接入仿真”的通信方向工程师和学生。这套框架跑通之后换协议只是换一个函数的事情。2. 多址接入协议的建模粒度连续时间、离散事件与状态机2.1 为什么不能用“逐时隙扫描”替代事件驱动很多初学者写第一个多址仿真习惯做法是把时间切成极小的时间片比如1微秒一个步长在每个时间片上依次让所有节点执行“是否有新包到达、信道是否忙、是否发送”的判断。这种做法在业务量极低时勉强能跑但只要节点数超过几十个、业务负载超过信道容量的20%仿真时间就会呈指数级膨胀因为绝大部分时间片里没有任何事件需要处理。在Matlab里实现高效的协议仿真核心思想是离散事件驱动仿真时钟只在“有事情发生”的时刻向前跳跃这些时刻包括新包到达、发送开始、发送结束、超时重传决定、退避计时器到期。每个事件按时间顺序放入事件队列主循环每次取出队首事件调用对应的事件处理函数函数执行过程中可能生成新事件并插入队列。这样仿真的时间开销只与事件总数成正比而与仿真时长无关。2.2 协议动作的公共抽象节点状态与信道状态把多址协议拆开看每个节点在最小粒度上都只有四种状态空闲Idle、退避Backoff、发送Transmit、等待确认WaitACK。信道状态只有两种忙Busy、空闲Idle。所有多址协议之间的差异本质上就是“状态转移的条件”和“转移后等待的时长”不同。以纯ALOHA为例状态转移极其简单节点有包就直接发发完立刻监听信道是否冲突CSMA/CA则引入载波监听和随机退避节点不能在信道忙时发送退避计时器挂起TDMA则是把“能否发送”这个条件改成了“当前时隙是否属于本节点编号”。在Matlab里建模时比较推荐的做法是定义一个MACNode结构体数组每个节点记录当前状态、剩余退避时间、等待确认的定时器、已发送包数、已冲突包数。协议切换时只改事件触发逻辑不改数据结构和主循环框架。这里有一个经常被忽略的建模细节ACK与超时定时器。真实系统中接收端收到正确数据帧后会回复确认帧发送端只有在收到ACK后才认为发送成功。但很多课程设计的仿真为了省事默认信道没有噪声、冲突是唯一的丢包原因于是直接省略了ACK把“发送结束未冲突”等价于“发送成功”。这种简化对于纯ALOHA这种无监听协议还能接受但对于CSMA这类依赖ACK触发退避的协议省略ACK会让退避算法完全失真。2.2.1 仿真参数表的预先定义无论你打算实现哪几种多址协议下面这张参数表建议在写代码之前先固定下来所有协议共用同一份配置保证对比时只有接入机制这一个变量参数名符号默认值说明仿真时长simTime10单位秒配合数据速率使用节点数量numNodes30接入同一信道的竞争节点数数据速率dataRate1e6单位bps决定帧传输时间帧长度frameSize1000单位bit固定帧长便于算吞吐到达间隔均值lambda0.005单位秒泊松到达的平均间隔传播时延propDelay1e-6单位秒用于CSMA的监听判断重传次数上限maxRetry5超过后丢弃该帧运行次数numRuns30每次独立种子取平均固定帧长的好处在于传输时间可以直接计算txTime frameSize / dataRate这就避免了在事件队列里维护一个“发送剩余时间”的变量事情会简单很多。真实系统中帧长是变化的仿真阶段固定帧长不影响协议对比的相对结论。3. 用Matlab实现最小可运行的多址仿真框架3.1 事件队列与仿真时钟的数据结构在Matlab中实现离散事件仿真可以不用面向对象的classdef但那会让你在协议切换时付出沉重的维护代价。这里给一个折中方案用结构化数组实现事件队列用一个struct承载全局状态协议差异通过函数句柄注入。这样代码量不大可读性和扩展性都有保证。事件队列的定义和操作函数如下function events eventPush(events, time, type, nodeId, extra) % events: 结构体数组含字段 t(时间), type(事件类型), nodeId(节点编号), extra(附加数据) % 事件类型: 1包到达, 2发送开始, 3发送结束, 4退避结束 ev struct(t, time, type, type, nodeId, nodeId, extra, extra); % 插入排序保证队首始终是最早发生的事件 if isempty(events) events ev; else idx find([events.t] time, 1, first); if isempty(idx) events(end1) ev; else events [events(1:idx-1), ev, events(idx:end)]; end end end主循环从事件队列里取出events(1)执行然后删除该元素。因为每次插入都排序取事件是O(1)操作插入是O(n)操作节点数几百个以内性能完全够用。这套结构比用timer对象或for循环扫时间轴要快一个数量级以上也更容易加断点观察某个具体时刻的信道状态。3.2 主循环框架与统计计数器全局状态我们用state结构体承载state.channelBusy标记信道忙闲state.nodeStatus是一个numNodes x 1的数组记录每个节点当前所处状态码state.collisionFlag表示当前正在发送的帧是否已经冲突。统计量分四类发送帧数txCount、冲突帧数collisionCount、成功帧数successCount、平均时延delaySum。function [stats, events] runSimulation(params, protocolFunc) % 初始化状态 state struct(channelBusy, false, collisionFlag, false, ... nodeStatus, zeros(1, params.numNodes)); stats struct(txCount, 0, collisionCount, 0, ... successCount, 0, delaySum, 0); events eventInit(); % 为每个节点生成首个包到达事件 for n 1:params.numNodes tArrival exprnd(params.lambda); if tArrival params.simTime events eventPush(events, tArrival, 1, n, struct(retry, 0)); end end % 主循环 while ~isempty(events) ev events(1); events(1) []; if ev.t params.simTime break; end % 时钟推进 state.curTime ev.t; % 分派事件到协议处理函数 [stats, state, events] protocolFunc(ev, stats, state, events, params); end end这段代码的逻辑要点在“生成首事件”和“时钟推进”两处。首事件生成用exprnd(params.lambda)而不是均匀分布是为了让包到达过程符合泊松过程假设——这是通信系统性能分析的标准前提也是理论公式对比时的统一假设。时钟推进不做任何物理时间运算只跟随事件时间戳所以即使simTime设成10秒实际CPU运行时间也只有几毫秒到几秒。3.3 纯ALOHA协议的最小实现用上面框架写纯ALOHA只需要处理两类事件包到达、发送结束。function [stats, state, events] alohaProtocol(ev, stats, state, events, params) txTime params.frameSize / params.dataRate; switch ev.type case 1 % 包到达直接发送检查信道冲突 if state.channelBusy % 已有别的节点在发冲突 stats.collisionCount stats.collisionCount 1; state.collisionFlag true; else state.channelBusy true; end state.nodeStatus(ev.nodeId) 1; % 发送中 stats.txCount stats.txCount 1; events eventPush(events, ev.t txTime, 3, ev.nodeId, ev.extra); case 3 % 发送结束释放信道记录结果 state.channelBusy false; state.nodeStatus(ev.nodeId) 0; if state.collisionFlag stats.collisionCount stats.collisionCount 1; % 重传处理若未超重试上限安排一个随机退避后的重传 if ev.extra.retry params.maxRetry backoffTime rand() * 2 * params.lambda; events eventPush(events, ev.t backoffTime, 4, ev.nodeId, ... struct(retry, ev.extra.retry 1)); end else stats.successCount stats.successCount 1; end state.collisionFlag false; case 4 % 退避结束重新发送 state.channelBusy true; stats.txCount stats.txCount 1; state.nodeStatus(ev.nodeId) 1; events eventPush(events, ev.t txTime, 3, ev.nodeId, ev.extra); end end注意看这个实现里的一个隐蔽问题当两个节点的包到达事件发生在同一时刻第一个包的发送事件会立即把channelBusy置为true第二个包到达时会发现信道忙这种“同时刻事件”处理是合理的它模拟了物理层冲突检测。但如果在同一时刻有两个节点同时启动发送且都设置了collisionFlag true发送结束后计数会重复计算——这就是离散事件仿真里经典的“同时刻事件顺序”问题后面排错章节会专门讲。纯ALOHA跑完后吞吐量用successCount * frameSize / simTime计算单位是bps。理论上纯ALOHA最大吞吐在负载G0.5时达到18.4%的信道利用率你用上面代码跑出来应该在0.15到0.2之间偏差太大就要回去检查事件处理是否有逻辑漏洞。4. 从ALOHA到CSMA/TDMA协议扩展与参数标定4.1 Slotted ALOHA与时隙对齐的实现技巧纯ALOHA改成Slotted ALOHA关键区别是发送必须对齐到固定时隙边界。实现上有两种常见做法一种是在事件处理函数里计算nextSlot ceil(ev.t / slotTime) * slotTime把发送事件钉在下一个时隙开始另一种更省事——在初始化时就生成一个全局的时隙边界数组所有发送事件都找最近的边界对齐。这里推荐第一种因为时隙长度如果定义为帧传输时间的整数倍边界计算就是纯算术运算不会引入额外事件。slotTime params.frameSize / params.dataRate; % 在包到达处理分支中 startTime ceil(ev.t / slotTime) * slotTime; events eventPush(events, startTime, 2, ev.nodeId, ev.extra);这个改动对性能影响极大纯ALOHA冲突窗口是2倍帧传输时间而Slotted ALOHA冲突窗口缩减到1倍帧传输时间理论最大吞吐翻倍到36.8%。你在Matlab中跑对比实验时最直观的验证方式就是固定lambda不变看吞吐量是否接近翻倍。4.2 非坚持CSMA的载波监听与退避设计CSMA类协议比ALOHA多了一个动作发送前先监听信道。在离散事件仿真中这个“监听”不是一个持续的过程而是在发送事件执行的那个时刻查看state.channelBusy。非坚持CSMA的规则是信道忙就等待一个随机时间后重试信道空闲就立即发送。看到这里你应该意识到这个逻辑和ALOHA的退避重传几乎一样区别只在“是否先看信道状态再决定发不发”。case 1 % 包到达 if state.channelBusy % 信道忙非坚持随机延迟后重听 waitTime rand() * params.lambda * 2; events eventPush(events, ev.t waitTime, 4, ev.nodeId, ev.extra); else % 信道空闲直接发送 state.channelBusy true; stats.txCount stats.txCount 1; events eventPush(events, ev.t txTime, 3, ev.nodeId, ev.extra); end case 4 % 重监听 if state.channelBusy waitTime rand() * params.lambda * 2; events eventPush(events, ev.t waitTime, 4, ev.nodeId, ev.extra); else state.channelBusy true; stats.txCount stats.txCount 1; events eventPush(events, ev.t txTime, 3, ev.nodeId, ev.extra); end这段代码看起来有重复但它恰恰反映了非坚持CSMA的建模核心退避完成后不需要检查重试次数是否超限因为监听不产生冲突只是延迟发送。真正可能产生冲突的情况是传播时延大于0时两个节点同时监听到“空闲”——仿真中把传播时延设成一个大于0的数可以复现这个场景这也是CSMA吞吐量永远达不到100%的根本原因。4.2.1 CSMA的参数敏感性实验设计CSMA协议对传播时延极度敏感。建议做一组对比实验固定其他参数把propDelay从0扫到5e-6对应1.5km距离的传播时延步长1e-6观察吞吐量变化。在Matlab里跑这个扫描不需要大改代码只要在runSimulation外面包一层for循环即可。实际跑下来你会发现一个明显拐点传播时延超过帧传输时间的1%后吞吐量开始显著下滑这是因为载波监听的“先听后说”机制在长传播时延下信息滞后严重冲突概率上升。4.3 TDMA时隙分配在不连续业务下的吞吐天花板TDMA的实现比随机接入协议简单得多每个节点只在属于自己的时隙内发送没有冲突无需退避。在仿真中把“包到达”事件和“时隙边界”事件关联即可。但这里有一个建模陷阱如果节点在非本节点时隙有包到达它必须等到下一个自己的时隙才能发送引入的等待时延就是“排队时延”。没有队列管理逻辑的TDMA仿真往往会低估实际时延。协议冲突窗口理论最大吞吐实现复杂度纯ALOHA2倍帧时18.4%低Slotted ALOHA1倍帧时36.8%低非坚持CSMA随传播时延变化接近100%理想中TDMA无100%固定帧长中这张表是你在写论文或做技术方案对比时的标准参考。仿真结果和理论值的偏差控制在10%以内就说明你的事件驱动框架是正确的偏差过大优先检查统计计数逻辑而不是协议规则。5. 结果分析与调试吞吐量、时延、丢包率三个必查点5.1 吞吐量、时延与丢包率的统计口径仿真代码写完最容易被质疑的就是统计口径。吞吐量建议用“成功接收的比特数/仿真时长”而非“发送的比特数/仿真时长”后者会把冲突导致的无效传输也算进去数值虚高。平均时延建议只统计成功接收的帧从包的“第一个比特准备好发送”开始计时到“最后一个比特被成功接收”为止——这个口径和网络仿真工具NS-3的定义一致方便后续做跨平台对比验证。丢包率分母用“所有到达的包总数”分子是“超过重试上限被丢弃的包数”注意不要和冲突率混为一谈冲突只是丢包的诱因不是丢包本身。5.2 三个高频错误的排查方法第一个高频错误是同时刻事件计数重复。两个及以上节点在同一时刻产生发送事件时发送结束处理函数会为每个节点各执行一次冲突计数而真实冲突只需要记一次。修正办法是给发送结束事件加上一个共享标识——所有在同一时刻开始发送的事件携带相同的冲突编号发送结束后只计数一次。在Matlab里可以用ev.extra.conflictTag字段实现。第二个高频错误是退避计时器没有随信道状态挂起。这在CSMA的实现中尤其常见节点在退避过程中信道变忙退避计时器应该暂停计时待信道空闲后继续。如果你的实现让退避计时器自顾自地走仿真结果会表现为负载高时丢包率反而下降这个反直觉现象是退避逻辑错误的典型信号。第三个高频错误是随机数种子未隔离。每个独立仿真运行前调用rng(runIndex)设置种子保证不同参数配置之间可以复现、可以对比。很多仿真结果画出来曲线毛刺极多不是因为算法有问题而是因为每次都重新生成随机数序列。5.3 用批量实验快速验证协议差异把整个仿真框架跑成批处理实验常用做法是写一个脚本遍历负载值lambdaList [0.001, 0.002, 0.005, 0.01, 0.02, 0.05]; throughput zeros(length(lambdaList), numRuns); for i 1:length(lambdaList) for r 1:numRuns rng(r); params.lambda lambdaList(i); [stats, ~] runSimulation(params, alohaProtocol); throughput(i, r) stats.successCount * params.frameSize / params.simTime; end end meanThroughput mean(throughput, 2); plot(lambdaList * params.frameSize / params.dataRate, meanThroughput);注意横坐标的换算lambda * frameSize / dataRate就是“到达负载G”它表示单位信道时间内新到达的业务量量纲为1。用G作为横轴不同数据速率的仿真结果可以直接对比这是多址接入协议性能分析的标准坐标选择。负载轴用“到达速率”而不用“到达间隔”能让曲线更加平滑便于观察ALOHA的“吞吐回落”现象——负载超过0.5后吞吐量不升反降这个指标性拐点是协议局限性的最直观体现。本文还有配套的精品资源点击获取