NS3学习笔记03

NS3实现Fat-Tree

Posted by Liu Mengxuan on November 9, 2025

本文是 NS3 学习笔记的第三章节,主要是对 FatTree 的实现

📊 扩展阅读:为了更直观地理解 FatTree 的拓扑结构,你可以查看 FatTree 网络架构分析 页面。本文的代码可以在Fat-Tree NS3代码实现获取

ns-3 Fat-Tree 数据中心网络仿真代码详解

项目概述

仿真目标

本项目使用 ns-3.44 网络仿真器实现了一个 k=4 的 Fat-Tree 数据中心网络拓扑,并通过 ECMP (等价多路径) 路由实现负载均衡。

拓扑规模

1
2
3
4
5
总节点数: 36
├── 服务器: 16 台 (4 个 Pod × 4 台/Pod)
├── 接入交换机: 8 个 (4 个 Pod × 2 个/Pod)
├── 汇聚交换机: 8 个 (4 个 Pod × 2 个/Pod)
└── 核心交换机: 4 个

拓扑结构图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
                    核心层 (Core Layer)
                    ┌────┬────┬────┬────┐
                    │ C0 │ C1 │ C2 │ C3 │
                    └─┬──┴─┬──┴─┬──┴─┬──┘
                      │    │    │    │
        ┌─────────────┴────┴────┴────┴─────────────┐
        │                                           │
   汇聚层 (Aggregation Layer)                       │
   ┌────┬────┐  ┌────┬────┐  ┌────┬────┐  ┌────┬────┐
   │ A6 │ A7 │  │ A6 │ A7 │  │ A6 │ A7 │  │ A6 │ A7 │
   └─┬──┴─┬──┘  └─┬──┴─┬──┘  └─┬──┴─┬──┘  └─┬──┴─┬──┘
     │    │       │    │       │    │       │    │
接入层 (Access Layer)
   ┌─┴──┬─┴──┐  ┌─┴──┬─┴──┐  ┌─┴──┬─┴──┐  ┌─┴──┬─┴──┐
   │ S4 │ S5 │  │ S4 │ S5 │  │ S4 │ S5 │  │ S4 │ S5 │
   └┬─┬─┴┬─┬─┘  └┬─┬─┴┬─┬─┘  └┬─┬─┴┬─┬─┘  └┬─┬─┴┬─┬─┘
    │ │  │ │     │ │  │ │     │ │  │ │     │ │  │ │
服务器层 (Server Layer)
   ┌┴┐┌┴┐┌┴┐┌┴┐  ┌┴┐┌┴┐┌┴┐┌┴┐  ┌┴┐┌┴┐┌┴┐┌┴┐  ┌┴┐┌┴┐┌┴┐┌┴┐
   │0││1││2││3│  │0││1││2││3│  │0││1││2││3│  │0││1││2││3│
   └─┘└─┘└─┘└─┘  └─┘└─┘└─┘└─┘  └─┘└─┘└─┘└─┘  └─┘└─┘└─┘└─┘
     Pod 0         Pod 1         Pod 2         Pod 3

仿真结果验证

FlowMonitor 统计数据分析

运行仿真后生成的 DCN_FatTree_FlowStat.flowmon 文件显示了 4 个流的完整统计信息:

Flow 1: TCP 数据流 (Pod3 → Pod2)

1
2
3
4
源地址: 10.3.0.1 (Pod3.Server0)
目标地址: 10.2.0.1 (Pod2.Server0)
协议: TCP (protocol="6")
端口: 49153 → 80

性能指标

  • 发送数据: 1,097,192 字节 (~1.05 MB)
  • 接收数据: 1,097,192 字节 (~1.05 MB)
  • 丢包率: 0% (lostPackets=”0”)
  • 发送包数: 1,869 个
  • 接收包数: 1,869 个
  • 平均延迟: 27.2 µs (50,841,900 ns / 1,869)
  • 最小延迟: 770 ns
  • 最大延迟: 48,358 ns

结论: TCP 流量成功传输完整的 1 MB 数据,无丢包,延迟稳定在微秒级别。

Flow 2: TCP ACK 流 (Pod2 → Pod3)

