基于Mininet与Ryu的SDN网络测量实验指南
2026/9/13 6:33:28 网站建设 项目流程

简介:网络测量课程拓展实验项目,基于Mininet和Ryu控制器开发,面向高校计算机、通信、自动化等专业学生,可作课程设计、毕业设计或项目初期演示。资源包含完整Python源码、拓扑图、运行截图和说明文档,覆盖流量生成、规则匹配与结果采集等核心环节,有助于理解SDN数据平面测量、流表下发与网络监控机制。其中Python脚本承担流量构造与测试逻辑,PNG图片直观展示网络拓扑与模块结构,HTML页面可用于交互查询与结果可视化。压缩包共23个文件,以12个Python脚本为核心,辅以6张PNG示意图片、2个HTML页面、README说明和许可文件等,整体大小549KB,轻量且便于部署。已有164人学习下载。代码经测试运行成功,项目评分达到96分,下载后可按文档快速复现实验,也可基于现有框架二次开发,用于流量监控、网络测量等方向的深入实践。

1. 为什么 SDN 网络测量实验是课程里性价比最高的一环

网络测量这门课最常见的问题是:教材上讲得头头是道,延迟、吞吐、丢包率的定义背得滚瓜烂熟,一到真机环境就抓瞎——没有权限动交换机、没有第二台机器组链路、实验室拓扑被其他组占着。这个基于 Mininet 和 Ryu 的 SDN 网络测量实验,本质上是用纯软件的方式把"测量对象"和"测量工具"全部虚拟化:Mininet 负责模拟出交换机、主机和链路,Ryu 控制器既是转发决策者,也是测量数据的采集探针。你不需要真实硬件,就能完成端口字节数统计、流表匹配计数、链路吞吐量推算、端到端延迟测量这一整套动作。它适合两类人:一是正被课程设计或大作业压着的学生,需要一份能跑通、能写进报告、能答上答辩提问的完整链路;二是刚接触 SDN 想搞清楚"控制器到底能拿到哪些网络数据"的开发者。这个实验含金量的关键点在于,它把测量逻辑从"被动看结果"变成了"主动设计测量方案",这正是高分和及格的分水岭。

2. 用 Mininet 把被测网络搭起来:拓扑、启动与验活

2.1 安装前的版本约束:Python 环境决定后面一半的坑

实验的底座是 Mininet 加 Ryu,两者都对 Python 解释器有依赖。Mininet 从 2.2 开始支持 Python 3,Ryu 4.30 以上的版本也全面转向 Python 3.6+。动手之前先确认三件事:操作系统(Ubuntu 20.04 或 22.04 最省心)、Python 版本(建议 3.8 到 3.10,太新反而容易遇到依赖包没跟上)、是否已经装了 pip 和 git。

Mininet 安装常见做法是使用 apt 源直装,适合大多数课程实验:

sudo apt update sudo apt install -y mininet mn --version

如果希望拿到最新特性或需要改 Mininet 源码,才用源码安装,git clone后执行util/install.sh。Ryu 通过 pip 安装即可:

pip install ryu ryu-manager --version

提示:千万别图省事用系统自带的 Python 2 环境跑 Ryu,老版本 API 和 Mininet 的交互方式差异很大,网上教程鱼龙混杂,认准 Python 3 语法和 Ryu 3.30+ 的写法。

安装完后验证 Mininet 自带的拓扑能否正常拉起,用最小命令跑一次:

sudo mn --test pingall --topo single,3

能看到两台以上的主机pingall结果是0% dropped就说明底层网络命名空间、虚拟网卡和交换机进程都正常。这一步花三分钟,能避免后面排查半天发现是环境问题而非代码问题。

2.2 定制一个课程实验专用拓扑:把链路参数做成可变量

