天天看点

【网络仿真】ns-3基础(上)1 ns-3初识2 ns-3快速上手3 ns-3基础4 ns-3仿真结果统计分析5 ns-3内核

1 ns-3初识

资料:

  1. ns-3官网:http://www.nsnam.org/
  2. ns-3官方开发文档:https://www.nsnam.org/releases/ns-3-34/documentation/
  3. ns-3维基百科(文档中没有的内容)、FAQ问题解答:https://www.nsnam.org/support/
  4. ns-3相关文献:https://www.nsnam.org/education/

2 ns-3快速上手

使用waf配置编译ns-3系统:

./waf clean #清除先前的配置编译
./waf -d optimized --enable-examples --enable-tests configure #重新配置ns-3,优化编译包括例子和测试(优化模式下禁止输出)
./waf -d --enable-examples --enable-tests configure #重新配置ns-3,优化编译包括例子和测试(这样就有输出了)
./waf #正式编译
           

测试安装

./test.py -c core # 测试ns-3发行版是否编译正确
./waf --run scratch-simulator# 运行脚本测试
           

3 ns-3基础

3.1 关键概念

  1. 节点

    Node类描述节点,提供添加外设卡、驱动程序、协议栈、应用程序等功能。

    NodeContainer类,拓扑生成器,用于创建、管理

    使用节点,保存一组节点指针。

NodeContainer nodes;//声明一个名为nodes的NodeContainer
node.Create (2);//调用nodes对象的Create()方法创建2个节点对象,并把2个节点对象的指针存储在系统中
           
  1. 应用

    Application类描述。

    UdpEchoServerHelper和UdpEchoClientHelper为Application类实例。(动物类->实例:熊猫类)

  2. 信道

    Channel类,实例包括:CsmaChannel(模拟了载波侦听多路访问通信子网中的媒介,有以太网的功能)、PointToPointChannel(最多2个点到点连接的网络设备)、WifiChannel(无线信道)

  3. 网络设备(网卡)

    NetDevice类,提供连接节点和信道对象的各种方法。实例有:CsmaNetDevice、PointToPointNetDevice、Wi-FiNetDevice。

    NetDeviceContainer类存放(类似NodeContainer类)。

  4. 拓扑帮助

    Helper类,用于网络设备配置到节点、连接信道、配置IP地址等。

    例如TopologyReaderHelper类可以配置和使用TopologyReader;InternetStackHelper安装PointToPointHelper对象和网络协议栈。

3.2 优化技术

3.2.1 Logging系统

将执行结果输出命令行中(类似cout、printf)。

LogComponentEnable ("UdpEchoClientApplication",LOG_LEVEL_INFO); //LogComponentEnable()使记录模块有效,参数1是类组件的字符串,参数2是显示的等级(例如改为下列其他值) 
  LogComponentEnable ("UdpEchoServerApplication", LOG_LEVEL_INFO);
           
【网络仿真】ns-3基础(上)1 ns-3初识2 ns-3快速上手3 ns-3基础4 ns-3仿真结果统计分析5 ns-3内核

通过环境变量修改记录系统等级

export 'NS_LOG=UdpEchoClientApplication=level_all:UdpEchoServerApplication=level_all' #export为命令,level_all等级表示显示全部的调试信息
./waf --run scratch/first
           

添加附加前缀

自定义Logging代码(输出自定义信息)

NS_LOG_COMPONENT_DEFINE("FirstScriptExample");//向ns-3系统注册一个名为"FirstScriptExample"的记录组件,之后才能在Logging系统中自定义输出语句。
NS_LOG_INFO("Creating Topology")//在first.cc中添加

           

3.2.2 命令行参数

作用:不修改脚本,用命令行传递参数来修改脚本中的变量。

CommandLine cmd;
cmd.Parse(argc,argv);//之后用户可以用命令行访问代码中的全局变量和属性系统
pointToPoint.SetDeviceAttribute ("DataRate", StringValue ("5Mbps"));
pointToPoint.SetChannelAttribute ("Delay", StringValue ("2ms"));
           
【网络仿真】ns-3基础(上)1 ns-3初识2 ns-3快速上手3 ns-3基础4 ns-3仿真结果统计分析5 ns-3内核

挂钩自定义变量

int main(int argc,char *argv[]){
	uint32_t nPackets = 1;
	CommandLine cmd;
	cmd.AddValue("nPackets","Number of packets to echo",nPackets);//使得变量nPackets可以在命令行中修改
	cmd.Parse(argc, argv);
	
	//echoClient.SetAttribute("MaxPackets",UintegerValue(1));
	echoClient.SetAttribute("MaxPackets",UintegerValue(nPackets));
}
           
# 运行
./waf --run "scratch/first --PrintHelp"
./waf --run "scratch/first --nPackets=2"
           

3.2.3 Tracing系统

  • 作用:将执行结果输出一个文件中。
  • 3个基本概念:Tracing Sources、Tracing Sink、连接的统一机制。
  • Helper类:AsciiTraceHelper生成文档
  1. ASCII Tracing:以ASCII格式的信息输出
//在Simulator::Run();前写入
AsciiTraceHelper ascii;//声明一个AsciiTraceHelper对象,为调用其成员函数做准备
pointToPoint.EnableAsciiAll (ascii.CreateFileStream ("myfirst.tr"));//CreateFileStream()创建一个名为myfirst.tr流文件。EnableAsciiAll()通知helper将所有关于pointToPoint设备的仿真信息打印为ASCII Tracing格式。
           
# 运行后在ns-3.xx中多一个文件myfirst.tr
./waf --run scratch/first
           
  1. PCAP Tracing

    生成.pacp格式文件,可用于Wireshark工具打开分析。

4 ns-3仿真结果统计分析

4.1 ns-3仿真可视化工具

4.1.1 PyViz 可视化模块

# 运行
./waf --run example/tutorial/third --vis
           

其他信息:http://www/nsnam.org/wiki/index.php/PyViz

4.1.2 NetAnim 动画演示工具

在脚本文件中写入代码,以生成XML记录文件。

//确保程序的wscript文件包含netanim模块(关于wscript文件的例子在src/netanim/examples/wscript中)
# include "ns3/netanim-module.h"
AnimationInterface anim ("first.xml");// 设置XML文件。生成文件名为animation.xml的xml格式的追踪文件
// AnimationInterface anim ("animation.xml",50000); // 确保每个动画XML文件仅包含50000个数据分组,多了分为几个XML文件

