ns3链路拥塞实验

实验目的:

收集和分析不同背景流下的路径丢包率与时延性能

拓扑结构:

// 仿真网络拓扑
//   n0                            n5
//      \       10 Mb/s,2ms       /
//       \                       /
// n1 ---- n3 ----10Mb/s,10ms---- n4 ---- n6
//       /                       \
//      /                         \
//   n2                            n7
// 
// 网络中链路类型均为p2p
// n3-n4 有10Mb/s带宽和 10ms时延
// 其它链路有10Mb/s带宽和 2ms时延

实验思路:

● 编写"E2ePathCongestionDetection.cc"用于模拟的主要程序,程序中配置了探测流、拥塞流、应用流。命令行参数接口可改变包的字节大小和发包频率。程序内可以生成.tr .xml .flown 几种文件用于分析。
"flowmon-processing.py" 读取NS3 仿真的流监控文件(.flowon),并保存为txt格式在独立的文件夹内(因为.cc需要循环运行,每次都会覆盖上次的文件)
"CongestionProbingDataPlot.py" 利用matplotlib库处理之前生成的数据,生成三维图片,自变量两个维度分别是字节大小和发包频率,因变量分别是Loss Rate,Mean Delay,Mean Jitter,Absolute Relative Error。最后生成四张图片。
'main_E2ePathCongestionDetection.sh'、shell脚本用于自动化的调用上述三个文件,多次循环调用前两个文件,依次通过命令行参数增大字节大小和发包频率,然后记录拥塞情况,从而模拟网络流量由少到多的情况。

实验代码:

main_E2ePathCongestionDetection.sh

  • 该shell脚本做的是宏观调用其它几个程序文件以实现自动化的执行程序。
  • 首先利用pwd命令获取当前的工作目录(scratch目录)的绝对路径,然后指定储存数据的datadir为当前目录的兄弟目录(父目录的子目录即…/data/),并且确保该目录存在且为空(不存在就新建,存在就清空)。
  • 然后是一个双层循环外层循环表示探测报文大小由16到1024步进为16增长,内层循环表示报文发包频率由100到1000hz步进为100增长。对于每次循环调用../waf --run "E2ePathCongestionDetection --pktSize=$Size --probeHz=$Hz"即指定命令行参数为当前配置来运行,然后调用python flowmon-processing.py -s $Size -f $Hz即指定参数来处理上述程序生成的NS3流监控文件(.flowmon结尾)
  • 待循环结束之后执行python3 CongestionProbingDataPlot.py即调用matplotlib库来处理上述程序在data文件夹下生成的文件。
#!/bin/bash
# -*- coding: utf-8 -*-

# 默认当前工作路径为 "ns-allinone-3.34/ns-3.34/scratch/"; *** 务必根据提示确认下
currentDir=$(pwd)
echo "\n***\n-current dir is: $currentDir\n-please carefully confirm that it is on 'ns-allinone-3.34/ns-3.34/scratch/'\n***\n"

# 指定数据文件目录 "ns-allinone-3.34/ns-3.34/data/"
dataDir="../data/"

if [ ! -d $dataDir ];then  # 如果没有 ns-allinone-3.34/ns-3.34/data/ 数据文件夹,就创建一个
    mkdir $dataDir
    echo "\n-Notice: 'ns-allinone-3.34/ns-3.34/data/' newly created\n"
else                       # 如果有该文件夹,就间接实现 “删除已有的、旧的仿真数据文件” 的功能
    rm -rf $dataDir
    echo "\n-Notice: 'ns-allinone-3.34/ns-3.34/data/' newly cleaned\n"
    mkdir $dataDir
fi

for Size in $(seq 16 16 1024);  do  # 指定探测报文的大小
    for Hz in $(seq 100 100 1000);  do  # 指定探测报文的发包频率
        echo "\n-*-\n(Probe Packet Size, Probing Frequency): ($Size, $Hz)"
        ../waf --run "E2ePathCongestionDetection --pktSize=$Size --probeHz=$Hz"  # 运行 NS3 仿真
        echo "-NS3- Simulation Completed"
        python flowmon-processing.py -s $Size -f $Hz  # 处理 NS3 流监控数据文件
        echo "-NS3- Flow Monitor Data Processed\n-*-\n"
    done
