ns3

ns3的概念概述与第一个例子

ns3例子

Posted by MZ on September 17, 2022

一、概念概述

  • Node

    互联网的终端系统或主机,相当于一台裸机,不过我们只需要添加其网络功能即可,不需要操作系统之类的。Node抽象由C++类表示,用于管理模拟中计算设备表示的方法。

  • Application

    应用程序,用来生成一些模拟的活动,比如发送数据到另一台电脑。

  • Channel

    信道,各个终端都是通过信道相互连接在一起,这里的信道可以是有线的(以太网),也可以是无线的(WiFi,蜂窝网络)。

  • Net Device

    网络设备,相当于网卡一样的东西,只有安装在Node上,Node才可以进行网络通信。

  • Topology Helpers

    用于帮助构建Node、Channel、Net Device等类,当网络结构比较庞大复杂时,用拓扑帮助可以更快的构建这些基类。

二、第一个例子代码解读

简单实现两个终端系统的网络互连

#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"
//"ns3/core-module.h"是ns3的核心模块需要的;
//"ns3/network-module.h"是ns3的网络模块; 
//"ns3/applications-module.h"是ns3的应用模块需要的;
//一般我们自己写脚本时,上面三个都是必要的。
//"ns3/internet-module.h"是ns3提供的因特网模块需要的;
//"ns3/point-to-point-module.h"是点对点通信模块需要的。

// Default Network Topology
//
//       10.1.1.0
// n0 -------------- n1
//    point-to-point
//

using namespace ns3;

NS_LOG_COMPONENT_DEFINE ("FirstScriptExample");

 int main (int argc, char *argv[])
{
     //接收命令行参数
     CommandLine cmd (__FILE__);
     cmd.Parse (argc, argv);
     //设置时间分辨率为1纳秒
     Time::SetResolution (Time::NS);
     //启用 Echo 客户端和 Echo 服务器应用程序中内置的两个日志记录组件
     LogComponentEnable ("UdpEchoClientApplication", LOG_LEVEL_INFO);
     LogComponentEnable ("UdpEchoServerApplication", LOG_LEVEL_INFO);
     //创建 ns-3 对象,这些对象将表示模拟中的计算机
     //NodeContainer,Node容器,同于存放node,方便管理和使用
     NodeContainer nodes;
     nodes.Create (2);
     //使用点到点拓扑助手类帮助我们直接设置NetDevice和Channel,相当于现实世界的网卡和信道
     PointToPointHelper pointToPoint;
     //网卡速率为5Mbps
     pointToPoint.SetDeviceAttribute ("DataRate", StringValue ("5Mbps"));
     //信道延迟为2ms
     pointToPoint.SetChannelAttribute ("Delay", StringValue ("2ms"));
     //NetDevice容器
     NetDeviceContainer devices;
     //在内部,创建一个NetDeviceContainer。对于NodeContainer中的每个节点(点对点链接必须正好有两个节点) ,将创建一个PointToPointNetDevice并将其保存在设备容器中。创建PointToPointChannel并附加两个PointToPointNetDevice。当PointToPointHelper创建对象时,以前在助手中设置的Attritribute用于初始化创建对象中的相应Attritribute。
     //拓扑助手将Channel与NetDevice 安装到节点上----重要的一步
     devices = pointToPoint.Install (nodes);
     //这一步之后,每个节点都会有点对点网络设备(网卡)和它们之间的单个点对点通道
     //节点上有了设备,开始安装协议
     //网络堆栈助手相当于点对点助手,只不过它是帮助节点安装网络协议堆栈(TCP、UDP、IP)
     InternetStackHelper stack;
     stack.Install (nodes);
     //为每个网络设备配置ip地址
     Ipv4AddressHelper address;
     //设置基础ip地址和网络掩码
     address.SetBase ("10.1.1.0", "255.255.255.0");
     
     //我们使用 IPv4Interface 对象在 IP 地址和设备之间建立关联。
     Ipv4InterfaceContainer interfaces = address.Assign (devices);
     
     //现在我们有了点对点网络,并有了网络协议和ip地址,接下来需要应用程序产生流量。
     //设置服务端应用程序
     UdpEchoServerHelper echoServer (9);

     ApplicationContainer serverApps = echoServer.Install (nodes.Get (1));
     serverApps.Start (Seconds (1.0));
     serverApps.Stop (Seconds (10.0));
     
     //设置客户端应用程序
     UdpEchoClientHelper echoClient (interfaces.GetAddress (1), 9);
     echoClient.SetAttribute ("MaxPackets", UintegerValue (1));
     echoClient.SetAttribute ("Interval", TimeValue (Seconds (1.0)));
     echoClient.SetAttribute ("PacketSize", UintegerValue (1024));

     ApplicationContainer clientApps = echoClient.Install (nodes.Get (0));
     clientApps.Start (Seconds (2.0));
     clientApps.Stop (Seconds (10.0));
     //运行
     Simulator::Run ();
     Simulator::Destroy ();
     return 0;
 }