// 以下为可选语句
anim.SetMobilityPollInterval (Seconds (1)); //设置记录节点位置的周期,默认为250ms
anim.SetConstantPosition (Ptr<Node> n, double x, double y); //设置节点的位置,ConstantPosition为移动模型中节点静态位置的x、y坐标。
anim.SetStartTime (Seconds (150)); //动画接口可以只记录仿真过程中一部分。设置动画记录的开始和结束时间
anim.SetStopTime (Seconds (200));
anim.EnablePacketMetadata (true); // 设置XML文件记录包括元数据(如TCP序号、源节点目的节点的IP地址)。启用此特性会导致xml记录文件增大,在WIMAX仿真中不建议使用
//AnimationInterface的其他方法可以看ns-3/src/netanim/examples/wireless-anemation.cc目录下的文件例子
           

运行:

# 在NetAnim目录下
./NetAnim
# 在ns-3.xx目录下
./waf --run first
           

在netanim界面中点击open->first.xml->play

详细信息:http://www.nsnam.org/wiki/index.php/NetAnim

4.2 分析追踪记录文件数据

第三方网络数据分组显示和统计分析工具,读取ns-3的trace文件,并进行统计和分析。

4.2.1 TcpDump

TcpDump (dump the traffic on a network),读取pcap文件,截获分组,分析头部,输出

系统时间来源主机.端口 > 目标主机.端口数据分组参数

使用方法:

在脚本first.cc中加入代码

在命令行中使用TcpDump

# 格式
# tcpdump [-adeflnOpqStvx][-c 数量][-F 文件名][-i 网络接口][-r 文件名][-s snaplen][-T 类型][-w 文件名][表达式]
tcpdump -nn -tt -r first-0-0.pcap
# 输出
reading from file first-1-0.pcap, link-type PPP (PPP) # 链路类型为PPP
2.003686 IP 10.1.1.1.49153 > 10.1.1.2.9: UDP, length 1024 # 数据分组从节点0(IP:10.1.1.1, port:49153)发出,到达节点4(IP:10.1.2.4, port:9)
2.003686 IP 10.1.1.2.9 > 10.1.1.1.49153: UDP, length 1024 
           
【网络仿真】ns-3基础(上)1 ns-3初识2 ns-3快速上手3 ns-3基础4 ns-3仿真结果统计分析5 ns-3内核

4.2.2 Wireshark

在Wireshark界面中打开first-0-0.pace,可以看到通信过程、数据分组格式、二进制流。

工作区上部分为数据分组的简单信息,中间为详细信息(各个协议的头格式),下面为二进制显示。

详细信息:http://www.wireshark.org/

【网络仿真】ns-3基础(上)1 ns-3初识2 ns-3快速上手3 ns-3基础4 ns-3仿真结果统计分析5 ns-3内核

4.3 统计模块status

使用方法

  1. 写脚本

    例子:examples/stats/wifi-example-sim.cc

/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
/*
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation;
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * Authors: Joe Kopena <[email protected]>
 *
 * This program conducts a simple experiment: It places two nodes at a
 * parameterized distance apart. 把两个节点放在指定参数距离。 One node generates packets and the
 * other node receives.一个发一个收  The stat framework collects data on packet
 * loss. 数据统计模块收集丢包统计数据。 Outside of this program, a control script uses that data to
 * produce graphs presenting performance at the varying distances.程序之外的一个控制脚本使用数据画图,以表示距离变化。
 * This isn't a typical simulation but is a common "experiment"
 * performed in real life and serves as an accessible exemplar for the
 * stat framework.  It also gives some intuition on the behavior and
 * basic reasonability of the NS-3 WiFi models.
 *
 * Applications used by this program are in test02-apps.h and
 * test02-apps.cc, which should be in the same place as this file.
 * 
 */

#include <ctime>
#include <sstream>
#include "ns3/core-module.h"
#include "ns3/network-module.h"
#include "ns3/mobility-module.h"
#include "ns3/internet-module.h"
#include "ns3/stats-module.h"
#include "ns3/yans-wifi-helper.h"
#include "wifi-example-apps.h"

using namespace ns3;
using namespace std;

// 定义日志组件
NS_LOG_COMPONENT_DEFINE ("WiFiDistanceExperiment");

void TxCallback (Ptr<CounterCalculator<uint32_t> > datac,
                 std::string path, Ptr<const Packet> packet) {
  NS_LOG_INFO ("Sent frame counted in " <<
               datac->GetKey ());
  datac->Update ();
  // end TxCallback
}