done

python3 CongestionProbingDataPlot.py

E2ePathCongestionDetection.cc

  • 这是程序模拟的主要程序,所有的原始数据都从这里得出
#include <iostream>
#include <fstream>
#include <string>
#include <cassert>

#include "ns3/core-module.h"
#include "ns3/network-module.h"
#include "ns3/internet-module.h"
#include "ns3/point-to-point-module.h"
#include "ns3/applications-module.h"
#include "ns3/flow-monitor-helper.h" //流监控头文件声明
#include "ns3/ipv4-global-routing-helper.h"
#include "ns3/netanim-module.h" // 网络仿真动画追踪头文件声明

using namespace ns3;

AnimationInterface* pAnim = 0; //仿真动画指针

const std::string outputDir = "./scratch/"; //指定数据输出到scratch文件夹
//指定数据输出的文件名,后续将根据不同文件类型采用不同后缀
const std::string fileName = "data-E2ePathCongestionDetection";


int main(int argc, char* argv[])
{
	bool enableTrace = false; // 默认关闭网络报文转发追踪trace
	bool enableAnimation = false; // 默认关闭网络仿真动画追踪 netanim
	bool enableFlowMonitor = true; //默认启用流量监控 flowmonitor

	//定义可通过命令行控制的仿真参数
	uint16_t pktSize = 512; //探测流:默认字节大小
	uint32_t probeHz = 1000; //探测流:默认发包频率

	CommandLine cmd(); //开启命令行参数
	cmd.AddValue("pktSize", "Indicate the pakcet size of each probe.", pktSize);
	cmd.AddValue("probeHz", "Indicate the probing frequency.", probeHz);
	cmd.Parse(argc, argv);

	// 创建网络节点
	NodeContainer srcNodes;
	NodeContainer rtNodes;
	NodeContainer dstNodes;

	srcNodes.Create(3); //源节点
	rtNodes.Create(2); //中间节点
	dstNodes.Create(3); //目的节点

	//给节点配置协议栈
	NodeContainer allNodes = NodeContainer(srcNodes, dstNodes, rtNodes);

	InternetStackHelper internet;
	internet.Install(allNodes);

	//设置网络链路属性
	PointToPointHelper p2p;
	p2p.SetDeviceAttribute("DataRate", StringValue("10Mb/s")); // ᬟ边缘链路带宽与时延୊
	p2p.SetChannelAttribute("Delay", StringValue("2ms"));

	NetDeviceContainer s0r0 = p2p.Install(NodeContainer(srcNodes.Get(0), rtNodes.Get(0)));
	NetDeviceContainer s1r0 = p2p.Install(NodeContainer(srcNodes.Get(1), rtNodes.Get(0)));
	NetDeviceContainer s2r0 = p2p.Install(NodeContainer(srcNodes.Get(2), rtNodes.Get(0)));

	NetDeviceContainer r1d0 = p2p.Install(NodeContainer(rtNodes.Get(1), dstNodes.Get(0)));
	NetDeviceContainer r1d1 = p2p.Install(NodeContainer(rtNodes.Get(1), dstNodes.Get(1)));
	NetDeviceContainer r1d2 = p2p.Install(NodeContainer(rtNodes.Get(1), dstNodes.Get(2)));

	p2p.SetDeviceAttribute("DataRate", StringValue("10Mb/s")); // 中间链路带宽与时延
	p2p.SetChannelAttribute("Delay", StringValue("10ms"));
	p2p.SetQueue("ns3::DropTailQueue<Packet>", "MaxSize", QueueSizeValue(QueueSize("150p"))); // 队列缓存大小

	NetDeviceContainer r0r1 = p2p.Install(NodeContainer(rtNodes.Get(0), rtNodes.Get(1)));

	// 配置ipv4地址
	Ipv4AddressHelper ipv4;
	ipv4.SetBase("10.1.1.0", "255.255.255.0"); // 定义网络地址
	Ipv4InterfaceContainer is0r0 = ipv4.Assign(s0r0); // 分配处于该网络中,各节点网卡的IP地址

	ipv4.SetBase("10.1.2.0", "255.255.255.0");
	Ipv4InterfaceContainer is1r0 = ipv4.Assign(s1r0);

	ipv4.SetBase("10.1.3.0", "255.255.255.0");
	Ipv4InterfaceContainer is2r0 = ipv4.Assign(s2r0);

	ipv4.SetBase("10.1.4.0", "255.255.255.0");
	Ipv4InterfaceContainer ir0r1 = ipv4.Assign(r0r1);

	ipv4.SetBase("10.1.5.0", "255.255.255.0");
	Ipv4InterfaceContainer ir1d0 = ipv4.Assign(r1d0);

	ipv4.SetBase("10.1.6.0", "255.255.255.0");
	Ipv4InterfaceContainer ir1d1 = ipv4.Assign(r1d1);

	ipv4.SetBase("10.1.7.0", "255.255.255.0");
	Ipv4InterfaceContainer ir1d2 = ipv4.Assign(r1d2);

	Ipv4GlobalRoutingHelper::PopulateRoutingTables(); //启用全局路由

	//创建和配置探测流:基于onoff(UDP)应用
	uint16_t port = 9; // Discard port (RFC 863)
	OnOffHelper onoff("ns3::UdpSocketFactory",
		Address(InetSocketAddress(ir1d0.GetAddress(1), port)));

	std::string probingRate = std::to_string(probeHz * 8 * pktSize) + std::string("b/s");

	onoff.SetConstantRate(DataRate(probingRate), pktSize);

	ApplicationContainer onoffApps = onoff.Install(srcNodes.Get(0));
	onoffApps.Start(Seconds(1.25));
	onoffApps.Stop(Seconds(1.75));

	PacketSinkHelper onoff_sink("ns3::UdpSocketFactory",
		Address(InetSocketAddress(Ipv4Address::GetAny(), port)));
	ApplicationContainer onoffSink = onoff_sink.Install(dstNodes.Get(0));
	onoffSink.Start(Seconds(1.0));
	onoffSink.Stop(Seconds(2.5));


	//配置背景-拥塞流 (PPBP, https://github.com/sharan-naribole/PPBP-ns3)
	uint16_t ppbp_port = 9;

	PPBPHelper ppbp = PPBPHelper("ns3::UdpSocketFactory",
		InetSocketAddress(ir1d1.GetAddress(1), ppbp_port));

	ppbp.SetAttribute("BurstIntensity", DataRateValue(DataRate("0.5Mb/s"))); //
	ppbp.SetAttribute("PacketSize", UintegerValue(512));
	ppbp.SetAttribute("MeanBurstArrivals", StringValue("ns3::ConstantRandomVariable[Constant=100.0]"));
	ppbp.SetAttribute("MeanBurstTimeLength", StringValue("ns3::ConstantRandomVariable[Constant=0.1]"));

	ApplicationContainer ppbpApps = ppbp.Install(srcNodes.Get(1));
	ppbpApps.Start(Seconds(1.0));
	ppbpApps.Stop(Seconds(2.0));

	Ptr<Socket> ppbpSink = Socket::CreateSocket(dstNodes.Get(1), UdpSocketFactory::GetTypeId());
	InetSocketAddress local = InetSocketAddress(ir1d1.GetAddress(1), ppbp_port);
	ppbpSink->Bind(local);

	//配置应用流(BulkSendApplication,基于tcp;模拟大文件的网络传输)
	uint16_t bs_port = 9; // 网络唤醒端口 参考:https://wenku.baidu.com/view/d921383359fafab069dc5022aaea998fcc2240cd.html
	BulkSendHelper bulk("ns3::TcpSocketFactory",
						InetSocketAddress(ir1d2.GetAddress(1), bs_port));
	
	uint32_t maxBytes = 0; // 设置发送数据的总量,0表示不限
	bulk.SetAttribute("MaxBytes", UintegerValue(maxBytes));
	
	ApplicationContainer bulkApps = bulk.Install(srcNodes.Get(2));
	bulkApps.Start(Seconds(0.0));
	bulkApps.Stop(Seconds(3.0));
	
	PacketSinkHelper bulk_sink("ns3::TcpSocketFactory",
								InetSocketAddress(Ipv4Address::GetAny(), bs_port));
	ApplicationContainer bulkSink = bulk_sink.Install(dstNodes.Get(2));
	bulkSink.Start(Seconds(0.0));
	bulkSink.Stop(Seconds(3.5));
	
	//配置追踪仿真实验各种报文的详细收发情况
	if (enableTrace) {
		AsciiTraceHelper ascii;
		p2p.EnableAsciiAll(ascii.CreateFileStream(outputDir + fileName + ".tr"));
	}

	
	//创建和配置animation文件 路由器等图标文件存放于ns-allinone-3.34/ns-3.34/icons/
	if (enableAnimation) {
		AnimationInterface::SetConstantPosition(srcNodes.Get(0), 2, 2); // 设置节点在NetAnim中的固定位置
		AnimationInterface::SetConstantPosition(srcNodes.Get(1), 2, 7);
		AnimationInterface::SetConstantPosition(srcNodes.Get(2), 2, 12);
		AnimationInterface::SetConstantPosition(rtNodes.Get(0), 7, 7);
		AnimationInterface::SetConstantPosition(rtNodes.Get(1), 17, 7);
		AnimationInterface::SetConstantPosition(dstNodes.Get(0), 22, 2);
		AnimationInterface::SetConstantPosition(dstNodes.Get(1), 22, 7);
		AnimationInterface::SetConstantPosition(dstNodes.Get(2), 22, 12);
		
		pAnim = new AnimationInterface(outputDir + fileName + ".xml");
		
		uint32_t router_img = pAnim->AddResource("./icons/router.png"); // 指定路由器节点的图标;支持png不支持jpg
		uint32_t server_img = pAnim->AddResource("./icons/server.png");
		uint32_t client_img = pAnim->AddResource("./icons/pc.png");
		
		pAnim->UpdateNodeDescription(srcNodes.Get(0), "s0"); // 可自定义节点的显示名称
		pAnim->UpdateNodeDescription(srcNodes.Get(1), "s1");
		pAnim->UpdateNodeDescription(srcNodes.Get(2), "s2");
		pAnim->UpdateNodeDescription(rtNodes.Get(0), "r0");
		pAnim->UpdateNodeDescription(rtNodes.Get(1), "r1");
		pAnim->UpdateNodeDescription(dstNodes.Get(0), "d0");
		pAnim->UpdateNodeDescription(dstNodes.Get(1), "d1");
		pAnim->UpdateNodeDescription(dstNodes.Get(2), "d2");

		pAnim->UpdateNodeImage(0, client_img); //修改节点的图标为上述自定义的图标
		pAnim->UpdateNodeImage(1, client_img);
		pAnim->UpdateNodeImage(2, client_img);
		pAnim->UpdateNodeImage(3, router_img);
		pAnim->UpdateNodeImage(4, router_img);
		pAnim->UpdateNodeImage(5, server_img);
		pAnim->UpdateNodeImage(6, server_img);
		pAnim->UpdateNodeImage(7, server_img);
	}
	
	//配置流量监控器,可参考 https://www.nsnam.org/docs/models/html/flow-monitor.html
	FlowMonitorHelper flowmonHelper;
	if (enableFlowMonitor) {
		flowmonHelper.InstallAll();
	}
	
	Simulator::Stop(Seconds(3.5));
	Simulator::Run();
	
	if (enableFlowMonitor) { // 输出流监控的统计信息,不统计直方图,不记录单个报文的网络转发性能信息
		flowmonHelper.SerializeToXmlFile(outputDir + fileName + ".flowmon", false, false);
	}
	
	if (enableAnimation) { // 删除netanim动画追踪指针
		delete pAnim;
		pAnim = NULL;
	}
	
	Simulator::Destroy(); //释放仿真内存
	return 0;

}