下图是该实例的简单拓扑图


三、Logging日志模块

日志七个等级:

日志级别 日志描述(关联的宏:)
LOG_ERROR 记录错误消息(关联的宏:NS_LOG_ERROR)
LOG_WARN 日志警告消息(关联的宏:NS_LOG_WARN)
LOG_DEBUG 记录相对罕见的临时调试消息(关联的宏:NS_LOG_DEBUG)
LOG_INFO 记录有关程序进度的信息性消息(关联的宏:NS_LOG_INFO)
LOG_FUNCTION 记录描述每个调用的函数的消息(两个关联的宏:NS_LOG_FUNCTION,用于成员函数,NS_LOG_FUNCTION_NOARGS,用于静态函数)
LOG_LOGIC 描述函数内逻辑流的日志消息(关联的宏:NS_LOG_LOGIC)
LOG_ALL 记录上面提到的所有内容(没有关联的宏)

I、通过设置shell环境变量

1.调整日志等级

$ export NS_LOG=UdpEchoClientApplication=level_all

需要注意的是 NS_LOG后的日志模块必须已经被定义过,要么自己定义,要么导入包中已经存在,UdpEchoClientApplication就是包中内置的日志模块

上面这行命令执行完后,用export -p可以看到以下信息:

$ export -p 
declare -x NS_LOG="UdpEchoClientApplication=level_all"

环境变量的NS_LOG就发生了改变,然后运行脚本发现打印出来的东西变多了

#没有设置环境变量之前的输出
At time +2s client sent 1024 bytes to 10.1.1.2 port 9
At time +2.00369s server received 1024 bytes from 10.1.1.1 port 49153
At time +2.00369s server sent 1024 bytes to 10.1.1.1 port 49153
At time +2.00737s client received 1024 bytes from 10.1.1.2 port 9
#设置完环境变量的输出,可以看到只多了客户端应用程序的一些信息,服务端并没有增加,因为我们只把客户端的日志等级调到了最高级
UdpEchoClientApplication:UdpEchoClient(0xef90d0)
UdpEchoClientApplication:SetDataSize(0xef90d0, 1024)
UdpEchoClientApplication:StartApplication(0xef90d0)
UdpEchoClientApplication:ScheduleTransmit(0xef90d0, +0ns)
UdpEchoClientApplication:Send(0xef90d0)
At time +2s client sent 1024 bytes to 10.1.1.2 port 9
At time +2.00369s server received 1024 bytes from 10.1.1.1 port 49153
At time +2.00369s server sent 1024 bytes to 10.1.1.1 port 49153
UdpEchoClientApplication:HandleRead(0xef90d0, 0xee7b20)
At time +2.00737s client received 1024 bytes from 10.1.1.2 port 9
UdpEchoClientApplication:StopApplication(0xef90d0)
UdpEchoClientApplication:DoDispose(0xef90d0)
UdpEchoClientApplication:~UdpEchoClient(0xef90d0)

2.确定哪个方法生成的日志信息——prefix_func

$ export 'NS_LOG=UdpEchoClientApplication=level_all|prefix_func'

UdpEchoClientApplication:UdpEchoClient(0xea8e50)
UdpEchoClientApplication:SetDataSize(0xea8e50, 1024)
UdpEchoClientApplication:StartApplication(0xea8e50)
UdpEchoClientApplication:ScheduleTransmit(0xea8e50, +0ns)
UdpEchoClientApplication:Send(0xea8e50)
###
#只有客户端的前面打印出了方法,想要服务端打印出来方法,操作和客户端一样
UdpEchoClientApplication:Send(): At time +2s client sent 1024 bytes to 10.1.1.2 port 9
At time +2.00369s server received 1024 bytes from 10.1.1.1 port 49153
At time +2.00369s server sent 1024 bytes to 10.1.1.1 port 49153
UdpEchoClientApplication:HandleRead(0xea8e50, 0xea5b20)
UdpEchoClientApplication:HandleRead(): At time +2.00737s client received 1024 bytes from 10.1.1.2 port 9
###
UdpEchoClientApplication:StopApplication(0xea8e50)
UdpEchoClientApplication:DoDispose(0xea8e50)
UdpEchoClientApplication:~UdpEchoClient(0xea8e50)
$ export 'NS_LOG=UdpEchoClientApplication=level_all|prefix_func:UdpEchoServerApplication=level_all|prefix_func'