//----------------------------------------------------------------------
//-- main
//----------------------------------------------
int main (int argc, char *argv[]) {

  double distance = 50.0;
  string format ("omnet");

  string experiment ("wifi-distance-test");
  string strategy ("wifi-default");
  string input;
  string runID;

  {
    stringstream sstr;
    sstr << "run-" << time (NULL);
    runID = sstr.str ();
  }

  // 1. Set up command line parameters used to control the experiment.声明参数和使用ns3::CommandLine解析命令
  CommandLine cmd (__FILE__);
  cmd.AddValue ("distance", "Distance apart to place nodes (in meters).",
                distance);
  cmd.AddValue ("format", "Format to use for data output.",
                format);
  cmd.AddValue ("experiment", "Identifier for experiment.",
                experiment);
  cmd.AddValue ("strategy", "Identifier for strategy.",
                strategy);
  cmd.AddValue ("run", "Identifier for run.",
                runID);
  cmd.Parse (argc, argv);

  if (format != "omnet" && format != "db") {
      NS_LOG_ERROR ("Unknown output format '" << format << "'");
      return -1;
    }

  #ifndef STATS_HAS_SQLITE3
  if (format == "db") {
      NS_LOG_ERROR ("sqlite support not compiled in.");
      return -1;
    }
  #endif

  {
    stringstream sstr ("");
    sstr << distance;
    input = sstr.str ();
  }




  //------------------------------------------------------------
  //-- 2. Create nodes and network stacks创建节点和网络堆栈
  //--------------------------------------------
  NS_LOG_INFO ("Creating nodes.");
  NodeContainer nodes;//NodeContainer
  nodes.Create (2);

  NS_LOG_INFO ("Installing WiFi and Internet stack.");
  WifiHelper wifi;//WifiHelper
  WifiMacHelper wifiMac;
  wifiMac.SetType ("ns3::AdhocWifiMac");
  YansWifiPhyHelper wifiPhy;
  YansWifiChannelHelper wifiChannel = YansWifiChannelHelper::Default ();
  wifiPhy.SetChannel (wifiChannel.Create ());
  NetDeviceContainer nodeDevices = wifi.Install (wifiPhy, wifiMac, nodes);

  InternetStackHelper internet; // InternetStackHelper
  internet.Install (nodes);
  Ipv4AddressHelper ipAddrs;
  ipAddrs.SetBase ("192.168.0.0", "255.255.255.0");
  ipAddrs.Assign (nodeDevices);




  //------------------------------------------------------------
  //-- 3. Setup physical layout
  //--------------------------------------------
  NS_LOG_INFO ("Installing static mobility; distance " << distance << " .");
  MobilityHelper mobility;//使用MobilityHelper定位节点。默认节点有静态流动性且不能移动
  Ptr<ListPositionAllocator> positionAlloc =
    CreateObject<ListPositionAllocator>();
  positionAlloc->Add (Vector (0.0, 0.0, 0.0));
  positionAlloc->Add (Vector (0.0, distance, 0.0));
  mobility.SetPositionAllocator (positionAlloc);
  mobility.Install (nodes);




  //------------------------------------------------------------
  //-- 4. Create a custom traffic source and sink安装流量发生器(发送端)和接收器。
  //--------------------------------------------
  NS_LOG_INFO ("Create traffic source & sink.");
  Ptr<Node> appSource = NodeList::GetNode (0);
  Ptr<Sender> sender = CreateObject<Sender>();
  appSource->AddApplication (sender);
  sender->SetStartTime (Seconds (1));

  Ptr<Node> appSink = NodeList::GetNode (1);
  Ptr<Receiver> receiver = CreateObject<Receiver>();
  appSink->AddApplication (receiver);
  receiver->SetStartTime (Seconds (0));
  // 更改数据分组的目的地,默认情况为广播。
  Config::Set ("/NodeList/*/ApplicationList/*/$Sender/Destination",
               Ipv4AddressValue ("192.168.0.2"));




  //------------------------------------------------------------
  //-- Setup stats and data collection配置要收集的统计数据
  //--------------------------------------------

  // Create a DataCollector object to hold information about this run.
  DataCollector data;
  data.DescribeRun (experiment,//“实验”信息标签,标识研究对象,Wi-Fi性能和距离
                    strategy,//策略:实验中被检测的参数,e.g. Wi-Fi比特率
                    input,//输入,2个节点之间的距离
                    runID);//运行ID,实验的唯一标识符。运行信息

  // Add any information we wish to record about this run.
  data.AddMetadata ("author", "tjkopena");


  // Create a counter to track how many frames are generated.  Updates
  // are triggered by the trace signal generated by the WiFi MAC model
  // object.  Here we connect the counter to the signal via the simple
  // TxCallback() glue function defined above.
  Ptr<CounterCalculator<uint32_t> > totalTx =
    CreateObject<CounterCalculator<uint32_t> >();
  totalTx->SetKey ("wifi-tx-frames");
  totalTx->SetContext ("node[0]");
  Config::Connect ("/NodeList/0/DeviceList/*/$ns3::WifiNetDevice/Mac/MacTx",
                   MakeBoundCallback (&TxCallback, totalTx));
  data.AddDataCalculator (totalTx);

  // This is similar, but creates a counter to track how many frames
  // are received.  Instead of our own glue function, this uses a
  // method of an adapter class to connect a counter directly to the
  // trace signal generated by the WiFi MAC.
  Ptr<PacketCounterCalculator> totalRx =
    CreateObject<PacketCounterCalculator>();
  totalRx->SetKey ("wifi-rx-frames");
  totalRx->SetContext ("node[1]");
  Config::Connect ("/NodeList/1/DeviceList/*/$ns3::WifiNetDevice/Mac/MacRx",
                   MakeCallback (&PacketCounterCalculator::PacketUpdate,
                                 totalRx));
  data.AddDataCalculator (totalRx);




  // This counter tracks how many packets---as opposed to frames---are
  // generated.  This is connected directly to a trace signal provided
  // by our Sender class.
  Ptr<PacketCounterCalculator> appTx =
    CreateObject<PacketCounterCalculator>();
  appTx->SetKey ("sender-tx-packets");
  appTx->SetContext ("node[0]");
  Config::Connect ("/NodeList/0/ApplicationList/*/$Sender/Tx",
                   MakeCallback (&PacketCounterCalculator::PacketUpdate,
                                 appTx));
  data.AddDataCalculator (appTx);

  // Here a counter for received packets is directly manipulated by
  // one of the custom objects in our simulation, the Receiver
  // Application.  The Receiver object is given a pointer to the
  // counter and calls its Update() method whenever a packet arrives.
  Ptr<CounterCalculator<> > appRx =
    CreateObject<CounterCalculator<> >();
  appRx->SetKey ("receiver-rx-packets");
  appRx->SetContext ("node[1]");
  receiver->SetCounter (appRx);
  data.AddDataCalculator (appRx);




  /**
   * Just to show this is here...
   Ptr<MinMaxAvgTotalCalculator<uint32_t> > test = 
   CreateObject<MinMaxAvgTotalCalculator<uint32_t> >();
   test->SetKey("test-dc");
   data.AddDataCalculator(test);

   test->Update(4);
   test->Update(8);
   test->Update(24);
   test->Update(12);
  **/

  // This DataCalculator connects directly to the transmit trace
  // provided by our Sender Application.  It records some basic
  // statistics about the sizes of the packets received (min, max,
  // avg, total # bytes), although in this scenaro they're fixed.
  Ptr<PacketSizeMinMaxAvgTotalCalculator> appTxPkts =
    CreateObject<PacketSizeMinMaxAvgTotalCalculator>();
  appTxPkts->SetKey ("tx-pkt-size");
  appTxPkts->SetContext ("node[0]");
  Config::Connect ("/NodeList/0/ApplicationList/*/$Sender/Tx",
                   MakeCallback
                     (&PacketSizeMinMaxAvgTotalCalculator::PacketUpdate,
                     appTxPkts));
  data.AddDataCalculator (appTxPkts);


  // Here we directly manipulate another DataCollector tracking min,
  // max, total, and average propagation delays.  Check out the Sender
  // and Receiver classes to see how packets are tagged with
  // timestamps to do this.
  Ptr<TimeMinMaxAvgTotalCalculator> delayStat =
    CreateObject<TimeMinMaxAvgTotalCalculator>();
  delayStat->SetKey ("delay");
  delayStat->SetContext (".");
  receiver->SetDelayTracker (delayStat);
  data.AddDataCalculator (delayStat);




  //------------------------------------------------------------
  //-- Run the simulation
  //--------------------------------------------
  NS_LOG_INFO ("Run Simulation.");
  Simulator::Run ();




  //------------------------------------------------------------
  //-- Generate statistics output.
  //--------------------------------------------

  // Pick an output writer based in the requested format.
  Ptr<DataOutputInterface> output = 0;
  if (format == "omnet") {
      NS_LOG_INFO ("Creating omnet formatted data output.");
      output = CreateObject<OmnetDataOutput>();
    } else if (format == "db") {
    #ifdef STATS_HAS_SQLITE3
      NS_LOG_INFO ("Creating sqlite formatted data output.");
      output = CreateObject<SqliteDataOutput>();
    #endif
    } else {
      NS_LOG_ERROR ("Unknown output format " << format);
    }

  // Finally, have that writer interrogate the DataCollector and save
  // the results.
  if (output != 0)
    output->Output (data);

  // Free any memory here at the end of this example.
  Simulator::Destroy ();

  // end main
}

           