flowmon-processing.py

# -*- coding: utf-8 -*-
# 默认当前工作路径为 "ns-allinone-3.34/ns-3.34/scratch/"

import xml.etree.ElementTree as ET
import argparse

args = argparse.ArgumentParser()
args.add_argument('-s', '--size',  type=int,
                  help='Indicate the pakcet size of each probe.')
args.add_argument('-f', '--frequency', type=int,
                  help='Indicate the probing frequency.')
args = args.parse_args()

'''
如下为设定 "ns-allinone-3.34/ns-3.34/data/" 数据文件目录;
* 不能在 scratch 目录下生成其他目录,否则, waf 编译时会报错
'''
dataDir = '../data/'

# NS3 仿真的流监控文件; *请注意* 按照仿真程序文件里保存的文件名来对应修改如下待读取的文件名
ns3FlowMon ='data-E2ePathCongestionDetection' + '.flowmon'

pktSize = args.size
probeHz = args.frequency

# 读入流监控文件;其实际上为 xml 格式文件
tree = ET.parse(ns3FlowMon)
root = tree.getroot()
flowStats = root[0]

filename = dataDir + 'PktSize-' + \
    str(pktSize) + '-ProbeHz-' + str(probeHz) + '.txt'
file = open(filename, 'a+')
for fid in range(len(flowStats)):
    flow = flowStats[fid].attrib
    pktSentNum = int(flow['txPackets'])
    pktLostNum = int(flow['lostPackets'])

    pktLossRate = pktLostNum / pktSentNum
    temp_data = [pktLossRate, eval(flow['delaySum'][:-2]) / (
        10**9 * pktSentNum), eval(flow['jitterSum'][:-2]) / (10**9 * pktSentNum)]

    for element in temp_data:
        file.write(str(element)+' ')

    if fid < (len(flowStats) - 1):
        file.write('\n')