1
2
3
4
源地址: 10.2.0.1 (Pod2.Server0)
目标地址: 10.3.0.1 (Pod3.Server0)
协议: TCP (protocol="6")
端口: 80 → 49153

性能指标

  • 发送数据: 48,676 字节 (~47.5 KB)
  • 接收数据: 48,676 字节
  • 丢包率: 0%
  • 发送包数: 936 个
  • 接收包数: 936 个
  • 平均延迟: 0.75 µs (702,733 ns / 936)

结论: TCP ACK 包正常返回,延迟极低,说明反向路径畅通。

Flow 3: UDP Echo 请求 (Pod1 → Pod0)

1
2
3
4
源地址: 10.1.0.1 (Pod1.Server0)
目标地址: 10.0.0.1 (Pod0.Server0)
协议: UDP (protocol="17")
端口: 49153 → 9

性能指标

  • 发送数据: 1,052 字节
  • 接收数据: 1,052 字节
  • 丢包率: 0%
  • 延迟: 3.17 µs

结论: UDP Echo 请求成功到达服务器。

Flow 4: UDP Echo 响应 (Pod0 → Pod1)

1
2
3
4
源地址: 10.0.0.1 (Pod0.Server0)
目标地址: 10.1.0.1 (Pod1.Server0)
协议: UDP (protocol="17")
端口: 9 → 49153

性能指标

  • 发送数据: 1,052 字节
  • 接收数据: 1,052 字节
  • 丢包率: 0%
  • 延迟: 3.13 µs

结论: UDP Echo 响应成功返回,往返延迟约 6.3 µs。

ECMP 负载均衡验证

从 FlowProbe 数据可以看出,TCP 流量 (Flow 1) 在多个探针上都有记录:

1
2
3
4
FlowProbe 44: 935 packets (549,604 bytes)
FlowProbe 46: 934 packets (547,588 bytes)
FlowProbe 60: 935 packets (549,604 bytes)
FlowProbe 62: 934 packets (547,588 bytes)

结论: 流量被分散到多条路径上,证明 ECMP 负载均衡生效

整体评估

指标 结果 状态
     
跨 Pod 连通性 4 个 Pod 之间完全连通 ✅ 正常
丢包率 0% ✅ 优秀
延迟 微秒级 (0.77~48 µs) ✅ 优秀
TCP 吞吐量 ~12.4 MB/s ✅ 正常
ECMP 负载均衡 流量分布在多条路径 ✅ 生效
UDP Echo 往返成功 ✅ 正常

总结: 仿真结果完全正常,Fat-Tree 拓扑工作正常,ECMP 路由生效。

代码结构详解

整体架构

代码按照 9 个主要步骤组织:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
1. 配置模拟参数
   ├── 命令行参数解析
   ├── 时间精度设置
   ├── 日志配置
   └── ECMP 路由配置

2. 定义链路助手
   ├── 服务器到接入交换机 (10 Gbps, 200 ns)
   ├── 接入到汇聚交换机 (40 Gbps, 70 ns)
   └── 汇聚到核心交换机 (40 Gbps, 50 ns)

3. 创建节点
   ├── 4 个 Pod (每个 8 个节点)
   ├── 4 个核心交换机
   ├── 移动性模型配置
   └── TCP/IP 协议栈安装

4. 构建拓扑
   ├── Pod 内部连接
   └── 跨 Pod 核心层连接

5. 分配 IP 地址
   ├── Pod 内链路: 10.PodID.SubnetID.0/30
   └── 核心层链路: 10.10.SubnetID.0/30

6. 计算路由表
   └── Ipv4GlobalRoutingHelper::PopulateRoutingTables()

7. 部署应用程序
   ├── UDP Echo (Pod0 ↔ Pod1)
   └── TCP BulkSend (Pod3 → Pod2)

8. 配置监控
   ├── PCAP 数据包捕获
   ├── FlowMonitor 统计
   └── NetAnim 动画

9. 运行仿真
   ├── 启动仿真器
   ├── 导出统计数据
   └── 清理资源

关键代码段解析

1. 链路配置