实际课程实验里,默认的 single 或 linear 拓扑往往不够用——你需要指定链路带宽、延迟、丢包率、队列规则,才能构造出"有测量意义"的网络场景。比如测量 TCP 吞吐量时链路带宽设为 10Mbps,测量延迟时给链路加 5ms 延迟,这些参数不写在拓扑里,测量就变成了测 loopback。

写一个自定义的 Python 拓扑脚本,这是整个实验的第一个源码交付物:

from mininet.topo import Topo class MeasNetTopo(Topo): """三台主机经两台交换机互联,链路参数可调""" def build(self, bw=10, delay="5ms", loss=0): # 创建交换机 s1 = self.addSwitch("s1", dpid="0000000000000001") s2 = self.addSwitch("s2", dpid="0000000000000002") # 创建主机,每个 host 可以指定 IP 和 MAC h1 = self.addHost("h1", ip="10.0.0.1/24") h2 = self.addHost("h2", ip="10.0.0.2/24") h3 = self.addHost("h3", ip="10.0.0.3/24") # 交换机间链路 self.addLink(s1, s2, bw=bw, delay=delay, loss=loss, use_htb=True) # 主机接入链路 self.addLink(h1, s1, bw=bw, delay=delay, loss=loss, use_htb=True) self.addLink(h2, s2, bw=bw, delay=delay, loss=loss, use_htb=True) self.addLink(h3, s2, bw=bw, delay=delay, loss=loss, use_htb=True) topos = {"measnet": MeasNetTopo}

dpid参数很关键,它是交换机在 OpenFlow 协议中的唯一标识,Ryu 控制器监控交换机状态时靠它区分数据通路。use_htb=True表示用 Linux 的 HTB 队列做带宽限速,这是 Mininet 模拟真实带宽的手段,不加这个参数带宽设置不生效。链路延迟用delay参数,丢包率用loss参数(0 到 100 的整数,表示百分比)。

启动这个拓扑时,用--custom指定脚本文件,--topo指定拓扑名:

sudo mn --custom meas_topo.py --topo measnet --controller remote --mac --switch ovsk

控制器用remote模式,不启动 Mininet 自带的控制器,把控制权交给接下来要启动的 Ryu;--switch ovsk是让 Mininet 创建 Open vSwitch 类型的软件交换机,这样才能支持ovs-vsctl命令和后续的流表检查。

2.3 启动后的验活命令:测量之前先保证通路干净

拓扑起来以后,先别急着跑测量脚本,用一组命令确认网络是健康的:

mininet> net # 查看拓扑连接关系 mininet> dump # 查看各节点的 IP 和端口 mininet> pingall # 全连通性测试

然后在 Mininet 里给 h1、h2 互相 ping 通,再执行iperf做一次带宽摸底。如果预期带宽 10Mbps,结果只有几百 Kbps,多半是use_htb没生效或者系统 tcp 窗口太小。iperf用法:

mininet> h1 iperf -s & mininet> h2 iperf -c 10.0.0.1 -t 5

这步测出的带宽是"测量基线的参考值",后面 Ryu 控制器算出的端口速率要和它对比,差太多就说明测量代码里有采样周期或单位换算的问题。

3. Ryu 控制器的测量逻辑:端口统计的采集与二次计算

3.1 控制器在测量中的角色:一个主动下发的采样器

Ryu 控制器在此实验里的工作分三层。第一层是接收交换机上送的包、下发流表规则,保证数据面能正常转发;第二层是周期性向交换机发送OFPMP_PORT_STATS请求,把交换机端口上的收发字节数、包数、丢包数拉上来;第三层是把两次采样的差值除以时间间隔,换算成实时的端口速率。很多课程设计只做到了第二层就把数据存下来了,答辩时老师一问"速率怎么算的"就卡住——因为端口统计寄存器是累加值,不是瞬时速率,不做差值计算拿到的数据毫无测量意义。这个差值计算才是测量的核心。

3.2 用 Ryu 的事件机制挂接统计响应