file.close()

CongestionProbingDataPlot.py

# -*- coding: utf-8 -*-
# 默认当前工作路径为 "ns-allinone-3.34/ns-3.34/scratch/"

import matplotlib.pyplot as plt
from matplotlib import cm
import numpy as np

'''
如下为设定 "ns-allinone-3.34/ns-3.34/data/" 数据文件目录;
请确保 E2ePathCongestionDetection.sh 预先执行成功
'''
dataDir = '../data/'

# Make data.
X = np.arange(16, 1040, 16)
Y = np.arange(100, 1100, 100)

R = np.zeros((2, 3, len(X), len(Y))) # 创建四位数组并全部置为0,

i = -1
for Size in np.arange(16, 1040, 16):
    i = i + 1
    j = -1 
    for Hz in np.arange(100, 1100, 100):
        j = j + 1
        fileName = 'PktSize-' + str(Size) + '-ProbeHz-' + str(Hz) + '.txt'
        temp_R = np.loadtxt(dataDir + fileName)
        temp_R = np.delete(temp_R, [0, 1], axis=0) # 以行的方式删除数组的前两行,过滤掉来回的背景流

        R[:, :, i, j] = temp_R # 由ij指示的二维数组赋值为temp_R 
# 拥塞流 Z_cross
Z_cross = R[0, :, :, :].reshape([3, len(X), len(Y)]) # 将第一维的第一个分量对应的其它三个维度提取出来,即:将拥塞流提取出来
# 探测流 Z_probe
Z_probe = R[1, :, :, :].reshape([3, len(X), len(Y)]) # 将第一维的第二个分量对应的其它三个维度提取出来,即:将探测流提取出来