4.4 绘图工具Gnuplot

第三方作图工具,有gnuplot类,可产生gnuplot数据,最后由gnuplot读取数据生成图表,从而进行仿真数据统计分析。

# 安装
apt-get install Gnuplot
# 进入Gnuplot交互界面
gnuplot>
# 得到正弦曲线
plot[-3.14:3.14]sin(x)
           

详细信息:http://www.gnuplot.info/

ns-3提供了Gnuplot类和GnuplotDataset类

ns-3中使用方法:(例子ns-3.xx/src/tools/examples/gnuplot-example.cc)

# 运行例子
./waf --run src/tools/examples/gnuplot-example
# 结果:生成Gnuplot控制文件 plot-2d.plt plot-2d-with-error-bars.plt plot-3d.plt
# 使用gnuplot处理gnuplot控制文件
gnuplot plot-2d.plt
gnuplot plot-2d-with-error-bars.plt
gnuplot plot-3d.plt
# 处理后生成图片文件:plot-2d.png plot-2d-with-error-bars.png plot-3d.png
# 图片可以用浏览器等工具打开(e.g. gimp, Image Viewer, Shotwell)
           

5 ns-3内核

5.1 ns-3的组织结构

  • 组织结构在src目录中实现。下层为上层服务。
  • 内核模块,在src/core中实现。包括了ns-3基本机制,例如随机变量、回调、属性系统、Tracing系统等。
  • 网络模块,分组在src/network中实现,讨论网络数据分组的相关问题。
  • Internet模块:介绍Internet网络基本协议,包括路由、传输层。
  • 移动模块:提供一些移动模型。
  • 应用层模块:提供一些应用程序。
  • 能量模块:关于节点能量消耗。
    【网络仿真】ns-3基础(上)1 ns-3初识2 ns-3快速上手3 ns-3基础4 ns-3仿真结果统计分析5 ns-3内核

5.2 随机变量Random Variables

  • 随机数生成器(RNG)

    随机变量通过调用类ns3::RandomVariableStream提供。这个类封装了随机数生成器并提供接口。子类:UniformVariableStream和ExponetialVariableStram。

  • 仿真程序中,不改变种子或运行标识的前提下,仿真程序产生的结果的一定的。想要结果不一样,ns3::RngSeedManager::SetSeed()设置种子,或ns3::RngSeedManager::SetRun()设置运行标识。
  • ns-3所有的随机变量使用一个基于全局固定的或随机的种子。
  • 固定的种子和标识存储在类GlobalValue的g_rngSeed和g_rngRun成员中。
  • 同一仿真程序多次实验,若要实现每次实验都具有独立性,有两种方法:

方法一:每次独立重复实验时,调用函数RngSeedManager::SetSeed()设置不同的全局种子。

//在scratch文件夹下,创建文件sample-random-variable-stream.cc
# include "ns3/simulator.h"
# include "ns3/nstime.h"
# include "ns3/command-line.h"
# include "ns3/rng-seed-manager.h"
# include "ns3/random-variable-stream.h"
# include <iostream>
using namespace ns3;
int main(int argc,char *argv[])
{
	CommandLine cmd;
	cmd.Parse(argc, argv);
	RngSeedManager::SetSeed(1);//改为SetSeed(2)结果也会变化
	Ptr<UniformRandomVariable> uv=CreateObject<UniformRandomVariable>();//创建一个指向随机变量类的指针uv
	std::cout << uv->GetValue() << std::endl;
	return 0;
}
           
# 运行
./waf --run scratch/sample-random-variable-stream
# 不改代码的话,每次运行输出同样的数字[0,1),例如0.816532
           

方法二:每次独立重复实验时,全局种子不变,每次设置不同的标识。改变标识有几种方式:

a. 调用函数RngSeedManager::SetRun(3)设置不同的运行标识。

// 修改文件代码
RngSeedManager::SetRun(3);//这一句代替RngSeedManager::SetSeed(1);
           

b. 修改全局变量NS_GLOBAL_VALUE值来修改运行标识。

# 运行
# ./waf --run scratch/sample-random-variable-stream
NS_GLOBAL_VALUE="RngRun=3" ./waf --run scratch/sample-random-variable-stream # RngRun=可以取不同的值
           

c(推荐). 使用命令行传递参数修改运行标识

# 运行
./waf --run "scratch/sample-random-variable-stream --RngRun=3"
           

d. 使用build

# 运行
./build/optimized/scratch/sample-random-variable-stream --RngRun=3
           

随机变量

ns-3中用来声明随机变量的类,都有一个基类RandomVariableStream。其基类有:

  1. UniformRandomVariable: 最基本的类。给定一个最大值和最小值,按均匀分布方式返回一个随机数。
# include "ns3/double.h"
# include "ns3/simulator.h"
# include "ns3/nstime.h"
# include "ns3/command-line.h"
# include "ns3/rng-seed-manager.h"
# include "ns3/random-variable-stream.h"
# include <iostream>
using namespace ns3;
int main(int argc,char *argv[])
{
	CommandLine cmd;
	cmd.Parse(argc,argv);
	RngSeedManager::SetSeed(3);
	double min = 0.0;
	double max = 10.0;
	Ptr<UniformRandomVariable> uv = CreateObject<UniformRandomVariable> ();
	//改变返回随机变量的区间为[0,10),默认为[0,1)
	uv->SetAttribute("Min",DoubleValue(min));
	uv->SetAttribute("Max",DoubleValue(max));
	std::cout << uv->GetValue() << std::endl;
	return 0;
}
           
  1. ConstantRandomVariable: 返回一个固定的数,默认为0
...	
	// Ptr<UniformRandomVariable> uv = CreateObject<UniformRandomVariable> ();
	//改变返回随机变量为10,默认为0
	double dValue = 10.0;
	Ptr<UniformRandomVariable> uv = CreateObject<ConstantRandomVariable> ();
	uv->SetAttribute("Constant",DoubleValue(dValue))
	...
}
           
  1. SequentialRandomVariable: 返回一串等差数列,超过最大上限重新从开始再次循环。
    【网络仿真】ns-3基础(上)1 ns-3初识2 ns-3快速上手3 ns-3基础4 ns-3仿真结果统计分析5 ns-3内核
  2. ExponentialRandomVariable: 根据指数概率分布返回随机数。
    【网络仿真】ns-3基础(上)1 ns-3初识2 ns-3快速上手3 ns-3基础4 ns-3仿真结果统计分析5 ns-3内核
  3. NormalRandomVariable: 根据正态分布返回随机数
    【网络仿真】ns-3基础(上)1 ns-3初识2 ns-3快速上手3 ns-3基础4 ns-3仿真结果统计分析5 ns-3内核