Ryu 应用是一个继承app_manager.RyuApp的类,通过装饰器订阅 OpenFlow 事件。下面是端口统计采集的最小可运行代码,它也是整个实验源码的核心骨架:

from ryu.base import app_manager from ryu.controller import ofp_event from ryu.controller.handler import MAIN_DISPATCHER, set_ev_cls from ryu.ofproto import ofproto_v1_3 from ryu.lib import hub import time class PortMonitor(app_manager.RyuApp): OFP_VERSIONS = [ofproto_v1_3.OFP_VERSION] def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.datapaths = {} # 保存所有连上的交换机 self.port_stats = {} # 端口历史采样值 self.monitor_thread = hub.spawn(self._monitor_loop) @set_ev_cls(ofp_event.EventOFPStateChange, MAIN_DISPATCHER) def _switch_connect_handler(self, ev): """交换机连接/断开时记录 datapath 对象""" dp = ev.datapath if ev.state == MAIN_DISPATCHER: self.datapaths[dp.id] = dp else: self.datapaths.pop(dp.id, None) def _monitor_loop(self): """每 5 秒对所有交换机发一次端口统计请求""" while True: for dp in self.datapaths.values(): self._request_port_stats(dp) hub.sleep(5) def _request_port_stats(self, dp): ofproto = dp.ofproto parser = dp.ofproto_parser req = parser.OFPPortStatsRequest(dp, 0, ofproto.OFPP_ANY) dp.send_msg(req) @set_ev_cls(ofp_event.EventOFPPortStatsReply, MAIN_DISPATCHER) def _port_stats_reply_handler(self, ev): body = ev.msg.body dp = ev.msg.datapath now = time.time() for stat in body: key = (dp.id, stat.port_no) prev = self.port_stats.get(key) if prev: # 字节数差值 / 时间差值 = 速率 (byte/s) tx_bps = (stat.tx_bytes - prev['tx_bytes']) / (now - prev['time']) rx_bps = (stat.rx_bytes - prev['rx_bytes']) / (now - prev['time']) self.logger.info( "dp=%s port=%s tx=%.2fBps rx=%.2fBps", dp.id, stat.port_no, tx_bps, rx_bps ) self.port_stats[key] = { 'tx_bytes': stat.tx_bytes, 'rx_bytes': stat.rx_bytes, 'time': now }

这段代码有几个值得展开讲的点。hub.spawn是 Ryu 基于 eventlet 的协程调度接口,它在应用初始化时启动一个后台循环线程,不可以直接用 Python 原生threading.Thread,因为 Ryu 的事件循环和协程库协同工作,原生线程拿不到交换机连接状态。OFPPortStatsRequest的第二个参数是 port number,传OFPP_ANY表示请求所有端口,实际上 OpenFlow 1.3 里就是0xffffffffEventOFPPortStatsReplybody是一个元素为OFPPortStats的列表,其中的tx_bytesrx_bytes都是累积计数器的值,从交换机启动后开始累计,所以必须用"本次减上次"的方式消除累积效应。

提示:prev['time']time.time()采集的是控制器的墙钟时间,不是交换机时间。两者若有时间偏差,测出的速率只影响绝对值,不影响趋势,课程报告里说明这一点反而加分。

3.2.1 速率计算里的两个隐性问题

第一个问题是采样周期的抖动。hub.sleep(5)不是精确的 5 秒,如果系统负载高,实际间隔可能是 5.2 秒或 4.8 秒。除法时分母用实际now - prev['time'],已经规避了这个问题。不要自作聪明地用固定的 5 去算,那样抖动会被放大成误差。

第二个问题是计数器溢出。OpenFlow 的计数器是 64 位无符号整数,在万兆链路上约 584 年才会溢出一次,课程实验不用担心。但如果用到 32 位系统的 OvS 早期版本处理大字节数,可能需要做溢出补偿。这个知识点写进报告的"误差分析"章节非常能体现深度。

