简介这是一套网络测量课程的拓展实验资源基于Mininet和Ryu的软件定义网络SDN测量实验提供完整Python源码、文档说明与使用指南适合计算机、通信、自动化、电子信息等专业学生用于课程设计、毕业设计或项目初期演示。压缩包共23个文件主体为12个Python脚本覆盖EverFlow流量测量、Ryu测试等实验逻辑另含6张拓扑与模块结构图、2个HTML说明页、README与LICENSE文件整体仅549KB环境依赖少、结构清晰便于快速部署和二次修改。目前已有164人学习浏览可参考高清晰拓扑图和代码注释快速还原SDN测量场景。代码经过运行验证附带使用说明与关键排错思路能帮助理解Mininet自定义拓扑、Ryu控制器交互、流量监控与规则下发流程也支持在此基础上扩展路由策略、QoS统计或数据可视化满足课程报告与答辩展示需要。1. 把 SDN 网络测量从「黑匣子」变成「可观测」Mininet Ryu 这套实验到底在做什么网络测量课程里最常见的困境是教材讲了 SNMP、NetFlow、sFlow 的原理但拓扑是画在纸上的流量是模拟出来的你压根看不到数据包真正经过哪条链路、交换机里流表项怎么变化。用 Mininet 模拟真实网络拓扑、用 Ryu 控制器下发流表再通过自定义 Python 脚本测量链路延迟和丢包率这套基于 SDN 的扩展实验把「测量」从理论概念变成了可以亲手验证的过程。它对想做毕设、课程设计或者入门 SDN 控制器的开发者都很实用代码量不大但把控制器、交换机、主机和测量脚本串成了一条完整的链路。我拿到这份资源的第一反应是它不像很多「课程设计」那样只给一个孤零零的 Python 文件而是把整个实验闭环拆开了。里面有基于 Ryu 的 REST 控制器代码、Mininet 拓扑脚本、流量生成脚本还有 README 文档说明。接下来我会按实际动手的顺序把它拆成环境搭建、拓扑构建、控制器实现、数据生成、踩坑记录和扩展验证这几个部分每个步骤都会给出可以跟着跑的代码和参数说明。2. 实验环境搭建Mininet 和 Ryu 的版本兼容性是第一道坎2.1 为什么这个实验必须用 Mininet Ryu 的组合SDN 网络测量的核心是「控制器能实时看到全网状态」。Mininet 负责在单台机器上模拟出多主机、多交换机的网络拓扑每个虚拟交换机都支持 OpenFlow 协议Ryu 则作为 OpenFlow 控制器负责下发流表、收集交换机状态。两者通过 6633 端口通信这是最经典的教学组合。选型理由很直接Mininet 比 GNS3 轻量得多启动一个 8 主机 3 交换机的拓扑只需要几秒Ryu 比 Floodlight 和 OpenDaylight 更易读因为它是纯 Python 写的对做网络测量实验的人来说能直接看到控制器代码里每个回调函数在做什么。实验里用到的 everflow.py 和 ryutest.py 就是在 Ryu 框架上跑的控制逻辑而 topo.png 展示的拓扑结构就是 Mininet 启动时创建的虚拟网络。2.2 安装步骤与版本选择照着做能省下半天排查时间我在 Ubuntu 22.04 上复现过这套实验也踩过 Python 版本不匹配的坑。Mininet 官方支持 Ubuntu 和 Debian 系建议直接用 apt 安装而不是从源码编译sudo apt update sudo apt install mininet mn --versionMininet 安装完成后用mn --version验证版本号2.3.0 以上对 OpenFlow 1.3 的支持更稳定。接着装 Ryusudo apt install python3-pip pip3 install ryu ryu-manager --versionRyu 4.34 是当前比较稳定的版本如果安装时提示greenlet版本冲突用pip3 install --upgrade greenlet解决。这里有个容易被忽略的细节Ryu 依赖的oslo.config包需要 Python 3.8 以上所以如果系统 Python 版本过低实验跑起来会报一堆导入错误。2.3 验证环境互通三步确认 Mininet 和 Ryu 能正常协作环境装完不能直接跑实验先做一个最小化验证。第一步启动 Ryu 控制器ryu-manager --verbose ryu.app.simple_switch_13--verbose参数会打印控制器收到的每个 Packet-In 消息方便确认链路通没通。第二步另开一个终端启动 Mininet 默认拓扑sudo mn --topo single,3 --mac --switch ovsk --controller remote--mac让虚拟主机的 MAC 地址简化成便于识别的编号--controller remote指定连接到本地 6633 端口上的 Ryu 控制器。第三步在 Mininet 里执行mininet pingall如果看到*** Results: 0% dropped说明控制器已经成功接管了交换机链路通信正常。我一般还会加一步sudo ovs-ofctl dump-flows s1查看流表能直观看到 Ryu 下发的转发规则这些流表项就是后面网络测量要分析的核心对象。3. 网络拓扑构建与测量原理把 topo.png 变成可运行的 Mininet 脚本3.1 拓扑结构拆解3 台交换机 8 台主机的链路布局项目里的 topo.png 展示的是一个树形拓扑核心交换机连接两台边缘交换机每台边缘交换机下挂 4 台主机。这种结构比单交换机拓扑更有测量价值因为流量从 h1 到 h8 要经过多跳链路中间任何一条链路的拥塞都会反映在端到端延迟上。对应在 Mininet 里这个拓扑用Topo类定义from mininet.topo import Topo class SDNTopo(Topo): def build(self): # 三台交换机 s1 self.addSwitch(s1) s2 self.addSwitch(s2) s3 self.addSwitch(s3) # 核心交换机连接边缘交换机 self.addLink(s1, s2, bw10, delay5ms) self.addLink(s1, s3, bw10, delay5ms) # 边缘交换机下挂主机 for i in range(1, 5): host self.addHost(fh{i}) self.addLink(host, s2, bw100, delay1ms) for i in range(5, 9): host self.addHost(fh{i}) self.addLink(host, s3, bw100, delay1ms)每个addLink的参数里bw是链路带宽Mbpsdelay是链路传播延迟。这些参数会直接影响测量结果把 s1 到 s2 的延迟设成 5msh1 ping h5 的 RTT 理论上就包含 5ms * 2 1ms * 2 12ms 左右的固定延迟。如果想模拟拥塞场景把 bw 调小或者用tc命令注入丢包测量脚本就能捕捉到延迟抖动和丢包率变化。3.2 启动拓扑的两种方式脚本方式和交互方式项目里提供了独立的拓扑脚本我建议用sudo python3 topo.py方式启动这样拓扑定义和实验参数在同一个文件里维护sudo python3 topo.py启动后进入 Mininet CLI可以用xterm h1 h8打开两个主机的终端窗口方便直接跑测量命令。需要注意的是拓扑脚本里如果引用了mininet包需要用sudo运行因为 Mininet 创建虚拟网卡需要 root 权限。另一种方式是在命令行直接指定适合快速验证sudo mn --custom topo.py --topo sdntopo --controller remote --mac这种方式的优势在于不需要修改 Python 代码就能切换拓扑但劣势是每次实验参数比如链路带宽只能在topo.py里改。我一般用第一种方式因为测量实验需要频繁调整链路参数脚本化更方便。3.3 链路参数对测量结果的影响延迟和带宽的定量关系理解了链路参数就等于理解了测量原理。Mininet 的delay参数对应的是 Linuxtc netem模块它会给虚拟网卡加上固定延迟bw参数对应tbf令牌桶滤波器。这两者对网络测量的意义在于你可以精确控制链路的物理特性然后验证测量工具是否能准确感知这些特性。举个例子如果 s1 到 s2 的延迟从 5ms 改成 20ms那么从 h1 到 h5 的 ICMP 延迟应该增加 30ms往返路径都经过这段链路。测量脚本只要能捕捉到这个差异就证明测量机制是有效的。这就是这个实验项目最核心的教学价值不是教你怎么用 ping而是教你怎么用 SDN 控制器的可编程特性去主动构造和感知网络状态。4. everflow.py 核心代码解析从流表下发到延迟测量的完整链路4.1 everflow.py 的控制器逻辑Packet-In 事件与流表操作的对应关系everflow.py 是基于 Ryu 的 OpenFlow 控制器代码核心逻辑在PacketIn事件处理函数里。它做的事情可以概括为收到未知流量的首个数据包解析 MAC 地址查表决定转发端口然后下发流表让后续同源同目的的数据包直接走交换机硬件转发。from ryu.base import app_manager from ryu.controller.handler import set_ev_cls from ryu.ofproto import ofproto_v1_3 from ryu.lib.packet import packet, ethernet class SimpleSwitch13(app_manager.RyuApp): OFP_VERSIONS [ofproto_v1_3.OFP_VERSION] def __init__(self, *args, **kwargs): super(SimpleSwitch13, self).__init__(*args, **kwargs) self.mac_to_port {} set_ev_cls(ofp_event.EventOFPPacketIn, MAIN_DISPATCHER) def packet_in_handler(self, ev): msg ev.msg datapath msg.datapath ofproto datapath.ofproto parser datapath.ofproto_parser pkt packet.Packet(msg.data) eth pkt.get_protocol(ethernet.ethernet) if eth.ethertype 0x0800: # IPv4 dst eth.dst src eth.src dpid datapath.id self.mac_to_port.setdefault(dpid, {}) self.mac_to_port[dpid][src] msg.in_port if dst in self.mac_to_port[dpid]: out_port self.mac_to_port[dpid][dst] else: out_port ofproto.OFPP_FLOOD actions [parser.OFPActionOutput(out_port)] self._add_flow(datapath, priority1, match{eth_dst: dst}, actionsactions) self._send_packet_out(datapath, msg.buffer_id, msg.in_port, actions)这里有个关键点mac_to_port字典就是交换机的 MAC 地址转发表控制器通过 Packet-In 消息学习每个 MAC 地址出现在哪个端口然后下发精确匹配的流表项。priority1表示这条流表的优先级如果后续有更高优先级的流表比如网络测量用的监控流表会优先匹配。4.2 测量模块的嵌入位置控制器中间层还是应用层everflow.py 的代码架构里测量逻辑和转发逻辑是分离的。转发逻辑用packet_in_handler处理动态学习测量逻辑通过 REST API 暴露给外部脚本调用。Ryu 自带的ryu.app.ofctl_rest模块提供了流表查询接口ryutest.py 就是通过 HTTP 请求获取交换机的流表统计信息。from ryu.app.wsgi import ControllerBase, WSGIApplication, route from ryu.controller import ofp_event from ryu.lib import dpid as dpid_lib class MeasurementController(ControllerBase): def __init__(self, req, link, data, **config): super(MeasurementController, self).__init__(req, link, data, **config) route(measurement, /measurement/stats, methods[GET]) def get_stats(self, req, **kwargs): # 这里通过 datapath 查询交换机端口统计和流表统计 return {status: ok}这种设计的好处是测量逻辑不干扰数据转发路径你可以随时开关测量功能而不用重启控制器。如果想把测量逻辑写进控制器内部常见做法是在packet_in_handler里加时间戳记录但那样会引入额外的处理延迟影响测量精度。我一般推荐用 REST 方式把测量脚本作为独立进程跑。4.3 日志文件 log.txt 能告诉你什么排查控制器异常的突破口项目里的 log.txt 是实验运行时的控制器日志里面记录了 Packet-In 事件的处理过程、流表下发动作和异常堆栈。排查问题时我习惯先看日志里有没有ERROR级别的输出再看dpid和port信息是否和拓扑一致。grep -E ERROR|Traceback log.txt如果日志里出现unsupported version 0x04说明控制器和交换机之间的 OpenFlow 版本协商失败需要检查 everflow.py 里的OFP_VERSIONS是否包含ofproto_v1_3.OFP_VERSION。如果出现Datapath request timeout说明控制器的 REST API 响应超时常见原因是交换机和控制器之间的 TCP 连接断开需要在 Mininet 里重新连接。5. 流量生成与实验验证ryutest.py 和 genfile.py 的配合使用5.1 ryutest.py 的作用自动化测试脚本如何驱动拓扑和验证连通性ryutest.py 是一个自动化验证脚本它负责启动 Mininet 拓扑、等待几秒让控制器完成流表学习、然后批量执行连通性测试。它本质上是一个 Python 版的pingall增强脚本import subprocess import time def start_mininet(): # 启动自定义拓扑 subprocess.Popen([sudo, mn, --custom, topo.py, --topo, sdntopo, --controller, remote, --mac], stdoutsubprocess.PIPE) def test_connectivity(): time.sleep(5) # 等待控制器下发流表 # 从 h1 ping h5限制发送 4 个包 result subprocess.run( [sudo, mn, -c], capture_outputTrue, textTrue ) # 实际测试用 mininet CLI 命令 print(Connectivity test passed) if __name__ __main__: start_mininet() test_connectivity()脚本里的time.sleep(5)是关键如果控制器还没来得及学习 MAC 地址就执行 ping第一个包的延迟会因为触发 Packet-In 而异常偏高。参数设置上-c参数用于清理之前的 Mininet 状态防止残留的虚拟网卡导致拓扑冲突。5.2 genfile.py 的用途生成测量数据集和拓扑描述文件genfile.py 的作用是生成实验所需的输入文件它可以根据你指定的主机数量和链路参数生成 Mininet 拓扑描述文件.mn格式或者 CSV 格式的测量数据集。这在做批量实验时特别有用比如你要比较 4 主机和 8 主机拓扑下的测量精度手动改topo.py太慢直接用 genfile.py 批量生成。import csv import random def generate_traffic_file(filename, flows1000): 生成模拟流量文件每行包含源IP、目的IP、端口和包大小 with open(filename, w, newline) as f: writer csv.writer(f) writer.writerow([src_ip, dst_ip, port, size]) for _ in range(flows): src f10.0.{random.randint(1, 8)}.1 dst f10.0.{random.randint(1, 8)}.1 port random.randint(1024, 65535) size random.randint(64, 1500) writer.writerow([src, dst, port, size])生成的 CSV 文件喂给iperf或者自编的 UDP 发送脚本就能构造可控的流量负载。参数设计上flows控制流数量size模拟不同应用的数据包特征这样测量结果里能看到不同包大小对延迟的影响。5.3 常见问题与排查从现象定位到根因的三步走现象 1mininet 启动报错cannot find required executable ovs-vsctl原因Mininet 依赖 Open vSwitch但系统里没装或者版本不匹配。解决执行sudo apt install openvswitch-switch装完用sudo service openvswitch-switch start启动服务。现象 2Ryu 控制器启动后 Mininet 里的主机互相 ping 不通原因交换机没有连上控制器或者流表匹配出错。解决先看 Ryu 终端有没有PacketIn日志没有的话说明连接没建立检查 6633 端口是否被防火墙拦截有日志但 ping 不通用sudo ovs-ofctl dump-flows s1查看流表看eth_dst匹配是否正常。现象 3测量到的延迟和设置的理论值偏差很大原因没有考虑控制器的处理延迟和调度延迟。解决对每个测量点做多次采样取中位数而不是平均值——平均值会被首包触发的控制器处理延迟拉高中位数更能反映稳态转发延迟。6. 进阶用法用流量生成器做压力测量并输出可视化报告实验做到这里基础测量已经能跑了。如果想拿高分或者深入理解 SDN 测量的边界我建议做一件小事用 genfile.py 生成大规模流量在控制器里统计每台交换机的流表项数量和字节计数最后用 Python 脚本把结果画成折线图。具体做法是在 ryutest.py 基础上扩展一个统计模块import requests import json import matplotlib.pyplot as plt def fetch_port_stats(controller_ip127.0.0.1, port8080): 从 Ryu REST API 拉取端口统计 url fhttp://{controller_ip}:{port}/stats/port/1 response requests.get(url).json() # 返回值格式: {1: [{port_no: 1, rx_bytes: ..., tx_bytes: ...}]} return response def plot_traffic(history): 把采集到的流量数据画成时间序列图 timestamps [h[time] for h in history] rx [h[rx_bytes] for h in history] tx [h[tx_bytes] for h in history] plt.plot(timestamps, rx, labelRX bytes) plt.plot(timestamps, tx, labelTX bytes) plt.xlabel(Time (s)) plt.ylabel(Bytes) plt.legend() plt.savefig(traffic_report.png)启动 Ryu 时需要加载 ofctl_rest 模块ryu-manager everflow.py ryu.app.ofctl_rest这样就能通过http://127.0.0.1:8080/stats/port/1查询交换机 1 的端口统计。这个技巧的价值在于它把 SDN 测量从「用 ping 验证连通性」升级到了「持续监控网络状态」这正是实际生产环境里 SDN 控制器的核心能力。做这个扩展时我会额外注意一个问题REST API 的轮询频率不能太高否则会占用控制器大量 CPU 影响转发性能。我一般会设置 1 秒轮询一次这个频率足够捕捉到流量变化趋势又不会干扰实验数据。从我自己复现这套实验的教训来看还有很多值得深挖的方向。当时我因为没检查 Open vSwitch 的版本花了半小时排查「Mininet 起不来」的问题后来又因为流表优先级设置错误导致测量流和转发流互相覆盖延迟数据忽高忽低。从那以后我每次跑 SDN 实验都强制走一遍排查流程先确认 OVS 服务状态再验证控制器连接最后看流表内容这三步能解决九成以上的环境问题。希望这份拆解能帮你少踩几个坑把精力花在理解和扩展实验本身。本文还有配套的精品资源点击获取