这行命令使得客户端。服务端日志等级都为最高级,且前面加上方法调用,输出就不展示了。

3.查看日志生成的模拟时间——prefix_time

$ export 'NS_LOG=UdpEchoClientApplication=level_all|prefix_func|prefix_time:UdpEchoServerApplication=level_all|prefix_func|prefix_time'

II、通过宏来定义、开启、显示日志信息

1.宏定义日志信息

NS_LOG_COMPONENT_DEFINE ("FirstScriptExample");

定义了本文件的日志模块

2.宏开启日志信息,并设置日志等级——LogComponentEnable(name, level)

LogComponentEnable ("FirstScriptExample", LOG_LEVEL_INFO);

等价于shell中:export NS_LOG = ‘FirstScriptExample=info’

例如第一个例子中的这两行,这是内置的日志模块

LogComponentEnable ("UdpEchoClientApplication", LOG_LEVEL_INFO);
LogComponentEnable ("UdpEchoServerApplication", LOG_LEVEL_INFO);

开启它们只能显示对应模块它们自己的日志信息,并不能显示自己写的文件的日志信息

3.输出日志信息

以下三行代码,要根据我们开启的日志等级进行输出

NS_LOG_WARN("Message:level_warn");

NS_LOG_INFO("Message:level_info");

NS_LOG_LOGIC("Message:level_logic");

因为开启的级别是info,所以只有Message:level_warn和Message:level_info会被输出。

想要在该文件下打印日志信息,必须定义开启同名的日志,如上面提到的FirstScriptExample,若只定义不开启,则三行代码都不会输出,只开启不定义必然报错;UdpEchoClientApplication与UdpEchoServerApplication的日志模块是内置的,不是该文件下的,所以开启了也无法输出该文件下的日志信息

四、命令行参数

使用命令行参数系统的第一步是声明命令行解析器

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

  CommandLine cmd;
  cmd.Parse (argc, argv);

  ...
}

在命令行里打印有关信息

$ ./ns3 run "scratch/myfirst --PrintHelp"
#打印出的信息
General Arguments:
  --PrintGlobals:              Print the list of globals.
  --PrintGroups:               Print the list of groups.
  --PrintGroup=[group]:        Print all TypeIds of group.
  --PrintTypeIds:              Print all TypeIds.
  --PrintAttributes=[typeid]:  Print all attributes of typeid.
  --PrintVersion:              Print the ns-3 version.
  --PrintHelp:                 Print this help message.
$ ./ns3 run "scratch/myfirst --PrintAttributes=ns3::PointToPointNetDevice"
#打印信息中的一条
--ns3::PointToPointNetDevice::DataRate=[32768bps]:
  The default data rate for point to point links

可以看到默认网络设备的DataRate是32768bps,而我们在程序中设置的是5Mbps将默认值覆盖掉了,如果把程序中的5Mbps注释掉,打印结果如下

PointToPointHelper pointToPoint;
//pointToPoint.SetDeviceAttribute ("DataRate", StringValue ("5Mbps"));
$ export 'NS_LOG=UdpEchoServerApplication=level_all|prefix_time'

+0.000000000s UdpEchoServerApplication:UdpEchoServer(0x20d0d10)
+1.000000000s UdpEchoServerApplication:StartApplication(0x20d0d10)
At time +2s client sent 1024 bytes to 10.1.1.2 port 9
+2.257324218s UdpEchoServerApplication:HandleRead(0x20d0d10, 0x20900b0)
+2.257324218s At time +2.25732s server received 1024 bytes from 10.1.1.1 port 49153
+2.257324218s Echoing packet
+2.257324218s At time +2.25732s server sent 1024 bytes to 10.1.1.1 port 49153
At time +2.51465s client received 1024 bytes from 10.1.1.2 port 9
+10.000000000s UdpEchoServerApplication:StopApplication(0x20d0d10)
UdpEchoServerApplication:DoDispose(0x20d0d10)
UdpEchoServerApplication:~UdpEchoServer(0x20d0d10)

现在,它在 2.25732 秒处接收数据包。这是因为我们刚刚将的数据速率从每秒5兆位降至默认的每秒32768位。