1
2
3
4
// 服务器到接入交换机 (边缘链路)
PointToPointHelper NodeToSW;
NodeToSW.SetDeviceAttribute("DataRate", StringValue("10Gbps"));
NodeToSW.SetChannelAttribute("Delay", StringValue("200ns"));

设计考虑:

  • 带宽: 10 Gbps 模拟标准服务器网卡
  • 延迟: 200 ns 模拟短距离光纤/铜缆
  • 队列: 使用默认值,因为边缘流量较小
1
2
3
4
5
6
// 汇聚到核心交换机 (上行链路)
PointToPointHelper SWToSW_50ns;
SWToSW_50ns.SetDeviceAttribute("DataRate", StringValue("40Gbps"));
SWToSW_50ns.SetChannelAttribute("Delay", StringValue("50ns"));
SWToSW_50ns.SetQueue("ns3::DropTailQueue", "MaxSize", 
                     StringValue(std::to_string(Corequeuesize) + "p"));

设计考虑:

  • 带宽: 40 Gbps 模拟高速交换机互联
  • 延迟: 50 ns 模拟核心层短距离连接
  • 队列: 8 个数据包,防止突发流量丢包

2. 节点创建与协议栈安装

1
2
3
4
5
6
7
8
9
10
11
12
// 创建 Pod 节点
NodeContainer pod0, pod1, pod2, pod3, core;
pod0.Create(8);  // 4 服务器 + 4 交换机
// ...

// 安装 TCP/IP 协议栈
InternetStackHelper stack;
stack.Install(pod0);
stack.Install(pod1);
stack.Install(pod2);
stack.Install(pod3);
stack.Install(core);

关键点:

  • 每个 Pod 的 8 个节点统一管理
  • 所有节点都安装完整的 TCP/IP 协议栈
  • 包括 IPv4、TCP、UDP、ICMP 等协议

3. 拓扑连接

1
2
3
4
5
// Pod 0 内部连接示例
pod0_dev  = NodeToSW.Install(pod0.Get(0), pod0.Get(4)); // Server0 → AccessSW4
pod0_dev2 = NodeToSW.Install(pod0.Get(1), pod0.Get(4)); // Server1 → AccessSW4
pod0_dev5 = SWToSW_50ns.Install(pod0.Get(4), pod0.Get(6)); // AccessSW4 → AggrSW6
pod0_dev6 = SWToSW_70ns.Install(pod0.Get(4), pod0.Get(7)); // AccessSW4 → AggrSW7

连接规律:

  • 每个接入交换机连接 2 台服务器
  • 每个接入交换机连接 2 个汇聚交换机(全连接)
  • 提供了 Pod 内的冗余路径
1
2
3
// 核心层连接示例
core_dev  = SWToSW_50ns.Install(pod0.Get(6), core.Get(0)); // Pod0.AggrSW6 → Core0
core_dev2 = SWToSW_50ns.Install(pod1.Get(6), core.Get(0)); // Pod1.AggrSW6 → Core0

连接规律:

  • 每个 Pod 的每个汇聚交换机连接所有核心交换机
  • 提供了 Pod 间的多条等价路径(ECMP 的基础)

4. IP 地址分配

1
2
3
4
5
6
7
8
9
10
// Pod 0 的 IP 分配
address.SetBase("10.0.0.0","255.255.255.252");  // Server0-AccessSW4
pod0_Iface = address.Assign(pod0_dev);
address.SetBase("10.0.1.0","255.255.255.252");  // Server1-AccessSW4
pod0_Iface2 = address.Assign(pod0_dev2);
// ...

// 核心层的 IP 分配
address.SetBase("10.10.0.0","255.255.255.252");   // Pod0.AggrSW6-Core0
core_Iface = address.Assign(core_dev);

关键技巧:

  • 每次 Assign 前都调用 SetBase 重置地址计数器
  • 避免 Ipv4AddressHelper 的地址溢出问题
  • 使用 /30 子网 (255.255.255.252) 为 P2P 链路提供 2 个可用地址

5. 路由配置

1
2
3
4
5
// 启用 ECMP
Config::SetDefault("ns3::Ipv4GlobalRouting::RandomEcmpRouting", BooleanValue(true));

// 填充路由表
Ipv4GlobalRoutingHelper::PopulateRoutingTables();