Xv, Yv = np.meshgrid(X, Y) #生成网格点坐标矩阵


# 下面四个figure对应四个图片
# 前三张图片均含有拥塞流、探测流两个Z轴对应的值
# 三张图片分别对应txt三个字段
# 最后一张图片是前三张的两个流之间的绝对相对误差,用三个Z轴对应的量来描述


# figure(1) ******
fig, ax = plt.subplots(subplot_kw={"projection": "3d"})

# 拥塞流 Z_cross的第一个字段 pktLossRate
# .T 求转置
Z = Z_cross[0, :, :].reshape([len(X), len(Y)]).T # 将第一维的第一个分量对应的其它两个维度提取出来
ax.plot_surface(Xv, Yv, Z, cmap=cm.Spectral,
                linewidth=0, antialiased=False)
# 探测流 Z_probe的第一个字段 pktLossRate
Z = Z_probe[0, :, :].reshape([len(X), len(Y)]).T
ax.plot_surface(Xv, Yv, Z, cmap=cm.seismic,
                linewidth=0, antialiased=False)

ax.set_xlabel('Packet Size')
ax.set_ylabel('Probing Frequency')
ax.set_zlabel('Loss Rate')

Z_error_LR = np.abs(Z_probe[0, :, :].reshape([len(X), len(Y)]).T - Z_cross[0, :,
                    :].reshape([len(X), len(Y)]).T) / Z_cross[0, :, :].reshape([len(X), len(Y)]).T

