天天看點

【網絡仿真】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()。