3.3 把测量扩展到流表级别:统计单一流的转发特征

端口统计回答的是"这个端口总共有多少流量",但课程实验经常还要回答"特定 IP 或特定端口号的流量走了多少"。这时要给交换机下发带cookiepriority区分的流表项,然后用OFPFlowStatsRequest拉取流表统计:

def _request_flow_stats(self, dp): parser = dp.ofproto_parser ofproto = dp.ofproto match = parser.OFPMatch() # 空 match 匹配所有流 req = parser.OFPFlowStatsRequest( dp, 0, ofproto.OFPTT_ALL, ofproto.OFPP_ANY, ofproto.OFPG_ANY, 0, 0, match ) dp.send_msg(req)

OFPFlowStatsRequest参数从第 4 个开始分别是 table_id、out_port、out_group、cookie、cookie_mask 和 match。想查特定端口的流,给out_port传端口号;想查特定表的流,给table_id传表编号。响应事件是EventOFPFlowStatsReplybody里的每个元素带byte_countpacket_countduration_sec,其中duration_sec是流表项存活的秒数,用byte_count / duration_sec可以直接得到这条流的平均速率,不需要像端口统计那样做差值——这也是流表统计和端口统计在数据模型上的一个典型差异。

4. 把测量数据做成实验报告里能用的结果

4.1 三种测量手段的配合方式与适用边界

这个实验里能用于网络测量的手段至少有三种:Mininet 内部的iperfping、Ryu 控制器的端口统计、Ryu 的流表统计。它们各有分工,报告里最好用一张表把它们的关系讲清楚:

测量手段测量对象数据粒度典型用途局限性
pingICMP 回声端到端延迟报文级验证链路延迟配置是否生效只能测连通性相关指标,拿不到吞吐量
iperfTCP/UDP 灌流链路实际吞吐流级测量可用带宽、TCP 窗口调优是主动测量,会占用带宽资源
Ryu 端口统计交换机端口收发速率端口级观察链路利用率、流量趋势只能得到累加值的差值,测不出单条流
Ryu 流表统计流表项匹配的数据流级分析特定流的行为依赖流表下发的匹配规则是否精确

实际做实验时,完整的测量链路是:用iperf在 h1 和 h2 之间打流量,同时让 Ryu 控制器采集 s1 和 s2 之间那条链路的端口速率,再在 Mininet CLI 里用ping测一遍延迟。三组数据各自独立,因为测量原理完全不同,正好可以互相印证。如果iperf测出的吞吐是 9.2Mbps,而 Ryu 端口统计显示 s1-s2 链路速率在 9Mbps 左右,说明测量基本可信。

4.2 数据落盘:把采集结果导出为 CSV

控制器里self.logger.info打印的数据只适合调试,不适合写进实验报告。给 PortMonitor 增加一个 CSV 导出功能,每次差值计算都把结果追加到文件里:

import csv import os CSV_PATH = "measure_result.csv" def _append_to_csv(self, row): file_exists = os.path.isfile(CSV_PATH) with open(CSV_PATH, "a", newline="") as f: writer = csv.DictWriter( f, fieldnames=["timestamp", "dp_id", "port_no", "tx_bps", "rx_bps"] ) if not file_exists: writer.writeheader() writer.writerow(row)

调用时在_port_stats_reply_handler里把计算好的tx_bpsrx_bps传进来,时间戳用time.strftime("%Y-%m-%d %H:%M:%S")的字符串格式。CSV 的好处是后续用 Python 的 pandas 或 matplotlib 做图非常方便,不必在控制器里耦合绘图逻辑。注意文件写入要用追加模式,否则每次采样覆盖上一次的旧数据,采集一小时最后只剩一行。

4.3 实验报告里的数据呈现:Python 画图脚本

拿到 CSV 后,用独立脚本做可视化。这里要小心 matplotlib 画时间序列横轴太密的问题,把横轴刻度旋转并做稀疏处理:

