ns-3调研
一、简介
ns-3(network simulator 3)是一个离散事件驱动网络模拟器(discrete-event network simulator for Internet systems),提供了一个开放、可扩展的网络模拟平台,旨在满足学术研究和教学对网络仿真模拟的需求。NS-3主要用来模拟计算机网络,可以在一台计算机上模拟现实世界中的各种类型与规模的网络。
ns-3项目是一个始于2006年的开源项目,负责开发ns-3软件。 ns-3并不是ns-2的扩展,而是一个全新的模拟器。其是为了取代知名的NS-2模拟器而开发的另外一个开源项目。
NS-3和NS-2的联系与区别:
- NS-3虽然叫做NS的版本3,但事实上跟它广泛流行的前任NS-2并非完全一致。它仅仅继承了NS-2的名称而已。NS-3是一个全新的模拟器,不支持NS-2的API,也不支持任何已经存在的NS-2的模块或者脚本(与ns-2没啥关系,且与ns-2不兼容)。
- 但是NS-3当中的很多算法和协议确是受到了NS-2的启发。或者说有很多算法和协议都是从NS-2的模块中移植(当然是基本都重写了一遍)过来的。
- NS-3当中支持很多NS-2所不支持的特性。例如:正确的多网卡处理、IP寻址策略的使用、更详细的802.11模块等等。NS-3能生成pcap文件,因此可以使用类似于WireShark的软件对数据进行分析。(功能更丰富)
- 现有的网络模拟/仿真工具:OPNET、OMNET++、ns-2。
NS-3的开发环境
- NS-3的脚本由C++或者Python编写。从NS-3.2开始,NS3的API提供了python语言接口,但是所有的模块都是由C++编写的,Python仅仅是做了一种映射。
- NS-3使用
waf
作为其自动化编译工具(类似于流行的make
;waf
是基于Python语言的源码构建系统,与make
的功能相同);- NS-3使用
Mercurial
作为其版本管理系统(类似于流行的Git
;2018年12月ns-3的源码管理工具从Mercurial
迁移到了Git
。)。- NS-3基本上只能运行在Linux系统中,或者可以使用Windows系统安装虚拟机,或者安装Cygwin环境。但是Linux原生环境是最好的。
- 虽然有动画、数据分析与可视化工具可用。绝大工作还是在终端命令行中完成。
ns-3实现了大部分的Internet协议和算法,可以进行多种网络的仿真,包括有线网络、WIFI、4G等。大多数的ns-3模型用于建模Internet协议和网络是如何工作的。ns-3不局限于Internet系统。也有一些用户用于建模非Internet系统
对于比较复杂的技术ns-3包含一个DCE模块,可以直接运行原生Linux程序作为仿真的一部分。这样可以方便地调用原生的应用程序、路由算法、协议栈等等,以获得更加真实的仿真效果。
二、NS-3目录结构
AUTHORS examples src utils.pyc wutils.py
bindings LICENSE test.py VERSION wutils.pyc
build Makefile testpy.supp waf
CHANGES.html README twoflow-1-0.pcap waf.bat
contrib RELEASE_NOTES utils waf-tools
doc scratch utils.py wscript
waf
:基于Python开发的编译工具,ns-3系统本身和我们写的仿真代码都是由waf负责编译运行的。scratch
:该目录一般存放用户脚本文件,也可以把要运行的例子拷贝到此目录下,该目录是ns-3默认的脚本存放目录,使用waf
编译运行脚本文件时,可以不加目录scratch
,如果脚本文件在其他目录下需要在文件名前加目录examples
:是由ns-3提供的关于如何使用ns-3的例子,包含了许多模块的使用,如能量、路由、无线网络等,入门ns-3这个目录下例子很重要。其中,tutorial目录下的例子很适合入门者学习。doc
:帮助文档。build
:ns-3编译结果存放目录,包含编译文件时使用的共享库和头文件。src
:ns-3源代码目录。
三、NS-3核心概念
- Node类:表示主机、路由器;
- Application类:表示网络应用程序;例如,客户端应用程序;
- Channel类:表示信道;
- NetDevice类:表示Node上的网络通信设备及驱动程序,负责将Node连接到Channel。例如,CsmaNetDevice, PointToPointNetDevice, WifiNetDevice;
- Topology Helper:把NetDevice连接到Node和Channel上,并配置它们;例如,分配ip地址;
ns-3仿真中两个节点通信所要经历的模块。
1. Node 节点
- node的概念对应于host, end system等。在因特网术语中,连接到网络的计算设备称为主机(host)或有时称为终端系统(end system)。 由于ns-3是网络模拟器,而不是特定的Internet模拟器,因此我们故意不使用术语host,因为它与Internet及其协议密切相关。 相反,我们使用一个更通用的术语,也用于源于图论的其他模拟器 - 节点(node)。
- ns3将基本的计算设备抽象为节点(node),这个抽象在C++中由Node类表示。node类提供了许多方法模拟计算设备在计算机网络中的行为;
- 可以把node看作一台要添加功能的计算机(什么都没安装的裸机),接下来需要向计算机内添加软硬件功能部分:应用程序、协议栈、计算机外设以及相关驱动程序等,以使计算机能够执行有用的工作。
2. Application 应用程序
- ns-3应用程序在ns-3节点上运行,以在模拟世界中驱动模拟。应用程序是运行在node内部的用户软件。
- 在ns-3中,生成一些要模拟的活动的用户程序的基本抽象是应用程序。这个抽象在C ++中由Application类表示。
- ns-3期望开发人员在面向对象的编程意义上专门化Application类来创建新的应用程序(可以设计自己的Apllication类以特定的方式处理和分发数据包)。在ns-3中提供了名为
UdpEchoClientApplication
和UdpEchoServerApplication
的Application类。这些应用程序组成了一个客户端/服务器应用程序集,用于生成和回显模拟的网络数据包。
3. Channel 信道
- 在现实世界中,人们可以将计算机连接到网络。数据在这些网络中流动的媒体通常称为信道(channel)。将以太网电缆连接到墙上的插头时,将计算机连接到以太网通信通道。在ns-3的模拟世界中,将节点连接到表示通信信道的对象。这里基本的通信子网抽象称为信道,并在C ++由Channel类表示。
- 信道是数据的传输通道。Channel类提供管理通信子网对象和将节点连接到它们的方法。信道可以模拟像网线一样简单的东西,还可以模拟像大型以太网交换机那样复杂的东西,或者在无线网络的情况下充满障碍物的三维空间。
- 常见的Channel类:
CsmaChannel
、PointToPointChannel
、WifiChannel
。例如,CsmaChannel模拟实现载波侦听多址通信介质的通信子网的版本。
4. NetDevice 网络设备
- 过去,如果您想将计算机连接到网络,则必须购买特定类型的网络电缆和称为需要在计算机中安装的外围设备卡的硬件设备。如果外围设备卡实现了某些网络功能,则称为网络接口卡或网卡。今天,大多数计算机都内置了网络接口硬件,用户看不到这些构建模块。如果没有软件驱动程序来控制硬件,网卡(NIC将无法运行。在Unix(或Linux)中,一块外围硬件被归类为设备。使用设备驱动程序控制设备,并使用统称为网络设备的网络设备驱动程序控制网络设备(NIC)。在Unix和Linux中,您可以通过名称(例如eth0)来引用这些网络设备。(node与channel连接必须有net device,例如网卡)
- 在ns-3中,网络设备抽象为涵盖了软件驱动程序和模拟硬件。网络设备被“安装”在节点中,以使节点能够通过信道与模拟中的其他节点通信。就像在真实计算机中一样,节点可以通过多个NetDevices连接到多个信道。
- 网络设备抽象在C++中由NetDevice类表示。 NetDevice类提供了管理Node和Channel对象连接的方法。
- ns-3中的NetDevice类包括:
CsmaNetDevice
,PointToPointNetDevice
和WifiNetDevice
。正如以太网NIC设计用于以太网网络一样,CsmaNetDevice
设计用于CsmaChannel
;PointToPointNetDevice
旨在与PointToPointChannel
协同工作,WifiNetNevice
旨在与WifiChannel
协同工作。 - net device 被安装在node中,使当前node能够和其他node构建网络(借助channel)。
5. Topology Helpers 拓扑辅助类
- 在实际网络中,您将找到具有添加(或内置)NIC的主机。 在ns-3中,我们会说你会找到带有NetDevices的节点。 在大型模拟网络中,您需要在节点,NetDevices和Channels之间安排许多连接。
- 由于将NetDevices连接到节点、NetDevices到通道、分配IP地址等是ns-3中的常见任务,因此ns-3提供了所谓的拓扑辅助工具,以使其尽可能简单(让配置操作不那么繁琐复杂)。 例如,可能需要许多不同的ns-3核心操作来创建NetDevice,添加MAC地址,在节点上安装该网络设备,配置节点的协议栈,然后将NetDevice连接到Channel。 将多个设备连接到多点通道然后将各个网络连接到互联网络中将需要更多操作。 ns-3提供拓扑辅助对象,将这些许多不同的操作组合成一个易于使用的模型,以方便使用。
- 核心:简化网络拓扑配置,将机械的重复配置使用Helper实现。
- Helpers类包括:
PointToPointHelper
,InternetStackHelper
,Ipv4AddressHelper
,UdpEchoServerHelper
,UdpEchoClientHelper
。
四、ns-3模拟的基本流程
-
选择或开发相应的模块
根据实际仿真对象和仿真场景选择相应的仿真模块:如有线局域网(CSMA)或还是无线局域网(Wi-Fi),若是没有相应模块,那就需要自己编写。
-
编写网络仿真脚本
若是有了相应的模块,那么我们就可以搭建网络仿真环境,ns-3仿真脚本支持2种语言:C++和Python,两种语言接口的API接口是一样的,但是部分API可能没有Python的接口。所以,仿真主要还是采用C++进行编写。
编写ns-3仿真脚本的大体过程如下:
- 生成节点:ns-3中节点相当于一个空的计算机外壳,我们需要根据需求给这个计算机安装网络所需要的软硬件,如网卡、应用程序、协议栈等。
- 安装网络设备:不同的网络类型有不同的网络设备,从而提供不同的通信、物理层和MAC层,如CSMA WI-FI WIMAX和point-to-point等。
- 安装协议栈:ns-3网络中一般是TCP/IP协议栈,依据网络选择具体协议,如是UDP还是TCP,选择何种不同的路由协议(OLSR AODV 和Global等)并为其配置相应的IP地址,ns-3既支持IPv4也支持IPv6 。
- 安装应用层协议:依据选择的传输层协议选择相应的应用层协议,但是有时需要自己编写应用层产生数据流量的代码。
- 其他配置:如节点是否移动,是否需要能量管理等
- 启动仿真:整个网络场景配置完毕,启动仿真
-
仿真结果分析
仿真结果一般有两种:一种是网络场景,二是网络数据。
- 网络场景,如节点拓扑结构、移动模型等,一般通过可视化界面(PyViz或者NetAnim)可直接观测到。
- 网络数据,可以在可视化界面进行简单统计,此外还可以通过专门的统计框架
status
或者自行通过ns-3提供的追踪框架收集、统计和分析相应的网络数据,如数据分组的延迟、网络流量、分组丢失和节点消息缓存队列等等。
-
依据仿真结果调整网络配置参数或者修改源代码。
五、模拟脚本示例讲解
ns-3.x->examples->tutorial
目录下有案例脚本文件,既有c文件,也有对应的python语言文件。可以通过示例脚本来了解怎么编写网络模拟脚本。
示例一
模拟脚本路径: ns-3.33->examples->tutorial->first.cc
-
源代码
/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ // 1. 头文件 //all header file is at build/ns3 #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/netanim-module.h" // Default Network Topology // // 10.1.1.0 // n0 -------------- n1 // point-to-point // // 2. 命名空间:将ns3项目与非ns3项目区分开。在ns3使用库函数时需要添加std名字空间,如std::cout等。 using namespace ns3; // 3. 定义LOG模块(允许脚本使用 log 系统中的宏定义打印辅助信息) NS_LOG_COMPONENT_DEFINE ("FirstScriptExample"); // 4. 主函数,main()函数中的准备工作,如读取命令行参数,设置最小模拟时间单元,开启log组件等。 int main (int argc, char *argv[]) { CommandLine cmd; // 在命令行中可以输入参数,例如sudo ./waf --run "hello-simulator --numbers=5" cmd.Parse (argc, argv); // 读取命令行参数 //this is to control time resolution unit (NS is millisecond)& how many log outputed Time::SetResolution (Time::NS); // 最小单元时间,毫秒 LogComponentEnable ("UdpEchoClientApplication", LOG_LEVEL_INFO); //记录指定内容信息 LogComponentEnable ("UdpEchoServerApplication", LOG_LEVEL_INFO); //------------------------------------ //below is to build network topology // 5. 创建网络拓扑(Node, Channel, NetDevice):创建节点、配置信道属性、创建信道并链接节点 //create two empty nodes by nodecontainer, and putint it. //you can get those nodes by nodes.Get (0) and nodes.Get (1) NodeContainer nodes; nodes.Create (2); // 创建两个网络节点,访问:nodes.Get(0)、nodes.Get(1) PointToPointHelper pointToPoint; // 创建P2P助手类 pointToPoint.SetDeviceAttribute ("DataRate", StringValue ("5Mbps")); // 设置设备属性(设备传输速率为5Mbps) pointToPoint.SetChannelAttribute ("Delay", StringValue ("2ms")); // 设置设备属性(延迟为2ms) //this is use PointToPointHelper, install devices onto two nodes NetDeviceContainer devices; // 创建网络设备 devices = pointToPoint.Install (nodes); // pointToPoint.Install(nodes)函数创建了两个P2P网络设备对象和一个P2P信道对象,并将他们连接起来 // 6. 安装 TCP/IP协议簇 //this is to install internet protocal into the nodes InternetStackHelper stack; stack.Install (nodes); //为 nodes 容器中的节点安装TCP/IP协议栈 //this is to set ip address: start from 10.1.1.0, subnet mask Ipv4AddressHelper address; //为网络设备分配 ip 地址 address.SetBase ("10.1.1.0", "255.255.255.0"); //设置起始地址,子网掩码 //this is assign ip address to every devices Ipv4InterfaceContainer interfaces = address.Assign (devices); // 把生成的IP分配给每个网络设备 //finished setting bottom layer //------------------------------ //start setting upper layer // 7. 安装应用程序:有很多不同的分组收发定义,如BulkSendApplication使用了贪婪分组发送模型,OnOffApplication使用了ON/OFF分组发生模型。 //set server's port=9 UdpEchoServerHelper echoServer (9); // 服务端助手类,服务端监听端口设为 9 //ApplicationContainer is to log every node's application //this is to install application, echoServer, into the node 1 ; set star time and end time ApplicationContainer serverApps = echoServer.Install (nodes.Get (1)); // echoServer.Install()方法把echoServer应用安装在1号节点,并用ApplicationContainer记录 serverApps.Start (Seconds (1.0)); // 设置 application 在第 1s 开始运行 serverApps.Stop (Seconds (10.0)); // 设置 application 在第 10s 结束运行 //interfaces.GetAddress (1)=ip address of server node; 9=port of server node UdpEchoClientHelper echoClient (interfaces.GetAddress (1), 9); // 客户端助手类,UdpEchoClientHelper在0号节点创建应用echoClient属性:链接1好ip地址和端口 echoClient.SetAttribute ("MaxPackets", UintegerValue (1)); // 最大发送组数 1 echoClient.SetAttribute ("Interval", TimeValue (Seconds (1.0))); // 分组发送间隔 1s echoClient.SetAttribute ("PacketSize", UintegerValue (1024)); // 分组负载字节大小,1024bit // 8. 数据生成 ApplicationContainer clientApps = echoClient.Install (nodes.Get (0)); clientApps.Start (Seconds (2.0)); clientApps.Stop (Seconds (10.0)); // 模拟启动后2s向节点1的9号端口发一个1024bit的UDP数据包,第10s结束 //this is set for visualization (NetAnim) AnimationInterface anim("first.xml"); anim.SetConstantPosition(nodes.Get(0), 1.0, 2.0); anim.SetConstantPosition(nodes.Get(1), 2.0, 3.0); // 9. 启动与结束 Simulator::Run (); Simulator::Destroy (); return 0; }
示例二
模拟脚本路径: ns-3.33->examples->tutorial->second.cc
-
源代码
/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ // 运行指令,在ns-3.29下 // cp examples/tutorial/second.cc scratch/second.cc // ./waf --run second // ./waf --run second --vis # 可视化 // ./waf --run "second -- nCsma=100" --vis # 局域网中节点数3->100 // 1. 头文件 #include "ns3/core-module.h" #include "ns3/network-module.h" #include "ns3/csma-module.h" #include "ns3/internet-module.h" #include "ns3/point-to-point-module.h" #include "ns3/applications-module.h" #include "ns3/ipv4-global-routing-helper.h" // 仿真拓扑图,有两种网络:p2p网络和CSMA网络,分别有2个和4个节点。其中n1上安装有两种NetDevice,i.e. p2p和csma。n0通过n1与n4通信。 // Default Network Topology // // 10.1.1.0 // n0 -------------- n1 n2 n3 n4 // point-to-point | | | | // ================ // LAN 10.1.2.0 // 2. 命名空间 using namespace ns3; // 3. 定义LOG模块 NS_LOG_COMPONENT_DEFINE ("SecondScriptExample"); // 4. 主函数 int main (int argc, char *argv[]) { bool verbose = true; uint32_t nCsma = 3; // 使用命令行声明nCsma变量 CommandLine cmd; cmd.AddValue ("nCsma", "Number of \"extra\" CSMA nodes/devices", nCsma);//添加命令行参数。nCsma变量表示,CSMA节点数目 cmd.AddValue ("verbose", "Tell echo applications to log if true", verbose); cmd.Parse (argc,argv);//读取命令行参数 if (verbose) { LogComponentEnable ("UdpEchoClientApplication", LOG_LEVEL_INFO); //打印UdpEchoClientApplication组件信息 LogComponentEnable ("UdpEchoServerApplication", LOG_LEVEL_INFO); //打印UdpEchoServerApplication组件信息 } nCsma = nCsma == 0 ? 1 : nCsma; // 5. 创建网络拓扑 NodeContainer p2pNodes; p2pNodes.Create (2); // 创建两个p2p型节点,n0----n1 NodeContainer csmaNodes; csmaNodes.Add (p2pNodes.Get (1)); // n1节点既是p2p节点,又是csma节点 csmaNodes.Create (nCsma); // 创建额外的nCsma个csma节点,n2,n3,n4 PointToPointHelper pointToPoint; // p2p助手类,设置设备和信道属性 pointToPoint.SetDeviceAttribute ("DataRate", StringValue ("5Mbps")); // 设置设备传输速率为5Mbps pointToPoint.SetChannelAttribute ("Delay", StringValue ("2ms")); // 设置信道延迟为2ms NetDeviceContainer p2pDevices; //创建p2p网络设备 p2pDevices = pointToPoint.Install (p2pNodes); //把p2p网络设备分别安装在节点n0和n1上,然后共同连接至同一信道对象 CsmaHelper csma; // csma助手类,设置csma信道属性 csma.SetChannelAttribute ("DataRate", StringValue ("100Mbps")); // 设置传输速率为100Mbps csma.SetChannelAttribute ("Delay", TimeValue (NanoSeconds (6560))); // 设置信道延迟为6560ns NetDeviceContainer csmaDevices; // 创建csma网络设备 csmaDevices = csma.Install (csmaNodes); // 连接节点与信道 // 6. 安装协议栈和分配ip地址 InternetStackHelper stack; // 为每个节点安装协议栈 stack.Install (p2pNodes.Get (0)); stack.Install (csmaNodes); Ipv4AddressHelper address; address.SetBase ("10.1.1.0", "255.255.255.0"); // 为p2p网络设备分配ip地址,起始地址为10.1.1.0,终止地址为10.1.1.254 Ipv4InterfaceContainer p2pInterfaces; p2pInterfaces = address.Assign (p2pDevices); address.SetBase ("10.1.2.0", "255.255.255.0");//为csma网络设备分配ip地址,起始地址为10.1.2.0,终止地址为10.1.2.254 Ipv4InterfaceContainer csmaInterfaces; csmaInterfaces = address.Assign (csmaDevices); // 7. 安装应用程序 UdpEchoServerHelper echoServer (9); // 监听9号端口 // 配置服务端属性 ApplicationContainer serverApps = echoServer.Install (csmaNodes.Get (nCsma));//使用Install方法将echoServer安装在节点n4(n3?)上 serverApps.Start (Seconds (1.0)); serverApps.Stop (Seconds (10.0)); // application在第1s开始运行并接受9号端口数据,在第10s结束。 // 配置客户端属性 UdpEchoClientHelper echoClient (csmaInterfaces.GetAddress (nCsma), 9); echoClient.SetAttribute ("MaxPackets", UintegerValue (1));//最大发送分组个数,1 echoClient.SetAttribute ("Interval", TimeValue (Seconds (1.0)));//分组发送间隔1s echoClient.SetAttribute ("PacketSize", UintegerValue (1024));//数据包大小,1024bit ApplicationContainer clientApps = echoClient.Install (p2pNodes.Get (0));//使用install方法将echoClient安装在节点n0上。 clientApps.Start (Seconds (2.0)); clientApps.Stop (Seconds (10.0)); // application在模拟,第2s开始运行,并接受9号端口的数据,在第10s停止。 // 8. 设置全局路由 Ipv4GlobalRoutingHelper::PopulateRoutingTables (); // 9. 数据追踪 pointToPoint.EnablePcapAll ("second");//EnablePcapAll("second")函数的作用是收集这个信道上所有结点链路层分组收发记录。记录文件格式是pcap,”second”是文件名前缀。 csma.EnablePcap ("second", csmaDevices.Get (1), true);//记录了一个有线节点中CSMA网络设备的分组收发信息 // 10. 启动与结束 Simulator::Run (); Simulator::Destroy (); return 0; }
示例三
模拟脚本路径:ns-3.33->examples->tutorial->third.py
-
源代码
# -*- Mode: Python; -*- # 1. 头文件 from ns import ns import sys # 网络场景,包括了p2p、CSMA有线网络、WIFI无线网络的混合场景 # // Default Network Topology # // # // Wifi 10.1.3.0 # // AP # // * * * * # // | | | | 10.1.1.0 # // n5 n6 n7 n0 -------------- n1 n2 n3 n4 # // point-to-point | | | | # // ================ # // LAN 10.1.2.0 from ctypes import c_bool, c_int nCsma = c_int(3) verbose = c_bool(True) nWifi = c_int(3) tracing = c_bool(False) # 使用命令行声明nCsma变量和nWifi变量 cmd = ns.CommandLine(__file__) cmd.AddValue("nCsma", "Number of extra CSMA nodes/devices", nCsma) cmd.AddValue("nWifi", "Number of wifi STA devices", nWifi) cmd.AddValue("verbose", "Tell echo applications to log if true", verbose) cmd.AddValue("tracing", "Enable pcap tracing", tracing) cmd.Parse(sys.argv) # 读取命令行 # The underlying restriction of 18 is due to the grid position # allocator's configuration; the grid layout will exceed the # bounding box if more than 18 nodes are provided. if nWifi.value > 18: print("nWifi should be 18 or less; otherwise grid layout exceeds the bounding box") sys.exit(1) if verbose.value: # 打印指定LOG组件信息 ns.core.LogComponentEnable("UdpEchoClientApplication", ns.core.LOG_LEVEL_INFO) ns.core.LogComponentEnable("UdpEchoServerApplication", ns.core.LOG_LEVEL_INFO) # 2. 创建网络拓扑 p2pNodes = ns.network.NodeContainer() p2pNodes.Create(2) # 创建两个P2P节点 pointToPoint = ns.point_to_point.PointToPointHelper() # 创建P2P助手类 pointToPoint.SetDeviceAttribute("DataRate", ns.core.StringValue("5Mbps")) # 设置设备传输速率 pointToPoint.SetChannelAttribute("Delay", ns.core.StringValue("2ms")) # 设置信道延迟 p2pDevices = pointToPoint.Install(p2pNodes) csmaNodes = ns.network.NodeContainer() csmaNodes.Add(p2pNodes.Get(1)) csmaNodes.Create(nCsma.value) csma = ns.csma.CsmaHelper() csma.SetChannelAttribute("DataRate", ns.core.StringValue("100Mbps")) csma.SetChannelAttribute("Delay", ns.core.TimeValue(ns.core.NanoSeconds(6560))) csmaDevices = csma.Install(csmaNodes) wifiStaNodes = ns.network.NodeContainer() wifiStaNodes.Create(nWifi.value) wifiApNode = p2pNodes.Get(0) channel = ns.wifi.YansWifiChannelHelper.Default() # 默认传播延迟模型,默认损耗模型 phy = ns.wifi.YansWifiPhyHelper() # 默认误码率模型 phy.SetChannel(channel.Create()) mac = ns.wifi.WifiMacHelper() ssid = ns.wifi.Ssid ("ns-3-ssid") wifi = ns.wifi.WifiHelper() mac.SetType ("ns3::StaWifiMac", "Ssid", ns.wifi.SsidValue(ssid), "ActiveProbing", ns.core.BooleanValue(False)) # 移动节点 staDevices = wifi.Install(phy, mac, wifiStaNodes) # 安装移动节点 mac.SetType("ns3::ApWifiMac","Ssid", ns.wifi.SsidValue (ssid)) # AP节点 apDevices = wifi.Install(phy, mac, wifiApNode) # 为AP节点安装网络设备 mobility = ns.mobility.MobilityHelper() # 移动模型助手类 mobility.SetPositionAllocator("ns3::GridPositionAllocator", "MinX", ns.core.DoubleValue(0.0), "MinY", ns.core.DoubleValue (0.0), "DeltaX", ns.core.DoubleValue(5.0), "DeltaY", ns.core.DoubleValue(10.0), "GridWidth", ns.core.UintegerValue(3), "LayoutType", ns.core.StringValue("RowFirst")) # MinX 起点坐标(0.0,0.0);DeltaX x轴节点间距:5m;DeltaY y轴节点间距:10m;GridWidth 每行最大节点数; mobility.SetMobilityModel ("ns3::RandomWalk2dMobilityModel", "Bounds", ns.mobility.RectangleValue(ns.mobility.Rectangle (-50, 50, -50, 50))) mobility.Install(wifiStaNodes) # 为AP节点设置移动模型 mobility.SetMobilityModel("ns3::ConstantPositionMobilityModel") mobility.Install(wifiApNode) # 3. 安装TCP/IP协议簇 stack = ns.internet.InternetStackHelper() stack.Install(csmaNodes) stack.Install(wifiApNode) stack.Install(wifiStaNodes) address = ns.internet.Ipv4AddressHelper() address.SetBase(ns.network.Ipv4Address("10.1.1.0"), ns.network.Ipv4Mask("255.255.255.0")) p2pInterfaces = address.Assign(p2pDevices) # 给P2P节点分配IP地址 address.SetBase(ns.network.Ipv4Address("10.1.2.0"), ns.network.Ipv4Mask("255.255.255.0")) csmaInterfaces = address.Assign(csmaDevices) # 给CSMA节点分配IP地址 address.SetBase(ns.network.Ipv4Address("10.1.3.0"), ns.network.Ipv4Mask("255.255.255.0")) address.Assign(staDevices) # 给AP节点分配IP地址 address.Assign(apDevices) # 给移动节点分配IP地址 # 4. 安装应用程序 echoServer = ns.applications.UdpEchoServerHelper(9) serverApps = echoServer.Install(csmaNodes.Get(nCsma.value)) serverApps.Start(ns.core.Seconds(1.0)) serverApps.Stop(ns.core.Seconds(10.0)) echoClient = ns.applications.UdpEchoClientHelper(csmaInterfaces.GetAddress(nCsma.value).ConvertTo(), 9) echoClient.SetAttribute("MaxPackets", ns.core.UintegerValue(1)) echoClient.SetAttribute("Interval", ns.core.TimeValue(ns.core.Seconds (1.0))) echoClient.SetAttribute("PacketSize", ns.core.UintegerValue(1024)) clientApps = echoClient.Install(wifiStaNodes.Get (nWifi.value - 1)) clientApps.Start(ns.core.Seconds(2.0)) clientApps.Stop(ns.core.Seconds(10.0)) # 5. 设置路由 ns.internet.Ipv4GlobalRoutingHelper.PopulateRoutingTables() ns.core.Simulator.Stop(ns.core.Seconds(10.0)) # 6. 数据追踪 if tracing.value: phy.SetPcapDataLinkType(phy.DLT_IEEE802_11_RADIO) pointToPoint.EnablePcapAll ("third") phy.EnablePcap ("third", apDevices.Get (0)) csma.EnablePcap ("third", csmaDevices.Get (0), True) # 7. 启动与结束 ns.core.Simulator.Run() ns.core.Simulator.Destroy()
六、网络场景可视化效果
1. NetAnim工具
NetAnim是一个独立的,基于QT4的离线动画演示工具,在NS-3的仿真过程中,生成XML格式的trace,仿真结束后的NetAnim读取该文件显示网络拓扑和节点间数据分组流等动画过程。
2. PyViz工具
PyViz是一个用Python开发的在线ns-3可视化工具,不需要使用trace文件。
七、ns-3 FMI
1. FMI
1.1 FMI简介
为应对 工具碎片化、模型重用和知识产权保护的问题和需求 ,欧洲仿真届提出了 FMI 标准, FMI 标准的全称是 Functional Mock-up Interface ,它是一个不依赖于工具的标准,其通过 XML 文件和已编译的 C 代码的组合来同时支持动态模型的模型交换 ( Model Exchange) 和联合仿真 (Co-Simulation) 。
FMI 为针对功能和性能模型重用的接口标准,通过 FMI 标准导出的文件是一个压缩包,文件的结尾为 .fmu
,故称依据 FMI 标准导出的用于模型重用的文件为 FMU 文件,即 Functional Mock-up Unit 。 FMU 的压缩包文件里包含了描述模型接口信息和数据的.xml
文件、实现模型动态行为功能的文件 (C 代码或二进制文件 ) 和其他用户希望包含在 FMU 中的文件和数据。
FMI 标准共包括两种模型重用的方法: Model Exchange (模型交换)和 Co-Simulation (联合仿真)。两者的区别是依据 Model Exchange 方法导出的 FMU 文件不包含求解模型方程的求解器,而依据 Co-Simulation 方法导出的 FMU 文件**包含求解模型方程的求解器,**包含求解器与否是两者最大的区别。
由于 FMI 标准是一个通用的第三方接口标准,不依赖于任何工具特有的接口形式。所以,只要是支持 FMI 标准的工具都可以将其他工具导出的 FMU 文件导入到自身的软件平台内,这时仿真软件会自动解析 FMU 中的 .xml
文件,并在软件的操作界面上给用户提供操作 FMU 的选项,例如参数设置值的调整,如果是依据 Co-Simulation 方法导出的 FMU 文件,用户还可通过界面设置此 FMU 的求解步长。对于描述模型动态行为的 C 或 DLL 文件,导入 FMU 的仿真软件会自动建立自身求解器与 C 或 DLL 文件的关联关系,并能将用户通过界面设置的一些参数和求解选型应用到稍后的求解过程中。依据 FMI 标准将希望重用的模型导出和导入操作简单,用户无需参与过多的软件配置即可完成,这也是 FMI 标准作为第三方独立接口标准得到大范围推广和应用的基础和优势。
基于 FMI 标准的文件传输
1.2 Co-Simulation 方法
Co-Simulation 即为联合仿真方法,此方法并不是 FMI 标准首创,而是在 FMI 标准的 Co-Simulation 方法制定之前既已存在。所以这里先介绍一下传统的 Co-Simulation 方法,然后介绍 FMI 标准的 Co-Simulation 方法与传统方法的区别和联系。
传统的 Co-Simulation 方法通常为特定软件之间制定的联合仿真接口,依赖于商业工具厂商之间的合作,通常不同的仿真工具之间如果没有合作开发接口,则这两款仿真工具无法进行联合仿真。解决方法通常是用户自己根据需求开发定制的联合仿真接口。缺点是需要投入比较多的软件二次开发工作才能实现不同工具之间的联合仿真,同时,传统的联合仿真接口配置麻烦,依赖所使用的软件工具的版本,且超过三个工具之间的联合仿真易出错,这增加了用户使用的入门门槛。(联合仿真的多个不同的仿真器之间要通信,即数据传输,需要特定的接口,传统联合仿真需要专门为两个仿真器之间设计通信接口)
传统联合仿真的定义:
- 几种仿真工具的耦合
- 每个工具作为模块耦合问题的一个组成部分
- 数据交互被限于离散的通信点
- 在通信点之间各子系统独立求解
传统联合仿真的动机:
- 异构系统的仿真
- 大系统的分割和并行化
- 多速率集成
- 硬件在环仿真
FMI 标准的 Co-Simulation 方法作为后制定者,借鉴了传统联合仿真技术,其涵盖了传统 Co-Simulation 技术的全部功能点,并在此基础上做了进一步的优化和丰富,例如:
- 支持主 / 从架构
- 考虑不同能力的仿真工具
- 支持简单和复杂的耦合算法:
- 迭代和直接反馈算法
- 固定和变化的通信步长
- 允许 ( 高阶 ) 连续输入的插值
- 支持本地和分布式联合仿真方案
FMI 标准的 Co-Simulation 方法的提出统一了之前各仿真软件工具接口“各自为政”的乱象。作为第三方独立的接口标准,为不同仿真软件之间的联合仿真提供了便利的技术途径。
FMI 标准的 Co-Simulation 方法按照不同的使用场景,共分为三种模式:
-
模式一:代码导出方式的 Co-Simulation ( Co-Simulation with generated codeon a single computer )
此种模式导出的 FMU 文件脱离原导出工具,并在使用时无需原导出工具的 License 限制。将 FMU 导入到主控软件中,主控软件占用单独的一个进程控制多个 FMU 同时求解。 -
模式二:工具耦合方式的 Co-Simulation ( Co-Simulation with tool coupling ona single computer )
此种模式导出的 FMU 文件绑定原导出工具的 License , FMU 文件只作为联合仿真的接口封装和数据对接传递功能,模型的实际求解还在各自的软件内部进行,受到各自软件的 License 控制。主控软件负责各软件之间特定时间点的数据互换共享。无论是主控软件还是从属软件,它们各自占用一个进程独立进行求解。 -
模式三:分布式方式的 Co-Simulation ( Distributed Co-Simulationinfrastructure )
模式三与模式二类似,应用模式三时导出的 FMU 文件绑定原导出工具的 License , FMU 文件只作为联合仿真的接口封装和数据对接传递功能,模型的实际求解还在各自的软件内部进行,受到各自软件的 License 控制。**主控软件负责各软件之间特定时间点的数据互换共享。**模式三与模式二的区别在于模式二是在一个计算平台上的多个进程运行各个联合仿真软件,而模式三将各个仿真软件分布于不同的计算平台,不同计算平台之间的数据互换共享除了依赖于 FMU 的封装接口,还需要计算平台之间的通信层传输数据。模式三为分布式仿真,各仿真软件独占各自的计算平台资源,其可仿真的系统模型规模要大于模式二。
对于 FMI 标准的 Co-Simulation 方法, FMI 标准只规定了封装接口,而没有定义 Co-Simulation 实现的算法,对于模式三, FMI 标准同样没有定义分布式方案的通信技术方法。这些未定义的部分由各软件工具厂商自行选择实现技术和路径。对于用户来说,只要联合仿真的工具都遵循 FMI 标准,那么用户在使用时就会配置简单,使用方便。
1.3 FMI小结
FMI 作为功能和性能模型进行模型重用、互换、集成的事实接口标准,解决了工具碎片化导致的各种问题。用户只需遵循 FMI 标准即可比较便捷的完成模型的集成重用。但是, FMI 标准仅定义和给出了完成模型重用和互换的技术方案,对于希望重用的模型是什么、符合什么标准的模型可以重用、建模的标准、模型重用的要求、跨场所集成协作的流程、组织和制度保证、责任分工和管理等并未提及。所以,建议在推广应用 FMI 标准的同时能根据实际工程需要,探索并完善符合各单位场所的相关标准、规范和流程等内容。
八、ns-3 FMI Export Module
1. 简介
这是github上一个开源资源:https://github.com/ERIGrid/ns3-fmi-export
fmi-export
模块支持与 ns-3 脚本进行符合 FMI 标准的仿真耦合,即通过符合 FMI 的协同仿真接口启动和执行 NS-3 脚本。就 FMI 术语而言,ns-3 是从属软件(slave application),生成的 FMU 启动 ns-3 并在运行时同步其执行(工具耦合)。
fmu-examples
模块 提供了使用 FMI 导出模块的示例。该模块包括专用模型(客户端和服务器)、帮助进程和实现示例应用进程的仿真脚本,其功能随后导出为 FMU 以进行协同仿真。此外,测试应用进程(用 Python 编写)显示了如何在仿真中使用生成的 FMU。
ns3-fmi-export
├─fmi-export
│ ├─doc
│ ├─lib
│ ├─model
│ └─scripts
└─fmu-examples
├─doc
├─examples
│ ├─scratch
│ └─test
├─helper
└─model
2. FMI-compliant ns-3 scripts 符合FMI的ns-3脚本
ns-3 脚本必须实现抽象类 SimpleEventQueueFMUBase
。此类提供了一个简单的事件队列,可以通过符合 FMI 的接口进行同步。传入/传出消息与输入/输出变量相关联。每当从网络节点发送消息时,关联的输入变量都会设置为非零整数值,称为message ID。当在另一个网络节点接收到相同的消息时,关联的输出变量被设置为相同的message ID。消息的发送/接收是通过单独的 ns-3 模拟运行来模拟的,其结果作为事件存储在队列中。
要定义符合 FMI 的 ns-3 脚本,必须实现一个继承自 SimpleEventQueueFMUBase
的新类,它提供以下两个方法:
- 方法
initializeSimulation()
:此方法允许将模拟变量(通常是继承类的成员变量)定义为 ns-3 模拟的输入/输出/参数。 - 方法
runSimulation(const double& sync_time)
:只要必须运行新的 ns-3 模拟,就会调用此方法。它包含的大部分代码都是您通常会在 ns-3 脚本中找到的代码。这种模拟的结果可以通过方法addNewEventForMessage(evt_time, msg_id, output_var)
添加到事件队列中。
在类定义之后,必须使用宏 CREATE_NS3_FMU_BACKEND
。这个宏取代了 ns-3 脚本的典型主要方法。
3. 示例
示例应用进程是来自 ERIGrid 项目的测试用例:
- SimpleFMU:一个非常简单的测试用例,其中客户端将数据发送到服务器。
- TC3:该测试用例包括两个智能电表向电压控制器发送数据,电压控制器发送数据以启动 OLTC 变压器的分接位置。该测试用例在 ERIGrid 可交付成果 D-JRA2.2 中有详细描述。
- LSS2:该测试用例还着眼于智能电表向控制器的数据传输,重点关注 Wi-Fi 网络同信道干扰的影响。该测试用例在 ERIGrid 可交付成果 D-JRA2.3 中有详细描述。
模块 fmu-examples
为这些测试用例提供了模型。这些模型实现了专用的客户端和服务器,它们提供了提取消息传输的端到端延迟的功能。基于这些端到端延迟,ns-3 模拟脚本将事件添加到 FMU 的事件队列中。
类 ClientBase
和 ServerBase
是为示例应用进程实现的所有客户端和服务器的基类。已实现的客户端和服务器是如何使用回调函数计算端到端延迟的示例。提供了用于将客户端和服务器包含到模拟脚本中的帮助进程。所有帮助进程都是模板基类 ClientHelperBase
和 ServerHelperBase
的特化。
这些示例在专用的 ns-3 脚本中实现,可以在模块的子目录 examples/scratch
中找到。可以使用 Python 脚本 ns3_fmu_create.py
(来自模块 fmi-export)将脚本转换为 FMU 以进行联合仿真。使用这些 FMU 的测试应用进程(用 Python 编写)可以在模块的子目录 examples/test
中找到。
要构建示例,将目录 fmu-examples
复制到 ns-3 的 src
目录(即包含所有其他 ns-3 模块的目录)。然后切换到 ns-3 根目录并使用 waf
构建模块。
3.1 Example SimpleFMU
(1)概览
可以找到简单示例的 ns-3 脚本 (fmu-examples/examples/scratch/SimpleFMU.cc
)。它实现了一个简单的模拟,其中一个节点 (A) 将消息发送到另一个节点 (B)。
该脚本定义了类SimpleFMU
,它继承自类SimpleEventQueueFMUBase
:
-
该类定义了三个类成员变量:
- 变量
nodeA_send
是fmippInteger
类型,将用作最终 FMU 的输入变量 - 变量
nodeB_receive
是fmippInteger
类型,将用作最终 FMU 的输出变量 - 变量
channel_delay
是fmippReal
类型,将用作最终 FMU 的参数
- 变量
-
函数
initializeSimulation()
:该函数使用宏addIntegerInput(...)
、addIntegerOutput(...)
和addRealParameter(...)
将类成员变量分别定义为输入、输出和参数。此定义足以稍后在 FMU 上创建输入、输出和参数,其名称与脚本中的相应变量完全相同。 -
函数
runSimulation( const double& sync_time )
:每次将新输入设置到 FMU 时,该函数都会运行 ns-3 模拟(并且 FMU 被迭代,见上文)。大多数代码就像普通的 ns-3 脚本一样,但是有一些不同:- 在该函数的开头,检查变量
nodeA_send
是否等于零,以便了解是否确实已发送消息(即,message ID
可用作输入)。 - 在模拟结束时,检索消息的端到端延迟。
- 最后,使用函数
addNewEventForMessage(...)
用新事件更新事件队列。输入是接收到消息的时间(即 FMU 同步的时间加上端到端延迟)、消息 ID 和指向相应输出变量的指针。
在脚本的最后,宏CREATE_NS3_FMU_BACKEND
与新类 (SimpleFMU
) 的名称一起使用。这个宏基本上是 main 函数的替代品。
- 在该函数的开头,检查变量
-
源代码(与ns-3的
first.cc
相似)/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ // ns-3 includes. #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/fmi-export-module.h" #include "ns3/tc3-custom-server.h" #include "ns3/tc3-custom-client.h" #include "ns3/tc3-helper.h" #include <sstream> using namespace ns3; NS_LOG_COMPONENT_DEFINE( "SimpleFMU" ); class SimpleFMU : public SimpleEventQueueFMUBase { public: // Define all FMI input/output variables and parameters as class members: fmippInteger nodeA_send; // Input variable associated to nodeA. fmippInteger nodeB_receive; // Output variable associated to nodeB. fmippReal channel_delay; // Parameter for channel delay. // Define the inputs/outputs/parameters of the ns-3 simulation. virtual void initializeSimulation(); // Define the ns-3 simulation that should be run. virtual void runSimulation( const double& sync_time ); }; void SimpleFMU::initializeSimulation() { // Define FMI integer input variable. addIntegerInput( nodeA_send ); // Define FMI integer output variable. addIntegerOutput( nodeB_receive ); // Define FMI parameter. addRealParameter( channel_delay ); } void SimpleFMU::runSimulation( const double& sync_time ) { // Check if nodeA received an input message. If not, don't run a simulation ... if ( 0 == nodeA_send ) return; std::stringstream str_delay; str_delay << channel_delay << "s"; // Setup of the simulation. NodeContainer nodes; nodes.Create(2); // 创建两个网络节点 PointToPointHelper point_to_point; // 这两个网络节点通过P2P信道连接 point_to_point.SetDeviceAttribute( "DataRate", StringValue( "5Mbps" ) ); point_to_point.SetChannelAttribute( "Delay", StringValue( str_delay.str() ) ); // 设置信道延迟时间为输入的参数 NetDeviceContainer devices; devices = point_to_point.Install( nodes ); // 给两个网络节点安装网卡 InternetStackHelper stack; stack.Install( nodes ); Ipv4AddressHelper address; address.SetBase( "10.1.1.0", "255.255.255.0" ); Ipv4InterfaceContainer interfaces = address.Assign( devices ); // 给两个网络节点分配IP地址 uint16_t port = 9; TC3CustomServerHelper echo_server( port ); ApplicationContainer server_apps = echo_server.Install( nodes.Get(1) ); // 设置第二个网络节点为服务器 server_apps.Start( Seconds(1.0) ); // 设置 application 在第 1s 开始运行 server_apps.Stop( Seconds(10.0) ); TC3CustomClientHelper echo_client( interfaces.GetAddress(1), port ); echo_client.SetAttribute( "MaxPackets", UintegerValue(1) ); echo_client.SetAttribute( "Interval", TimeValue( Seconds(1.0) ) ); echo_client.SetAttribute( "PacketSize", UintegerValue(1024) ); ApplicationContainer client_apps = echo_client.Install( nodes.Get(0) ); // 设置第一个网络节点为客户端,并安装应用程序 client_apps.Start( Seconds(2.0) ); client_apps.Stop( Seconds(10.0) ); // 模拟启动后2s向节点1的9号端口发一个1024bit的数据包,第10s结束 // Run the simulation. Simulator::Run (); // Retrieve the massage delay. const TC3CustomServer& server = dynamic_cast< const TC3CustomServer& >( *server_apps.Get(0) ) ; double delay = server.GetEndToEndDelay(); // Terminate the simulation. Simulator::Destroy (); // Add message as output to nodeB using the calculated delay. addNewEventForMessage( sync_time + delay, nodeA_send, &nodeB_receive ); } // The next line creates a working FMU backend. CREATE_NS3_FMU_BACKEND( SimpleFMU )
(2)Creating the FMU
在 Python 脚本 ns3_fmu_create.py
的帮助下创建 FMU。在命令行中,转到示例目录 src/fmu-examples/examples
并发出以下命令:
$ ./../../fmi-export/ns3_fmu_create.py -v -m SimpleFMU -s scratch/SimpleFMU.cc -f 1 channel_delay=0.2
此命令执行以下操作:
- 它将 FMU 的模型标识符定义为
SimpleFMU
。这意味着生成的 FMU 将称为SimpleFMU.fmu
。 - 它将
scratch/SimpleFMU.cc
定义为用于模拟的 ns-3 脚本。 - 参数
channel_delay
设置为 0.3。
命令行中脚本的输出应类似于以下行。(请注意,在此过程中调用 waf
两次)
[DEBUG] Using FMI version 1
[DEBUG] Found start value: channel_delay = 0.2
Waf: Entering directory `/cygdrive/c/Development/erigrid/ns-3-allinone/ns-3-dev/build'
Waf: Leaving directory `/cygdrive/c/Development/erigrid/ns-3-allinone/ns-3-dev/build'
Build commands will be stored in build/compile_commands.json
'build' finished successfully (1.363s)
Modules built:
antenna aodv applications
bridge buildings config-store
core csma csma-layout
dsdv dsr energy
flow-monitor fmi-export (no Python) fmu-examples (no Python)
internet internet-apps lr-wpan
lte mesh mobility
mpi netanim (no Python) network
nix-vector-routing olsr point-to-point
point-to-point-layout propagation sixlowpan
spectrum stats test (no Python)
topology-read traffic-control uan
virtual-net-device wave wifi
wimax
Modules not built (see ns-3 tutorial for explanation):
brite click fd-net-device
openflow tap-bridge visualizer
[DEBUG] successfully compiled ns-3 script
Waf: Entering directory `/cygdrive/c/Development/erigrid/ns-3-allinone/ns-3-dev/build'
Waf: Leaving directory `/cygdrive/c/Development/erigrid/ns-3-allinone/ns-3-dev/build'
Build commands will be stored in build/compile_commands.json
'build' finished successfully (1.349s)
[DEBUG] successfully created JSON script
[DEBUG] FMI model identifier: SimpleFMU
[DEBUG] ns-3 script: scratch/SimpleFMU.cc
[DEBUG] ns-3 install directory: /cygdrive/c/Development/erigrid/ns-3-allinone/ns-3-dev
[DEBUG] Aditional files:
[DEBUG] Added start value to model description: channel_delay = 0.2
[DEBUG] FMU created successfully: SimpleFMU.fmu
(3)Using the FMU in a simulation
Python 脚本 testSimpleFMU.py
在模拟中使用生成的 FMU。它可以在模块的子目录examples/test
中找到。运行模拟脚本时,输出应类似于以下内容:
[test_sim_ict] WARNING: The path specified for the FMU's entry point does not exist: ""
Use directory of main application as working directory instead.
[test_sim_ict] MIME-TYPE: Wrong MIME type: application/x-waf --- expected:
Waf: Entering directory `/cygdrive/c/Development/erigrid/ns-3-allinone/ns-3-dev/build'
Waf: Leaving directory `/cygdrive/c/Development/erigrid/ns-3-allinone/ns-3-dev/build'
Build commands will be stored in build/compile_commands.json
'build' finished successfully (1.406s)
================================================
simulation time : 0.0
next event time : 0.0
next send time : 1.0
================================================
simulation time : 1.0
next event time : no next event specified
next send time : 1.0
At time 1.00000: SEND message with ID = 1
================================================
simulation time : 1.301686399
next event time : 1.301686399
next send time : 2.0
At time 1.30169: RECEIVE message with ID = 1
================================================
simulation time : 2.0
next event time : no next event specified
next send time : 2.0
At time 2.00000: SEND message with ID = 2
================================================
simulation time : 2.301686399
next event time : 2.301686399
next send time : 3.0
At time 2.30169: RECEIVE message with ID = 2
================================================
simulation time : 3.0
next event time : no next event specified
next send time : 3.0
At time 3.00000: SEND message with ID = 3
================================================
simulation time : 3.301686399
next event time : 3.301686399
next send time : 4.0
At time 3.30169: RECEIVE message with ID = 3
================================================
参考
【NS-3学习】ns-3模拟基础:目录结构,模块,仿真流程 - sakuraxx - 博客园
【NS-3学习】ns3-模拟基础:关键概念,日志,命令行参数 - sakuraxx - 博客园
基于WSL2的NS3环境搭建教程_scratch simulator_不会像仲夏夜之梦一样轻易消失的博客-CSDN博客
【网络仿真】ns-3安装与简介_夕阳下的奔跑517的博客-CSDN博客
https://github.com/jnbai517/study_ns3
(6) NS-3仿真,利用Netnaim实现可视化仿真 - 灰信网(软件开发博客聚合)
NS_3–PyViz(Python开发的在线ns-3可视化工具,不需要使用trace文件)_ns-3 3.27可视化_wuzhiwuweisun的博客-CSDN博客