5.3 回调Callbacks

  1. 为什么用回调机制?why

    实现让A模块调用B模块的函数,并且两个模块没有任何依赖。

先看C语言中的回调机制:

//想调用另一个模块的函数,先把被调用函数的地址作为一个变量,这个变量为函数指针变量。
int (*pfi)(int arg) = 0;//声明一个带整型参数(int arg)的函数指针pfi,初始值为0,前面int表示指向函数的返回值为整型,函数参数和指针参数也一致。
int MyFunction(int arg){}//pfi对应函数可以声明为这样
pfi = MyFuction;//用函数初始化函数指针
int result = (*pfi)(1234);//通过函数指针调用函数
           

在C++中还要为成员函数建立成员函数指针、区分成员函数指针和函数指针:

int (MyClass::*pmi)(int arg) = 0;//声明类的成员函数指针,MyClass类名
class MyClass
{
	public:
	int MyMethod (int arg);//函数声明
};
pmi = &MyClass::MyMethod;//pmi类函数指针变量的赋值和调用
MyClass myclass;
myclass.*pmi(1234);
           
  1. 什么是回调机制?what

    例如调用快排函数,函数参数中有回调函数地址,可以传递自己的比较函数。这种被调用者(快排函数)调用调用者的排序函数(cmp),称为回调。

  2. 怎么用回调机制?how

    ns-3提供了Callback类的API接口:

  • 静态函数
// 对于静态函数,声明和实例化Callback类型
static double CbOne (double a, double b)
{
	std::cout<<"invoke cbOne a="<<a<<",b="<<b<<std::endl;
	return a;
}
int main(int argc,char *argv[])
{
	Callback<double, double, double> one;//实例化回调类。<返回类型,第一个参数,第二个参数>
	one = MakeCallback(&CbOne);//将回调one与签名匹配的函数进行绑定
	
	//使用回调
	NS_ASSERT(!one.IsNull());//检查回调one是否为空
	Double retOne;
	retOne = one(10.0, 20.0);
}
           
  • 类成员函数回调
class MyCb{
	public: 
	int CbTwo (double a){
		std::cout<<"invoke cbTwo a="<<a<<std::endl;
		return -5;
	}
}
int main(int argc,char *argv[])
{
	Callback<int, double> two;
	MyCb cb;
	two = MakeCallback (&MyCb::CbTwo,&cb);//创建一个回调变量并指向MyCb::CbTwo
	//若构建空回调:two = MakeCallback<int,double>();int retTwoNull = two(20.0);
	
	//使用回调
	NS_ASSERT(!two.IsNull());
	int retTwo;
	retTwo = two(10.0);
	NS_ASSERT(retTwo == -5);
	two = MakeNullCallback<int, double>();//传递一个额外指针给函数MakeNullCallback<>(),当函数two()被调用时,调用的是&cb指向的对象函数CbTwo。
	NS_ASSERT(two.IsNull());
	return 0;
}
           

5.4 对象模型

  1. 面向对象设计
  2. 对象基类

    ns-3提供了3个对象基类:Object、ObjectBase、SimpleRefCount。若每个类继承这种类,就可以包含ns-3提供的特有特性:

基类 属性
Object 属性系统、对象聚合系统、智能指针和引用计数系统
ObjectBase 属性系统、对象聚合系统
SimpleRefCount 智能指针和引用计数系统
  1. 内存管理与引用计数指针(Ptr类)

    ns-3使用引用计数来管理内存 。

//在ns-3中使用CreateObject()来创建对象,而不是使用C++的new操作符。这是对new操作的封装,自动处理智能指针的引用次数。
Ptr<WifiNetDevice> device = CreateObject<WifiNetDevice>();
           
  1. 聚合
  • why?

    例如ns-3中,Node类在任何网络终端中都使用,但不同网络终端的构建和协议不同,为了创建满足不同网络节点需求,就要使用聚合,将不同构件聚合到节点中。

  • what?
  • how?

    例如,将IPv4协议加入节点中:

static void
AddIpv4Stack(Ptr<Node> node)
{
	Ptr<IPv4L3Protocol> ipv4 = CreateObject<IPv4L3Protocol> ();//创建一个IPv4协议的指针对象ipv4
	ipv4->SetNode (node);//把IPv4协议聚合到节点中。这样Node就不需要被单独编辑。
	node->AggregateObject (ipv4);
	Ptr<IPv4Impl> ipv4Impl = CreateObject<IPv4Impl> ();
	ipv4Impl->SetIPv4 (ipv4);
	node->AggregateObject (ipv4Impl);
}
           

5.5 属性系统

  • 对象模型:设置实例化模型的参数。
  • Object类:多数ns-3类都是继承Object类。所有继承Object的类都可以包含一个叫TypeId的元数据类,用于记录类的元信息(包括唯一标识字符串、子类的基类、子类的构造函数)。
  • 例子:Node类,静态成员函数GetTypeId
// 头文件node.h中
Class Node : public Object
{
	public:Static TypeId GetTypeId(void):...
}
//node.cc文件中
TypeId
Node::GetTypeId (void)
{
	static TypeId tid = TypeId ("ns3::Node")
		.SetParent<Object> ()//声明此类的基类,方便在用GetObject()时进行向上或向下类型转化
		.AddConstryctor<Node> ()//构建对象
		.AddAttribute ("DeviceList","The list of devices associated to this Node.",ObjectVectorValue (), MakeObjectVectorAccessor (&Node::m_devices), MakeObjectVectorChecker<NetDevice> ())//把字符串与类的成员变量关联。参数1:要绑定的字符串,参数2:解释说明,参数3:成员变量必须转化的类型,参数4:将成员变量强制转化为参数3的类型,参数5:对成员变量是否合法的检查
		.AddAttribute ("ApplicationList","The list of application associated to this Node.",ObjectVectorValue (), MakeObjectVectorAccessor (&Node::m_applications), MakeObjectVectorChecker<Application> ())
		.AddAttribute ("Id","The id(unique integer)of this Node.",TypeId::ATTR_GET, UintegerValue (0), MakeUintegerAccessor (&Node::m_id), MakeUintegerChecker<uint32_t> ());
	return tid;
} 

//使用CreateObject(),创建节点:
Ptr<Node> n = CreateObject<Node>();
//使用对象工程机制,创建节点:
ObjectFactory factory;
const st::string typeId = "ns3::Node";
factory.SetTypeId (typeId);
Ptr(Object) node = factory.Create<Object> ();
           
  • 属性系统:管理和组织仿真中内部对象(方便用户访问模拟中内部变量,e.g. cwnd)
  • 例如:DropTailQueue类,无符号整型成员变量m_maxPackets(用来控制队列的大小)