工作原理:

  1. PopulateRoutingTables() 在所有节点上运行 Dijkstra 算法
  2. 计算到每个目标网络的最短路径
  3. 当存在多条等价路径时,ECMP 会随机选择一条
  4. 实现了自动的负载均衡

6. 应用部署

1
2
3
4
5
6
7
8
9
10
11
12
// UDP Echo 服务器 (Pod0.Server0)
UdpEchoServerHelper echoServer(9);
ApplicationContainer serverApps = echoServer.Install(pod0.Get(0));
serverApps.Start(Seconds(1.0));
serverApps.Stop(Seconds(10.0));

// UDP Echo 客户端 (Pod1.Server0)
UdpEchoClientHelper echoClient(pod0_Iface.GetAddress(0), 9);
echoClient.SetAttribute("MaxPackets", UintegerValue(1));
echoClient.SetAttribute("PacketSize", UintegerValue(1024));
ApplicationContainer clientApps = echoClient.Install(pod1.Get(0));
clientApps.Start(Seconds(2.0));

测试目的:

  • 验证跨 Pod 的基本连通性
  • 测量往返延迟 (RTT)
  • 确认路由表正确性
1
2
3
4
5
6
7
8
9
10
11
12
// TCP BulkSend 发送端 (Pod3.Server0)
BulkSendHelper source("ns3::TcpSocketFactory", 
                      InetSocketAddress(pod2_Iface.GetAddress(0), 80));
source.SetAttribute("MaxBytes", UintegerValue(1000000)); // 1 MB
ApplicationContainer sourceApps = source.Install(pod3.Get(0));
sourceApps.Start(Seconds(1.5));

// TCP PacketSink 接收端 (Pod2.Server0)
PacketSinkHelper sink("ns3::TcpSocketFactory", 
                      InetSocketAddress(Ipv4Address::GetAny(), 80));
ApplicationContainer sinkApp = sink.Install(pod2.Get(0));
sinkApp.Start(Seconds(1.0));

测试目的:

  • 测试大流量传输性能
  • 验证 ECMP 负载均衡效果
  • 测量吞吐量和延迟

7. 监控配置

1
2
3
4
5
6
7
8
9
10
// PCAP 数据包捕获
NodeToSW.EnablePcapAll("DCN_FatTree_CSMA_Pcap");

// FlowMonitor 统计
FlowMonitorHelper flowmonHelper;
flowmonHelper.InstallAll();

// NetAnim 动画
AnimationInterface anim("animation.xml");
anim.SetConstantPosition(pod0.Get(0), 1.0, 20.0);  // 设置节点位置

输出文件:

  • DCN_FatTree_CSMA_Pcap-*.pcap: Wireshark 可读的数据包文件
  • DCN_FatTree_FlowStat.flowmon: 流量统计 XML 文件
  • animation.xml: NetAnim 动画文件

关键技术点

1. Fat-Tree 拓扑特性

对称性

  • 每个 Pod 的结构完全相同
  • 每个服务器到任意其他服务器的路径数量相同
  • 保证了公平的带宽分配

多路径

对于跨 Pod 通信(如 Pod0.Server0 → Pod2.Server0),存在多条等价路径:

1
2
3
4
5
路径 1: Server0 → AccessSW4 → AggrSW6 → Core0 → AggrSW6 → AccessSW4 → Server0
路径 2: Server0 → AccessSW4 → AggrSW6 → Core1 → AggrSW6 → AccessSW4 → Server0
路径 3: Server0 → AccessSW4 → AggrSW7 → Core2 → AggrSW6 → AccessSW4 → Server0
路径 4: Server0 → AccessSW4 → AggrSW7 → Core3 → AggrSW6 → AccessSW4 → Server0
... (共 8 条等价路径)

无阻塞

  • 理论上可以实现全二分带宽 (Full Bisection Bandwidth)
  • 任意一半服务器可以同时以全速与另一半通信

2. ECMP 路由机制

工作原理

  1. 路由表计算: 全局路由助手为每个节点计算到所有目标的最短路径
  2. 等价路径识别: 当存在多条相同代价的路径时,标记为等价路径
  3. 流量分配: 根据流的五元组 (源IP、目标IP、源端口、目标端口、协议) 哈希选择路径
  4. 负载均衡: 不同的流会被分配到不同的路径上