import pandas as pd import matplotlib.pyplot as plt df = pd.read_csv("measure_result.csv") df["timestamp"] = pd.to_datetime(df["timestamp"]) fig, ax = plt.subplots(figsize=(10, 4)) ax.plot(df["timestamp"], df["tx_bps"] / 1e6, label="s1-s2 tx Mbps") ax.plot(df["timestamp"], df["rx_bps"] / 1e6, label="s1-s2 rx Mbps") ax.set_xlabel("time") ax.set_ylabel("Throughput (Mbps)") ax.legend() ax.xaxis.set_major_locator(plt.MaxNLocator(8)) # 横坐标最多显示8个刻度 plt.xticks(rotation=30) plt.tight_layout() plt.savefig("throughput.png", dpi=150)

ax.xaxis.set_major_locator(plt.MaxNLocator(8))这一行是解决横坐标太密集的关键,默认情况下几百个时间点全画在横轴上会糊成一团。plt.xticks(rotation=30)把刻度文字旋转 30 度防止重叠。中文标签如果显示成方框,在 plt 前面加两行:

plt.rcParams["font.sans-serif"] = ["WenQuanYi Micro Hei", "SimHei"] plt.rcParams["axes.unicode_minus"] = False

如果系统没装中文字体,直接sudo apt install fonts-wqy-microhei装上。这个细节在答辩演示时非常加分,因为多数小组屏幕上全是豆腐块。

5. 一张表验证测量精度,两个技巧拉开分差

5.1 精度验收清单:和真实配置逐项对账

实验做完,先别急着写报告,用一张表校准测量结果的可信度。把 Mininet 启动拓扑时传入的链路带宽、延迟、丢包率与测量值做对比:

配置项拓扑设定值测量方法合理误差范围
链路带宽10 Mbpsiperf TCP 测吞吐±10%
链路延迟5 msping 取 RTT/2±1 ms
丢包率1%ping 统计丢包占比±0.5%
端口速率取决于实发流量Ryu 端口统计差值与 iperf 结果差 10% 内

如果带宽误差超过 20%,优先查use_htb=True是否加上了;如果延迟测出来只有 0.0x ms,多半是拓扑脚本没生效,Mininet 还在用默认的 lo 接口;如果 Ryu 里算出的速率比 iperf 高一大截,回看第 3 章提到的采样周期。

5.2 差分测量:同一实验跑两组配置对比

高分实验和及格实验的分水岭,在于有没有做变量的控制实验。把链路带宽改成 5Mbps、10Mbps、20Mbps 三组,分别跑同一套测量流程,把三组曲线画在同一张图上,横轴是时间,纵轴是吞吐。这是课程实验报告里最常用也最有效的可视化方法,它能同时展示拓扑参数有效性和测量程序稳定性。批量跑实验的脚本可以写成循环:

for bw in 5 10 20; do sudo mn --custom meas_topo.py --topo measnet \ --link bw=$bw,delay=5ms \ --controller remote --mac --switch ovsk & sleep 3 ryu-manager port_monitor.py & sleep 10 sudo mn -c # 清理网络命名空间 kill %2 done

跑完用第 4 章的绘图脚本把每组的 CSV 文件名对应到带宽值,画出对比图。这样一份实验报告的测量部分是完整的闭环:有测量目的、有测量手段、有精度验证、有对照组。

最后补充一个文档说明的小技巧:给源码里的port_monitor.pymeas_topo.py各写十几行注释,把关键参数的含义标清楚,用说明文档时先写"环境要求:Ubuntu 20.04+,Python 3.8+,Mininet 2.3+,Ryu 4.30+",再写"运行步骤:终端 1 执行 mn 启动拓扑,终端 2 执行 ryu-manager"。这样的使用说明在验收时最省事,老师按步骤跑通一次,项目就过了大半。

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

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

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

立即咨询