// drop-tail-queue.h
class DropTailQueue : public Queue 
{
	public:
	static typeId GetTypeId (void);
	...
	private:
	std::queue<Ptr<Packet>> m_packets;
	uint32_t m_maxPackets;
};
// drop-tail-queue.cc
// 用TypeId类实现创建类时,成员变量被初始化为默认值
NS_OBJECT_ENSURE_REGISTERED (DropTailQueue);//自定义类必须有,使属性系统涉及的属性可以正常初始化。
TypeId DropTailQueue::GetTypeId (void)
{
	static TypeId tid = TypeId ("ns3::DropTailQueue")
		.SetParent<Queue> ()
		.AddConstructor<DropTailQueue> ()
		.AddAttribute ("MaxPackets","The maximum number of packets accepted by this DropTailQueue.", UintegerValue (100), MakeUintegerAccessor (&DropTailQueue::m_maxPackets), MakeUintegerChecker<uint32_t> ());//使用AddAttribute()方法处理变量m_maxPackets:将m_maxPackets绑定到字符串“MaxPackets”中,默认值为100,Checker可用来设置允许的范围
	return tid;
}
           

在脚本中操纵属性系统中的数据(src/point-to-point/examples/main-attribute-value.cc)

/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
/*
 * Copyright (c) 2008 University of Washington
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation;
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * Author: Tom Henderson <[email protected]>
 */

#include "ns3/log.h"
#include "ns3/command-line.h"
#include "ns3/ptr.h"
#include "ns3/config.h"
#include "ns3/uinteger.h"
#include "ns3/string.h"
#include "ns3/pointer.h"
#include "ns3/simulator.h"

#include "ns3/node.h"
#include "ns3/queue.h"
#include "ns3/drop-tail-queue.h"
#include "ns3/point-to-point-net-device.h"

using namespace ns3;

NS_LOG_COMPONENT_DEFINE ("AttributeValueSample");

//
// This is a basic example of how to use the attribute system to
// set and get a value in the underlying system; namely, the maximum
// size of the FIFO queue in the PointToPointNetDevice
//

int 
main (int argc, char *argv[])
{
  LogComponentEnable ("AttributeValueSample", LOG_LEVEL_INFO);

  // Queues in ns-3 are objects that hold items (other objects) in 
  // a queue structure.  The C++ implementation uses templates to
  // allow queues to hold various types of items, but the most
  // common is a pointer to a packet (Ptr<Packet>).
  //
  // The maximum queue size can either be enforced in bytes ('b') or
  // packets ('p').  A special type called the ns3::QueueSize can
  // hold queue size values in either unit (bytes or packets).  The
  // queue base class ns3::QueueBase has a MaxSize attribute that can
  // be set to a QueueSize.

  // By default, the MaxSize attribute has a value of 100 packets ('100p')
  // (this default can be observed in the function QueueBase::GetTypeId)
  // 
  // Here, we set it to 80 packets.  We could use one of two value types:
  // a string-based value or a QueueSizeValue value
  Config::SetDefault ("ns3::QueueBase::MaxSize", StringValue ("80p"));
  // The below function call is redundant
  Config::SetDefault ("ns3::QueueBase::MaxSize", QueueSizeValue (QueueSize (QueueSizeUnit::PACKETS, 80)));

  // Allow the user to override any of the defaults and the above
  // SetDefaults() at run-time, via command-line arguments
  // For example, via "--ns3::QueueBase::MaxSize=80p"
  CommandLine cmd;
  // This provides yet another way to set the value from the command line:
  cmd.AddValue ("maxSize", "ns3::QueueBase::MaxSize");
  cmd.Parse (argc, argv);

  // Now, we will create a few objects using the low-level API
  Ptr<Node> n0 = CreateObject<Node> ();

  Ptr<PointToPointNetDevice> net0 = CreateObject<PointToPointNetDevice> ();
  n0->AddDevice (net0);

  Ptr<Queue<Packet> > q = CreateObject<DropTailQueue<Packet> > ();
  net0->SetQueue (q);

  // At this point, we have created a single node (Node 0) and a 
  // single PointToPointNetDevice (NetDevice 0) and added a 
  // DropTailQueue to it.

  // Now, we can manipulate the MaxSize value of the already 
  // instantiated DropTailQueue.  Here are various ways to do that.

  // We assume that a smart pointer (Ptr) to a relevant network device
  // is in hand; here, it is the net0 pointer. 

  // 1.  Pointer-based access 通过指针访问属性值
  //
  // One way to change the value is to access a pointer to the
  // underlying queue and modify its attribute.
  // 
  // First, we observe that we can get a pointer to the (base class)
  // queue via the PointToPointNetDevice attributes, where it is called
  // TxQueue 
  PointerValue ptr;// 创建一个指针变量
  net0->GetAttribute ("TxQueue", ptr);// 为变量赋值
  Ptr<Queue<Packet> > txQueue = ptr.Get<Queue<Packet> > ();// 获取队列

  // Using the GetObject function, we can perform a safe downcast。通过GetObject函数安全地把txQueue向下类型转化(Queue->DropTailQueue)
  // to a DropTailQueue
  Ptr<DropTailQueue<Packet> > dtq = txQueue->GetObject <DropTailQueue<Packet> > ();
  NS_ASSERT (dtq);

  // Next, we can get the value of an attribute on this queue
  // We have introduced wrapper "Value" classes for the underlying
  // data types, similar to Java wrappers around these types, since
  // the attribute system stores values and not disparate types.
  // Here, the attribute value is assigned to a QueueSizeValue, and
  // the Get() method on this value produces the (unwrapped) QueueSize.
  // 通过输出数据验证程序是否将默认值100改成了80:
  QueueSizeValue limit;
  dtq->GetAttribute ("MaxSize", limit);
  NS_LOG_INFO ("1.  dtq limit: " << limit.Get ());

  // Note that the above downcast is not really needed; we could have
  // done the same using the Ptr<Queue> even though the attribute
  // is a member of the subclass
  // 实际上,不向下转化,也可以得到MaxSize值:80
  txQueue->GetAttribute ("MaxSize", limit);
  NS_LOG_INFO ("2.  txQueue limit: " << limit.Get ());

  // Now, let's set it to another value (60 packets).  Let's also make
  // use of the StringValue shorthand notation to set the size by
  // passing in a string (the string must be a positive integer suffixed
  // by either the 'p' or 'b' character).
  // 在创建对象后再改变MaxSize值:
  txQueue->SetAttribute ("MaxSize", StringValue ("60p"));
  txQueue->GetAttribute ("MaxSize", limit);
  NS_LOG_INFO ("3.  txQueue limit changed: " << limit.Get ());

  // 2.  Namespace-based access
  // 访问属性值的第二种方法:通过命名空间方式
  // An alternative way to get at the attribute is to use the configuration
  // namespace.  Here, this attribute resides on a known path in this
  // namespace; this approach is useful if one doesn't have access to
  // the underlying pointers and would like to configure a specific
  // attribute with a single statement.
  // 修改了第一个节点的第一个网络设备属性值
  Config::Set ("/NodeList/0/DeviceList/0/TxQueue/MaxSize", StringValue ("25p"));
  txQueue->GetAttribute ("MaxSize", limit); 
  NS_LOG_INFO ("4.  txQueue limit changed through namespace: " << 
               limit.Get ());

  // we could have also used wildcards to set this value for all nodes
  // and all net devices (which in this simple example has the same
  // effect as the previous Set())
  // 修改了所有节点的所有网络设备属性值
  Config::Set ("/NodeList/*/DeviceList/*/TxQueue/MaxSize", StringValue ("15p"));
  txQueue->GetAttribute ("MaxSize", limit); 
  NS_LOG_INFO ("5.  txQueue limit changed through wildcarded namespace: " << 
               limit.Get ());

  Simulator::Destroy ();
}

           
# 运行
./waf --run scratch/main-attribute-value
           