优势

  • 简单: 不需要复杂的流量工程
  • 高效: 利用了网络的所有可用路径
  • 公平: 长期来看,流量分布均匀

局限

  • 流粒度: 同一个流的所有数据包走相同路径(避免乱序)
  • 哈希碰撞: 可能导致某些路径过载
  • 大象流: 单个大流可能占满一条路径

3. P2P 链路与子网划分

为什么使用 P2P?

  • 物理隔离: 每条链路独立,不会相互干扰
  • 简化路由: 不需要 ARP,直接转发
  • 性能: 无冲突,无竞争
  • 兼容性: 与 Ipv4GlobalRouting 完美配合

为什么使用 /30 子网?

  • 地址效率: 每个 /30 子网提供 2 个可用地址(.1 和 .2)
  • 兼容性: ns-3 的 Ipv4AddressHelper 完全支持
  • 清晰性: 每条链路有独立的子网,便于调试

地址分配示例

1
2
3
4
10.0.0.0/30:
  - 网络地址: 10.0.0.0
  - 可用地址: 10.0.0.1 (Server0), 10.0.0.2 (AccessSW4)
  - 广播地址: 10.0.0.3

4. 队列管理

DropTailQueue

  • 策略: FIFO (先进先出)
  • 丢包: 队列满时丢弃新到达的数据包
  • 简单: 实现简单,开销低
  • 公平性: 对所有流一视同仁

队列大小设置

1
2
Corequeuesize = 8;   // 核心层: 8 个数据包
Leafqueuesize = 4;   // 接入/汇聚层: 4 个数据包

设计考虑:

  • 核心层队列更大,因为汇聚了更多流量
  • 队列过大会增加延迟
  • 队列过小会增加丢包率
  • 需要根据实际流量模式调整

IP 地址分配方案

规律化设计

本项目采用了高度规律化的 IP 地址分配方案,便于理解、调试和扩展。

地址结构

1
2
3
4
5
10.A.B.C/30
 │ │ │ └─ 主机位 (1-2)
 │ │ └─── 子网 ID (0-15)
 │ └───── Pod ID (0-3) 或 10 (核心层)
 └─────── 固定为 10

Pod 内地址分配

Pod 子网范围 用途
Pod 0 10.0.0.0/30 ~ 10.0.7.0/30 Pod 内 8 条链路
Pod 1 10.1.0.0/30 ~ 10.1.7.0/30 Pod 内 8 条链路
Pod 2 10.2.0.0/30 ~ 10.2.7.0/30 Pod 内 8 条链路
Pod 3 10.3.0.0/30 ~ 10.3.7.0/30 Pod 内 8 条链路

Pod 内子网分配细节 (以 Pod 0 为例)

子网 链路 地址 1 地址 2
10.0.0.0/30 Server0 ↔ AccessSW4 10.0.0.1 10.0.0.2
10.0.1.0/30 Server1 ↔ AccessSW4 10.0.1.1 10.0.1.2
10.0.2.0/30 Server2 ↔ AccessSW5 10.0.2.1 10.0.2.2
10.0.3.0/30 Server3 ↔ AccessSW5 10.0.3.1 10.0.3.2
10.0.4.0/30 AccessSW4 ↔ AggrSW6 10.0.4.1 10.0.4.2
10.0.5.0/30 AccessSW4 ↔ AggrSW7 10.0.5.1 10.0.5.2
10.0.6.0/30 AccessSW5 ↔ AggrSW6 10.0.6.1 10.0.6.2
10.0.7.0/30 AccessSW5 ↔ AggrSW7 10.0.7.1 10.0.7.2

核心层地址分配

子网 链路 地址 1 地址 2
10.10.0.0/30 Pod0.AggrSW6 ↔ Core0 10.10.0.1 10.10.0.2
10.10.1.0/30 Pod1.AggrSW6 ↔ Core0 10.10.1.1 10.10.1.2
10.10.2.0/30 Pod2.AggrSW6 ↔ Core0 10.10.2.1 10.10.2.2
10.10.3.0/30 Pod3.AggrSW6 ↔ Core0 10.10.3.1 10.10.3.2
10.10.4.0/30 Pod0.AggrSW6 ↔ Core1 10.10.4.1 10.10.4.2
10.10.15.0/30 Pod3.AggrSW7 ↔ Core3 10.10.15.1 10.10.15.2