通道延迟也是同样的道理,这里只放一下延迟的默认值

$ ./ns3 run "scratch/myfirst --PrintAttributes=ns3::PointToPointChannel"

--ns3::PointToPointChannel::Delay=[0ns]:
  Transmission delay through the channel

最后,只需要在命令行里给注释掉的DataRate与Delay属性赋值,就可以和源代码输出同样的结果

$ ./ns3 run "scratch/myfirst --ns3::PointToPointNetDevice::DataRate=5Mbps --ns3::PointToPointChannel::Delay=2ms"

也可以将自己的钩子函数添加到命令行系统

int
main (int argc, char *argv[])
{
  uint32_t nPackets = 1;

  CommandLine cmd;
  cmd.AddValue("nPackets", "Number of packets to echo", nPackets);
  cmd.Parse (argc, argv);

  ...
  echoClient.SetAttribute ("MaxPackets", UintegerValue (nPackets));
  ...

命令行里输入MaxPackets的值

$ ./ns3 run "scratch/myfirst --nPackets=2"

成功看到两个数据包的回显

+0.000000000s UdpEchoServerApplication:UdpEchoServer(0x836e50)
+1.000000000s UdpEchoServerApplication:StartApplication(0x836e50)
At time +2s client sent 1024 bytes to 10.1.1.2 port 9
+2.003686400s UdpEchoServerApplication:HandleRead(0x836e50, 0x8450c0)
+2.003686400s At time +2.00369s server received 1024 bytes from 10.1.1.1 port 49153
+2.003686400s Echoing packet
+2.003686400s At time +2.00369s server sent 1024 bytes to 10.1.1.1 port 49153
At time +2.00737s client received 1024 bytes from 10.1.1.2 port 9
At time +3s client sent 1024 bytes to 10.1.1.2 port 9
+3.003686400s UdpEchoServerApplication:HandleRead(0x836e50, 0x8450c0)
+3.003686400s At time +3.00369s server received 1024 bytes from 10.1.1.1 port 49153
+3.003686400s Echoing packet
+3.003686400s At time +3.00369s server sent 1024 bytes to 10.1.1.1 port 49153
At time +3.00737s client received 1024 bytes from 10.1.1.2 port 9
+10.000000000s UdpEchoServerApplication:StopApplication(0x836e50)
UdpEchoServerApplication:DoDispose(0x836e50)
UdpEchoServerApplication:~UdpEchoServer(0x836e50)

五、跟踪系统

I、Ascii跟踪

在Simulator::Run ()之前添加两行代码,开启Ascii跟踪

AsciiTraceHelper ascii;
pointToPoint.EnableAsciiAll (ascii.CreateFileStream ("myfirst.tr"));

写在PointToPointHelper定义之后并没有生效,

PointToPointHelper pointToPoint;
AsciiTraceHelper ascii;
pointToPoint.EnableAsciiAll (ascii.CreateFileStream ("myfirst.tr"));

而在Run之前生效了

AsciiTraceHelper ascii;
pointToPoint.EnableAsciiAll (ascii.CreateFileStream ("myfirst.tr"));
Simulator::Run ();

在本例中,我们将跟踪模拟中每个点对点网络设备中存在的传输队列上的事件。传输队列是一个队列,发往点对点通道的每个数据包都必须通过该队列。请注意,跟踪文件中的每一行都以一个单独字符开头(后面有一个空格)。此字符将具有以下含义:

  • +:设备队列上发生排队操作;
  • -:设备队列上发生取消排队操作;
  • d:数据包被丢弃,通常是因为队列已满;
  • r:网络设备收到数据包。

II、pcap 跟踪

启用代码

pointToPoint.EnablePcapAll ("myfirst");

结果查看

$ tcpdump -nn -tt -r myfirst-0-0.pcap
reading from file myfirst-0-0.pcap, link-type PPP (PPP)
2.000000 IP 10.1.1.1.49153 > 10.1.1.2.9: UDP, length 1024
2.514648 IP 10.1.1.2.9 > 10.1.1.1.49153: UDP, length 1024

tcpdump -nn -tt -r myfirst-1-0.pcap
reading from file myfirst-1-0.pcap, link-type PPP (PPP)
2.257324 IP 10.1.1.1.49153 > 10.1.1.2.9: UDP, length 1024
2.257324 IP 10.1.1.2.9 > 10.1.1.1.49153: UDP, length 1024

vim不能查看pcap文件,只能用tcpdump读取