写自己的网络模块:

  1. 添加现有类的成员变量到元数据系统中

    例如TcpSocket类的成员变量:uint32_tm_cWnd。使用TCP模块时想用元数据获得、设置变量的值,而ns-3没有提供这个变量,用户可以在元数据系统中添加声明:

./AddAttribute ("Congestion window","Tcp congestion window (bytes)",
				UintergerValue(1),
				MakeUintergerAccessor(&TcpSocket::m_cWnd),				
				MakeUintergerChecker<uint16_t>())
           

从而可以用指向TcpSocket类的指针执行设置和获取操作。

  1. 向属性系统中添加自定义类

    例如:把自定义的类A加入到系统属性中

//在a.h中声明类A
class A:public Object{
	public:
		...
		static TypeId GetTypeId(void);
		...
	private:
		int16_t m_int16;
};
NS_OBJECT_ENSURE_REGISTERED(A);

//在a.cc中定义类函数GetTypeId
static TypeId GetTypeId(void){
	static TypeId tid = TypeId("ns3::A")
		.SetParent<Object>()
		.addAttribute("TestInt16","help text",IntgerValue(-2),MakeIntegerAccessor(&A::m_int16),MakeInterChecker<int16_t>());
	return tid;		
}
           

5.6 Tracing系统

作用:追踪某仿真数据。

Tracing系统由三部分构成:Tracing Sources, Tracing Sinks, 连接前两者的方法。

  1. Tracing Sources提供信息(生产者)。
  2. Tracing Sinks使用信息做相关事务(消费者)。
  3. 二者关联。使用到回调。

(1)使用函数TraceConnectWithoutContext将二者关联。

例子:(examples/tutorial/fourth.cc)

/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
/*
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation;
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */
# include "ns3/object.h" //定义自己的类,父类为Object
# include "ns3/uinteger.h"//要用到自定义的无符号整型
# include "ns3/traced-value.h"//此头文件中引入了要跟踪的数据的类型,即TracedValue
# include "ns3/traced-source-accessor.h"//使用把自定义数据转换为traced-source的函数
# include <iostream>

using namespace ns3;

class MyObject : public Object
{//追踪系统与属性系统关联紧密,因此追踪的数据必须属于一个类
public:
  /**
   * Register this type.
   * \return The TypeId.
   */
  static TypeId GetTypeId (void)
  {
    static TypeId tid = TypeId ("MyObject")
      .SetParent<Object> ()
      .SetGroupName ("Tutorial")
      .AddConstructor<MyObject> ()
      .AddTraceSource ("MyInteger",
                       "An integer value to trace.",
                       MakeTraceSourceAccessor (&MyObject::m_myInt),
                       "ns3::TracedValueCallback::Int32")//AddTraceSorce()使得m_myInt成为一个Trace Source
    ;
    return tid;
  }

  MyObject () {}
  TracedValue<int32_t> m_myInt;
};

// 此函数为定义Trace Sink
void
IntTrace (int32_t oldValue, int32_t newValue)
{
  std::cout << "Traced " << oldValue << " to " << newValue << std::endl;
}

int
main (int argc, char *argv[])
{//定义一个对象实例,实例中包含一个TraceSource,m_myInt
  Ptr<MyObject> myObject = CreateObject<MyObject> ();
  myObject->TraceConnectWithoutContext ("MyInteger", MakeCallback (&IntTrace));//TraceConnectWithoutContext()将Trace Source和Trace Sink关联。当Trace Source数据m_myInt变化时,IntTrace函数会被调用。

  myObject->m_myInt = 1234;//m_myInt变化了,系统将m_myInt赋值前和赋值后的两个值作为形参传递给Trace Sink的回调函数IntTrace
}
           
# 运行
./waf --run example/tutorial/fourth
# outpuy
Traced 0 to 1234
           

(2)使用“配置路径”将Trace Sources和Trace Sink关联起来。

例子:third.cc改写,通过定义一个Trace Sink输出移动节点的位置变化信息。

#include "ns3/core-module.h"
#include "ns3/point-to-point-module.h"
#include "ns3/network-module.h"
#include "ns3/applications-module.h"
#include "ns3/mobility-module.h"
#include "ns3/csma-module.h"
#include "ns3/internet-module.h"
#include "ns3/yans-wifi-helper.h"
#include "ns3/ssid.h"

// 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
//2.命名空间
using namespace ns3;
using namespace std;// 加一行代码
//3.定义一个LOG模块
NS_LOG_COMPONENT_DEFINE ("ThirdScriptExample");

// 加一个函数,定义Trace Sink。
void CourseChange (string context, Ptr<const MobilityModel> model)
{
	Vector position = model->GetPosition();// 获得模型的位置
	NS_LOG_UNCOND(context << " x = " << position.x << "y = " << position.y);// 输出模型的x,y坐标
}

