
简介计算机网络课程设计资料《模拟Ethernet帧的发送过程.doc》面向计算机相关专业学生聚焦以太网CSMA/CD协议提供一份完整可参考的课设实现方案。内容从知识背景讲起涵盖网络协议、以太网、CSMA/CD机制与截断二进制指数退避算法并详细讲解用两个线程模拟两台主机、以双字变量Bus模拟总线、通过或操作发送数据及冲突检测与随机退避的完整流程同时按初始化、发送、冲突处理、输出和结束判断划分功能模块便于用C/C/VC/VB/JAVA等语言实现。文档还包含课程设计任务书、目录结构与报告撰写框架并给出时间安排和输出报告示例可帮助读者快速理解模拟逻辑并对照完成自己的课程设计。压缩包仅含1个doc文件大小663KB已有164人学习适合计算机网络原理、数据链路层实验及CSMA/CD仿真课设参考。1. 一份课设文档怎么把Ethernet帧从纸面送到线上计算机网络课设里模拟Ethernet帧的发送过程是出现频率最高的题目之一。大多数人的卡点不在“知不知道帧格式”——前导码、MAC、类型、FCS都能背——而在“怎么把一堆字段变成一串能发出去的字节”。这份文档本质上就是干这个的从帧结构拆解、CRC32校验计算到CSMA/CD发送时序和帧间隙的处理把发送端该做的事按步骤写全了。适合三类人被课设卡住的学生、准备网络方向面试想补实操的人、以及想拿一份完整代码改造进自己项目里的从业者。我拆完这份文档后验证了一件事按它的流程走Wireshark里确实能看到自己构造的帧而且CRC是绿色的Good状态。2. Ethernet帧结构前导码到FCS每个字段的字节序都不能错2.1 帧头字段逐一拆解Ethernet II帧这个课设模拟的就是它也就是不含VLAN Tag的untagged帧从上到下依次是7字节前导码Preamble、1字节帧起始定界符SFD、6字节目的MAC、6字节源MAC、2字节类型/长度、数据、4字节FCS。先纠正一个高频误解前导码和SFD在物理层就被消费掉了抓包工具默认不显示这部分。但从“发送过程模拟”的角度你必须把它们拼进字节流里再送出去因为接收方是靠前导码来同步时钟的。前导码是7个0xAA整个比特流是10101010交替排列SFD是0xAB它的特殊之处在于最后两位是11打破了交替模式接收方一看到这个就认定“帧头在这”。字段长度取值/说明前导码7字节0xAA×7比特流10101010交替用于时钟同步SFD1字节0xAB最后两位11标识帧起始目的MAC6字节单播/广播/组播地址源MAC6字节网卡地址从左到右逐字节发送类型/长度2字节≥0x0600是类型0x0800IPv4≤0x05DC是长度数据46~1500字节不足46字节补PaddingFCS4字节CRC32范围从目的MAC到数据末尾这里最容易被忽略的是“类型/长度”字段的双重身份。早期802.3标准用这个字段表示长度Ethernet II用这个字段表示上层协议类型后来用0x0600作为分界线来区分两者。课设里只需要记住填0x0800表示后面是IP包填0x0806表示ARP这个字段决定接收方把payload交给上层的哪个协议栈。如果你填了一个小于等于1500的数对端会认为你发的是802.3帧整个解析顺序就乱了。2.2 最小帧长与Padding规则为什么最小帧长是64字节而且是从目的MAC算到FCS末尾前导码和SFD不算这是CSMA/CD机制逼出来的发送站在发送过程中要能检测到冲突就必须保证最远的两个站之间一帧还在线上没发完。64字节在10Mbps、最长电缆距离的约束下正好满足“发完之前能听到冲突”这个条件。这个约束已经从历史问题变成了工程规范所以模拟发送时必须遵守数据少于46字节就补0凑到46字节加上14字节帧头DASAType和4字节FCS正好64。最大帧长1518字节对应数据1500字节这就是MTU最大传输单元的由来。超过1518就是巨型帧Jumbo Frame课设阶段不用处理但你要知道为什么“1500”这个数字在网络配置里到处出现——它就是从Ethernet帧结构里带出来的。后面做IP分片、TCP MSS调整时计算基数是1500而不是1518就是因为帧头14字节和FCS 4字节不算有效载荷。2.3 CRC32的算法选择与计算范围FCS用的是CRC32更准确地说是CRC-32/ISO-HDLC这个变体多项式0x04C11DB7初值全1结果再取反。Python里直接用zlib.crc32就行它用的就是这个变体。算出来的值按小端字节序little-endian写进帧尾Wireshark就会报Good CRC。容易踩坑的是计算范围从目的MAC的第一个字节开始到数据含Padding的最后一个字节结束。前导码和SFD不参与计算FCS自己也不参与计算。我见过不少同学把前导码也一起丢进CRC计算结果对端永远报Bad CRC。这个问题不细看很难发现因为打印出来的帧长度看起来是对的但接收端的校验结果是错的。具体怎么定位这个坑第4章展开讲。3. 模拟发送流程从上层数据到线上字节流的四步落地3.1 发送端的状态机空闲、等待IFG、发送、冲突退避把“发送”拆成状态机是这份课设文档里的核心思路也直接对应了真实网卡的链路层行为。发送端不是“拿一帧直接扔出去”而是一个循环状态机先监听信道介质忙就继续等介质空闲后还得等一个帧间隙IFGInter-Frame Gap96 bit time这是给接收方处理上一帧的时间然后才开始发前导码SFD帧体发送过程中如果检测到冲突立即停止发送执行截断二进制指数退避Truncated Binary Exponential Backoff随机等待0到2^n-1个时隙再重发。这里n是重传次数n超过10之后上限固定为10重传16次仍冲突就丢弃这一帧。没有实操经验的人容易把IFG忽略掉。在纸上模拟没感觉一旦接到真实链路两台机器配置成相同速率直连连续快速发两帧第二帧大概率被丢弃原因就是没等IFG。帧间隙的单位是bit time10Mbps下是9.6微秒100Mbps下是0.96微秒1000Mbps下是96纳秒。所以它不是一个固定sleep值必须跟链路速率挂钩换算。文档里用的是“96 × bit_time”的写法而不是写死一个微秒数这个设计在答辩时是一个加分点。3.2 用Python构造一帧字段拼接与CRC计算下面这段代码是文档里最核心的部分我整理成可直接运行的版本。它把一个IP数据包封装成完整的Ethernet II帧包含前导码和SFDimport zlib import struct import time import random PREAMBLE b\xaa * 7 # 前导码10101010交替用于接收方时钟同步 SFD b\xab # 帧起始定界符最后两位是11打破交替模式 MIN_FRAME 64 # 最小帧长从目的MAC到FCS末尾不含前导码和SFD def build_frame(dst_mac: bytes, src_mac: bytes, ethertype: int, payload: bytes) - bytes: 构造一帧Ethernet II帧返回包含前导码SFD的完整字节流 # 1. 数据不足46字节时补0保证帧长64 if len(payload) 46: payload payload b\x00 * (46 - len(payload)) # 2. 拼接14字节帧头目的MAC 源MAC 类型/长度 header dst_mac src_mac struct.pack(!H, ethertype) frame_body header payload # 3. CRC32计算范围帧头数据结果按小端写入4字节FCS fcs struct.pack(I, zlib.crc32(frame_body)) # 4. 完整帧 前导码 SFD 帧体 FCS return PREAMBLE SFD frame_body fcs这段代码的逻辑可以拆成四步看。第一步的Padding最容易漏漏了帧长小于64字节接收方或交换机会直接丢弃。第二步的类型字段用struct.pack(!H)按网络字节序大端打包注意MAC地址是逐个字节从左到右发送所以直接拿bytes传递不要转成整数再做字节序转换。第三步是关键zlib.crc32只接受bytes返回的是uint32整数用“”号小端pack成4字节才是线上FCS的实际字节顺序。如果你用“!H”这种方式按大端写入Wireshark一定报Bad CRC。第四步把前导码放在最前面模拟物理层发送时第一个字节就是0xAA。3.3 模拟发送循环IFG等待与冲突退避只有构造函数还不够发送过程本身要模拟信道竞争。常见做法是封装一个EthernetSender类把IFG和退避逻辑都收进去。我在文档基础上补全了参数处理class EthernetSender: def __init__(self, src_mac: bytes, speed: int 100_000_000): self.src_mac src_mac self.bit_time 1.0 / speed # 一个比特的传输时间单位秒 self.retry 0 def wait_ifg(self): ifg_bits 96 # 帧间隙固定96 bit time time.sleep(ifg_bits * self.bit_time) def backoff(self): n min(self.retry, 10) # 重传次数超过10后上限固定为10 slots random.randint(0, 2**n - 1) # 随机等待0~2^n-1个时隙 time.sleep(slots * 512 * self.bit_time) # 1个时隙512 bit time self.retry 1 def send(self, frame: bytes): self.wait_ifg() # 信道空闲后先等帧间隙 # 真实场景在这里监听信道、检测冲突模拟场景用打印代替 print(f发送 {len(frame)} 字节, 帧长(不含前导码) {len(frame) - 8}) self.retry 0 # 发送成功重传计数清零两个方法值得单独说明。wait_ifg把96比特换算成实际sleep时间它依赖初始化时传入的速率这比写死sleep(0.0000096)要通用得多——把speed改成1000_000_000同一份代码就适配千兆链路。backoff实现的是截断二进制指数退避第一次冲突随机等0或1个时隙第二次0到3个第三次0到7个以此类推。n用min(self.retry, 10)做上限这是802.3标准里明确写的。课上如果只要求“模拟发送过程”而不要求冲突检测backoff方法可以留空或者只打一条日志但答辩时能说清楚它的计算逻辑分数通常不一样。4. 避坑记录FCS范围、字节序与帧间隙四个高频翻车点课设文档里最值钱的不是框架代码而是这些让项目“从能跑到跑对”的坑。以下四条是文档里明确标注、也是我实际复现时踩过的。4.1 FCS算不对抓包永远是Bad CRC现象用Wireshark抓自己发的包协议栏里FCS显示红色Bad CRC接收端直接丢帧但发送端打印日志看起来一切正常。原因两处。其一CRC计算范围错了把前导码和SFD也算了进去其二zlib.crc32的结果用大端写入帧尾。解决CRC计算范围严格限定在“目的MAC到数据末尾”。zlib.crc32返回的uint32按小端字节序写入帧尾。校验方式可以写一个接收端解析函数把收到的帧的FCS和重新计算的CRC比对不一致就说明发送端封装有问题。这个坑的隐蔽之处在于打印帧长度看不出来问题只有上抓包工具才能暴露。4.2 数据不足46字节不补Padding帧被静默丢弃现象payload是20字节的ICMP回显发送端显示发送成功接收端抓包工具能看到这个帧但Wireshark提示帧长invalid或者接收端驱动直接丢弃。原因20字节payload加14字节帧头等于34字节小于64字节最小帧长。很多交换机和网卡驱动对短帧直接丢弃或者按错误帧处理。解决在封装阶段强制补齐。判断条件if len(payload) 46就补0把payload填充到46字节。注意补的是数据段不是往帧头塞东西。补充一句这个Padding在接收端不会被剥离它属于frame的一部分上层的IP解析会通过IP头里的总长度字段来识别真实数据的边界。4.3 类型字段当成“协议号”随便填现象类型字段填了0x0000或0x0001抓包工具把这一帧识别成802.3而不是Ethernet II后面的解析全乱。原因类型字段同时承载“长度”和“协议类型”两种语义分界线是0x0600十进制1536。小于等于0x05DC十进制1500按长度解析大于等于0x0600按类型解析。解决模拟发送时填常用值0x0800是IPv40x0806是ARP0x86DD是IPv6。如果你想模拟的确实是802.3长度帧那就故意填实际长度值——但课设题目里写的是“Ethernet帧”默认就是Ethernet II格式。顺带提一句带VLAN Tag的802.1Q帧是在源MAC和类型字段之间多塞了4字节TPID 0x8100加2字节TAG课设里先做untagged帧别混。4.4 帧间隙sleep时长大错位现象两台机器直连发送端一次性发1000帧接收端收到的少于1000而且丢帧没有明显规律。原因帧间隙没等或者等错了。有的同学把96 bit time当成96微秒用在100Mbps下实际等了9.6倍的时间有的干脆不等导致接收方DMA还没处理完上一帧新帧就来了。解决用bit_time 1/speed换算sleep(96 / speed)。如果是1000Mbps96 bit time等于96纳秒Python的time.sleep精度不够这时候可以用忙等或者直接忽略IFG——但在文档里必须把这个换算关系写清楚因为答辩时老师很可能问“你模拟的是几Mbps的链路IFG等了多久”另外一条字节序相关的坑也值得记MAC地址是逐字节发送目的MAC的第一个字节比如00:11:22里的0x00最先上线路内部不存在字节序问题。但有人习惯把MAC转成int再按大端pack结果地址逐字节颠倒。处理方法很简单MAC从头到尾当成bytes处理不要转int不要做任何字节序转换。5. 验证发送结果Wireshark对拍与接收端CRC复算5.1 抓包对拍确认帧被识别为Ethernet II模拟代码写完第一件事不是看打印日志而是抓包。常见做法是本机回环验证构造完帧后用raw socket发到lo回环接口Wireshark里选择loopback接口抓包。正常结果应该看到协议列显示“Ethernet”帧头里的目的MAC、源MAC、类型0x0800都正确FCS标记为Good。这里有个值得注意的现象Wireshark默认不显示前导码和SFD因为它们在物理层已经被消费掉了。如果你抓到的包里有前导码说明你发的不是标准帧。另外如果你的帧被识别成“802.3”或者“Unknown”优先检查类型字段——按照第4.3节的方法把它改成0x0800再看。5.2 写一个接收端解析函数自己校验自己抓包只能看外在表现内部字段的校验还得靠代码。对文档里的模拟发送端我补了一个对称的解析函数形成一个完整的收发闭环def parse_frame(frame: bytes): 解析不含前导码的Ethernet帧返回各字段和CRC校验结果 # 帧格式: DA(6) SA(6) Type(2) Payload FCS(4) dst_mac frame[0:6] src_mac frame[6:12] ethertype struct.unpack(!H, frame[12:14])[0] payload frame[14:-4] fcs_recv frame[-4:] # 重新计算CRC并与帧尾FCS比对 crc struct.pack(I, zlib.crc32(frame[:-4])) ok (crc fcs_recv) return { dst_mac: dst_mac.hex(), src_mac: src_mac.hex(), ethertype: hex(ethertype), payload_len: len(payload), crc_ok: ok }这个函数把解析流程拆成五个字段和封装流程完全对称。fcs_recv取帧的最后4字节crc从frame[:-4]重算两者相等说明发送端的封装和接收端的解析对同一个帧格式达成了共识。把build_frame的输出喂给parse_frame应该打印出crc_ok: True。这一步验证的是“自洽性”——课设答辩时被问“你怎么证明自己模拟的是对的”抛这个函数比说一万句都有说服力。5.3 边界用例最小帧、广播帧与最大帧验证完整性的另一个办法是跑边界用例。我一般会构造三帧来测试第一帧payload只有1字节验证Padding后帧长必须是64第二帧目的MAC全FF验证广播地址第三帧payload 1500字节验证最大帧长不越界。广播帧需要多说一句目的MAC ff:ff:ff:ff:ff:ff交换机收到后从所有端口转发接收端看到全1的目的地址就知道这是广播直接收下送交上层处理。代码里用b\xff * 6来构造不是把字符串“ff”直接拼进去。这三帧跑过一遍帧格式的边界条件就全部覆盖了。6. 把模拟发送接到真实网卡raw socket与硬件方向的两个延伸文档停在软件模拟就够交课设了但如果你想在答辩时多亮一手或者想把这套逻辑用到真实链路上有两个方向值得补。第一个是raw socket在Linux下用AF_PACKET套接字把build_frame构造出的字节流直接发到物理网卡绕过内核协议栈的封装。第二个是向硬件PCS/PMA层延伸——在千兆、2.5G Ethernet的PCS/PMA或SGMII场景里帧在进入线路之前还要经过8b/10b编码帧间隙和最小帧长的计算单位从字节变成编码字符这是另一个层面的事。import socket def send_raw(ifname: str, frame: bytes): 通过AF_PACKET原始套接字发送Ethernet帧frame从目的MAC开始前导码由驱动补充 sock socket.socket(socket.AF_PACKET, socket.SOCK_RAW) sock.bind((ifname, 0)) # 绑定到指定网卡0表示接收所有协议 sock.send(frame) # frame不含前导码驱动会自动补齐SFD sock.close()这段代码有一个容易搞混的点raw socket发送时frame从目的MAC开始即可前导码和SFD由网卡驱动补齐。这跟软件模拟有个差异——软件模拟把物理层职责也模拟了前导码要自己拼真实网卡驱动会接管物理层这部分工作。另外bind的第二个参数填0会接收该网卡上的所有协议如果你这个程序还要同时收包建议改成明确协议号避免收进来一堆无关流量影响调试。从那以后我每次写帧发送相关的课设或工具都强制走一遍“封装→抓包对拍→边界用例”的流程哪怕只是改了一个类型字段的值。Wireshark的CRC状态是绿色还是红色是最诚实的结果反馈代码里写得再漂亮都不如这一个小灯靠谱。希望帮到你。本文还有配套的精品资源点击获取