# figure(2) ******
fig, ax = plt.subplots(subplot_kw={"projection": "3d"})

# 拥塞流 Z_cross的第二个字段 delaySum / (10**9 * pktSentNum )
Z = Z_cross[1, :, :].reshape([len(X), len(Y)]).T # 将第一维的第一个分量对应的其它两个维度提取出来
ax.plot_surface(Xv, Yv, Z, cmap=cm.Spectral,
                linewidth=0, antialiased=False)
# 探测流 Z_probe的第二个字段 delaySum / (10**9 * pktSentNum )
Z = Z_probe[1, :, :].reshape([len(X), len(Y)]).T
ax.plot_surface(Xv, Yv, Z, cmap=cm.seismic,
                linewidth=0, antialiased=False)

ax.set_xlabel('Packet Size')
ax.set_ylabel('Probing Frequency')
ax.set_zlabel('Mean Delay')


Z_error_D = np.abs(Z_probe[1, :, :].reshape([len(X), len(Y)]).T - Z_cross[1, :,
                   :].reshape([len(X), len(Y)]).T) / Z_cross[1, :, :].reshape([len(X), len(Y)]).T

# figure(3) ******
fig, ax = plt.subplots(subplot_kw={"projection": "3d"})

# 拥塞流 Z_cross的第三个字段jitterSum / (10**9 * pktSentNum )
Z = Z_cross[2, :, :].reshape([len(X), len(Y)]).T
ax.plot_surface(Xv, Yv, Z, cmap=cm.Spectral,
                linewidth=0, antialiased=False)

# 探测流 Z_probe的第三个字段jitterSum / (10**9 * pktSentNum )
Z = Z_probe[2, :, :].reshape([len(X), len(Y)]).T
ax.plot_surface(Xv, Yv, Z, cmap=cm.seismic,
                linewidth=0, antialiased=False)

ax.set_xlabel('Packet Size')
ax.set_ylabel('Probing Frequency')
ax.set_zlabel('Mean Jitter')

Z_error_J = np.abs(Z_probe[2, :, :].reshape([len(X), len(Y)]).T - Z_cross[2, :,
                   :].reshape([len(X), len(Y)]).T) / Z_cross[2, :, :].reshape([len(X), len(Y)]).T

# figure(4) ******
fig, ax = plt.subplots(subplot_kw={"projection": "3d"})

ax.plot_surface(Xv, Yv, Z_error_LR, color='red',
                linewidth=0, antialiased=False)

ax.plot_surface(Xv, Yv, Z_error_D, color='green',
                linewidth=0, antialiased=False)

ax.plot_surface(Xv, Yv, Z_error_J, color='blue',
                linewidth=0, antialiased=False)

ax.set_xlabel('Packet Size')
ax.set_ylabel('Probing Frequency')
ax.set_zlabel('Absolute Relative Error')

plt.show()
  • 4
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Cheney822

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值