//4.主函数
int 
main (int argc, char *argv[])
{
  bool verbose = true;
  uint32_t nCsma = 3;
  uint32_t nWifi = 3;
  bool tracing = false;

  CommandLine cmd;
  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 (argc,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 > 18)
    {
      std::cout << "nWifi should be 18 or less; otherwise grid layout exceeds the bounding box" << std::endl;
      return 1;
    }

  if (verbose)
    {//打印指定LOG组件信息
      LogComponentEnable ("UdpEchoClientApplication", LOG_LEVEL_INFO);
      LogComponentEnable ("UdpEchoServerApplication", LOG_LEVEL_INFO);
    }
//5.创建网络拓扑
  NodeContainer p2pNodes;
  p2pNodes.Create (2);//创建两个p2p节点

  PointToPointHelper pointToPoint;
  pointToPoint.SetDeviceAttribute ("DataRate", StringValue ("5Mbps"));
  pointToPoint.SetChannelAttribute ("Delay", StringValue ("2ms"));

  NetDeviceContainer p2pDevices;
  p2pDevices = pointToPoint.Install (p2pNodes);

  NodeContainer csmaNodes;
  csmaNodes.Add (p2pNodes.Get (1));
  csmaNodes.Create (nCsma);

  CsmaHelper csma;
  csma.SetChannelAttribute ("DataRate", StringValue ("100Mbps"));
  csma.SetChannelAttribute ("Delay", TimeValue (NanoSeconds (6560)));

  NetDeviceContainer csmaDevices;
  csmaDevices = csma.Install (csmaNodes);

  NodeContainer wifiStaNodes;
  wifiStaNodes.Create (nWifi);
  NodeContainer wifiApNode = p2pNodes.Get (0);

  YansWifiChannelHelper channel = YansWifiChannelHelper::Default ();//默认传播延迟模型,默认损耗模型
  YansWifiPhyHelper phy = YansWifiPhyHelper::Default ();//默认误码率模型
  phy.SetChannel (channel.Create ());

  WifiHelper wifi;
  wifi.SetRemoteStationManager ("ns3::AarfWifiManager");// wifiRemoteStationManager主要用于wifi的速率控制(rate control)

  WifiMacHelper mac;
  Ssid = Ssid ("ns-3-ssid");
  mac.SetType ("ns3::StaWifiMac",//移动节点
               "Ssid", SsidValue (ssid),
               "ActiveProbing", BooleanValue (false));

  NetDeviceContainer staDevices;//安装移动节点
  staDevices = wifi.Install (phy, mac, wifiStaNodes);

  mac.SetType ("ns3::ApWifiMac",//AP节点
               "Ssid", SsidValue (ssid));

  NetDeviceContainer apDevices;//为AP节点安装应用
  apDevices = wifi.Install (phy, mac, wifiApNode);

  MobilityHelper mobility;//移动模型助手类

  mobility.SetPositionAllocator ("ns3::GridPositionAllocator",
                                 "MinX", DoubleValue (0.0),//起点坐标(0.0,0.0)
                                 "MinY", DoubleValue (0.0),
                                 "DeltaX", DoubleValue (5.0),//x轴节点间距:5m
                                 "DeltaY", DoubleValue (10.0), //y轴节点间距:10m

                                 "GridWidth", UintegerValue (3),//每行最大节点数
                                 "LayoutType", StringValue ("RowFirst"));

  mobility.SetMobilityModel ("ns3::RandomWalk2dMobilityModel",
                             "Bounds", RectangleValue (Rectangle (-50, 50, -50, 50)));
  mobility.Install (wifiStaNodes);//为AP节点设置移动模型

  mobility.SetMobilityModel ("ns3::ConstantPositionMobilityModel");
  mobility.Install (wifiApNode);
//6.安装TCP/IP协议族
  InternetStackHelper stack;
  stack.Install (csmaNodes);
  stack.Install (wifiApNode);
  stack.Install (wifiStaNodes);

  Ipv4AddressHelper address;

  address.SetBase ("10.1.1.0", "255.255.255.0");
  Ipv4InterfaceContainer p2pInterfaces;
  p2pInterfaces = address.Assign (p2pDevices);

  address.SetBase ("10.1.2.0", "255.255.255.0");
  Ipv4InterfaceContainer csmaInterfaces;
  csmaInterfaces = address.Assign (csmaDevices);

  address.SetBase ("10.1.3.0", "255.255.255.0");
  address.Assign (staDevices);
  address.Assign (apDevices);
//7.安装应用程序
  UdpEchoServerHelper echoServer (9);

  ApplicationContainer serverApps = echoServer.Install (csmaNodes.Get (nCsma));
  serverApps.Start (Seconds (1.0));
  serverApps.Stop (Seconds (10.0));

  UdpEchoClientHelper echoClient (csmaInterfaces.GetAddress (nCsma), 9);
  echoClient.SetAttribute ("MaxPackets", UintegerValue (1));
  echoClient.SetAttribute ("Interval", TimeValue (Seconds (1.0)));
  echoClient.SetAttribute ("PacketSize", UintegerValue (1024));

  ApplicationContainer clientApps = 
    echoClient.Install (wifiStaNodes.Get (nWifi - 1));
  clientApps.Start (Seconds (2.0));
  clientApps.Stop (Seconds (10.0));
//8.设置路由
  Ipv4GlobalRoutingHelper::PopulateRoutingTables ();

  Simulator::Stop (Seconds (10.0));
  //9.数据追踪
  if (tracing == true)
    {
      pointToPoint.EnablePcapAll ("third");
      phy.EnablePcap ("third", apDevices.Get (0));
      csma.EnablePcap ("third", csmaDevices.Get (0), true);
    }
	
  // 附加:使CourseChange (Trace Sink)和CourseChange (Trace Sources)相关联的代码(使用config path子系统,从系统中选取用户所要使用的Trace Sources)
  ostringstream oss;    
  oss << "/NodeList" << wifiStaNodes.Get(nWifi -1) -> GetId() << "/$ns3::MobilityModel/CourseChange";// "/"后面加的表示一个命名空间,这里用的为NodeList,即一个仿真中使用的节点的列表。后面是列表的索引,通过调用函数Get()获取节点,再通过GetId()得到节点的索引ID。当程序遇到$符号时,使调用GetObject()返回一个对象,需要给出返回对象的类型,这里为MobilityModel类,CourseChange属性(即要追踪的Tracing Source)。(注:如何确定Config Path:进入API文档->找到需要的类->Config Path标题下) (nWifi -1表示追踪n7节点的位置)
  Config::Connect(oss.str(), MakeCallback(&CourseChange));// 使用类Config的静态成员函数Connect将二者关联起来。函数的第二个参数:使函数CourseChange成为一个回调函数。第一个参数是一个由各种字符组成的字符串。

  //10.启动与结束
  Simulator::Run ();
  Simulator::Destroy ();
  return 0;
}
           
# 运行
./waf --run scratch/mythird
           

(3)如何确定Trace Sources

ns3官网->API文档->Modules->C++Constructs Used by All Modules->The list of all trace sources->找到可以直接使用的Trace Sources。

(4)如何确定Trace Sink

Trace Sink是一个函数,因此要确定其返回值和参数。看例子中已经写好的回调函数,例如CourseChange回调函数可以在ns-3.xx/examples/wirless/mixed-wireless.cc中找到一个函数CourseChangeCallback()。