服务器 IP 地址快速查询表

服务器 IP 地址 所属 Pod 连接的交换机
Pod0.Server0 10.0.0.1 Pod 0 AccessSW4
Pod0.Server1 10.0.1.1 Pod 0 AccessSW4
Pod0.Server2 10.0.2.1 Pod 0 AccessSW5
Pod0.Server3 10.0.3.1 Pod 0 AccessSW5
Pod1.Server0 10.1.0.1 Pod 1 AccessSW4
Pod1.Server1 10.1.1.1 Pod 1 AccessSW4
Pod1.Server2 10.1.2.1 Pod 1 AccessSW5
Pod1.Server3 10.1.3.1 Pod 1 AccessSW5
Pod2.Server0 10.2.0.1 Pod 2 AccessSW4
Pod2.Server1 10.2.1.1 Pod 2 AccessSW4
Pod2.Server2 10.2.2.1 Pod 2 AccessSW5
Pod2.Server3 10.2.3.1 Pod 2 AccessSW5
Pod3.Server0 10.3.0.1 Pod 3 AccessSW4
Pod3.Server1 10.3.1.1 Pod 3 AccessSW4
Pod3.Server2 10.3.2.1 Pod 3 AccessSW5
Pod3.Server3 10.3.3.1 Pod 3 AccessSW5

地址规律总结

  1. Pod 识别: 第二个八位组直接对应 Pod ID (0, 1, 2, 3)
  2. 子网识别: 第三个八位组对应子网 ID (0-7 for Pod, 0-15 for Core)
  3. 主机识别: 第四个八位组的 .1 通常是服务器或下层设备,.2 是交换机或上层设备
  4. 核心层标识: 使用 10.10.x.0 便于与 Pod 内地址区分

扩展性

如果需要扩展到 k=8 Fat-Tree:

  • Pod 0-7: 10.0.x.0/30 ~ 10.7.x.0/30
  • 核心层: 10.10.x.0/30 ~ 10.10.63.0/30 (64 条核心链路)

运行与调试

编译项目

1
2
cd /home/ceylan/Desktop/code/ns3/ns-3-allinone/ns-3.44
./ns3 build

运行仿真

1
2
3
4
5
# 默认运行 (启用 ECMP)
./ns3 run DCN_FatTree_CSMA

# 禁用 ECMP
./ns3 run "DCN_FatTree_CSMA --ECMProuting=false"

使用 GDB 调试

在 VS Code 中选择 (gdb) Launch FatTree CSMA 调试配置,或在终端中:

1
./ns3 run DCN_FatTree_CSMA --gdb

查看输出文件

1. FlowMonitor 统计

1
2
3
4
5
# 查看 XML 文件
cat DCN_FatTree_FlowStat.flowmon

# 或使用 Python 脚本解析
python3 utils/flowmon-parse-results.py DCN_FatTree_FlowStat.flowmon

2. PCAP 数据包分析

1
2
3
4
5
# 使用 Wireshark 打开
wireshark DCN_FatTree_CSMA_Pcap-0-0.pcap

# 或使用 tcpdump
tcpdump -r DCN_FatTree_CSMA_Pcap-0-0.pcap -n

3. NetAnim 动画

1
2
# 使用 NetAnim 打开
netanim animation.xml

启用详细日志

1
2
3
// 在代码中添加
LogComponentEnable("Ipv4GlobalRouting", LOG_LEVEL_LOGIC);
LogComponentEnable("TcpSocketBase", LOG_LEVEL_INFO);

或在运行时:

1
NS_LOG="Ipv4GlobalRouting=level_logic:TcpSocketBase=level_info" ./ns3 run DCN_FatTree_CSMA

可扩展点

1. 增加流量模式

Incast 流量 (多对一)

1
2
3
4
5
6
7
8
// 多个服务器同时向一个服务器发送数据
for (int i = 0; i < 4; i++) {
    BulkSendHelper source("ns3::TcpSocketFactory", 
                          InetSocketAddress(pod0_Iface.GetAddress(0), 80));
    source.SetAttribute("MaxBytes", UintegerValue(100000));
    ApplicationContainer app = source.Install(pod1.Get(i));
    app.Start(Seconds(1.0));
}

All-to-All 流量

1
2
3
4
5
6
7
8
// 所有服务器互相通信
for (int src_pod = 0; src_pod < 4; src_pod++) {
    for (int dst_pod = 0; dst_pod < 4; dst_pod++) {
        if (src_pod != dst_pod) {
            // 创建从 src_pod 到 dst_pod 的流量
        }
    }
}

2. 实现更大规模的 Fat-Tree

1
2
3
4
5
6
7
// k=8 Fat-Tree
// 64 台服务器, 80 个交换机
int k = 8;
int num_pods = k;
int servers_per_pod = (k * k) / 4;
int switches_per_pod = k;
int core_switches = (k * k) / 4;

3. 添加拥塞控制算法

1
2
3
4
5
6
7
// 使用 DCTCP
Config::SetDefault("ns3::TcpL4Protocol::SocketType", 
                   StringValue("ns3::TcpDctcp"));

// 启用 ECN
Config::SetDefault("ns3::TcpSocketBase::UseEcn", 
                   StringValue("On"));

4. 实现优先级队列

1
2
3
4
// 使用 PrioQueue 替代 DropTailQueue
SWToSW_50ns.SetQueue("ns3::PrioQueue", 
                     "MaxSize", StringValue("8p"),
                     "NumBands", UintegerValue(4));

5. 添加链路故障模拟

1
2
3
4
5
6
7
// 在仿真中途断开一条链路
Simulator::Schedule(Seconds(5.0), &DisableLink, pod0_dev5);

void DisableLink(NetDeviceContainer& dev) {
    dev.Get(0)->SetDown();
    dev.Get(1)->SetDown();
}

6. 实现自定义路由算法

1
2
3
4
5
6
7
8
9
10
// 替换全局路由为自定义路由
Ipv4StaticRoutingHelper staticRouting;
Ipv4ListRoutingHelper list;
list.Add(staticRouting, 0);

// 手动添加路由规则
Ptr<Ipv4StaticRouting> routing = staticRouting.GetStaticRouting(node->GetObject<Ipv4>());
routing->AddNetworkRouteTo(Ipv4Address("10.1.0.0"), 
                           Ipv4Mask("255.255.0.0"),
                           Ipv4Address("10.0.4.2"), 1);

7. 性能优化

使用更高效的队列算法

1
2
3
4
5
6
7
8
9
// RED (Random Early Detection)
SWToSW_50ns.SetQueue("ns3::RedQueue",
                     "MinTh", DoubleValue(5),
                     "MaxTh", DoubleValue(15));

// CoDel (Controlled Delay)
SWToSW_50ns.SetQueue("ns3::CoDelQueue",
                     "Target", TimeValue(MilliSeconds(5)),
                     "Interval", TimeValue(MilliSeconds(100)));

调整 TCP 参数

1
2
3
4
5
6
7
// 增大 TCP 窗口大小
Config::SetDefault("ns3::TcpSocket::RcvBufSize", UintegerValue(1 << 21));
Config::SetDefault("ns3::TcpSocket::SndBufSize", UintegerValue(1 << 21));

// 使用 TCP CUBIC
Config::SetDefault("ns3::TcpL4Protocol::SocketType",
                   StringValue("ns3::TcpCubic"));

8. 添加流量分析工具

1
2
3
4
5
6
7
8
9
10
// 自定义流量分析回调
void PacketSinkRxCallback(Ptr<const Packet> packet, const Address& from) {
    std::cout << "Received packet of size " << packet->GetSize() 
              << " from " << InetSocketAddress::ConvertFrom(from).GetIpv4() 
              << std::endl;
}

// 连接回调
Config::ConnectWithoutContext("/NodeList/*/ApplicationList/*/$ns3::PacketSink/Rx",
                              MakeCallback(&PacketSinkRxCallback));

📚 参考资料