ns3第一个项目(first.cc)

该仿真脚本的网络场景非常简单,只是在2个节点间创造一个简单的点到点通信。

源代码

  • GPL开源协议,一般会在上方看到一个相关机构的版权声明,而在GPL内容的下方会有相应的作者信息。
 * -*- 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文件,会把所有的文件根据模块功能进行大致的分类,提供按大致功能分类的一组include文件,在使用时只需要包含几个头文件即可。在编译过程中,每个ns-3的include文件被放在build目录下一个叫做ns3的目录中,这样可以避免include文件名的冲突。ns3/core-module.h与src/core目录下的ns-3模块相对应。当你编译时,waf会根据配置把在ns3目录下的公共的头文件放到build/debug或者build/optimized目录下。waf也会自动产生一个模块include文件来加载所有的公共头文件。
 #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"
  • ns-3命名空间:c++用using来把ns-3命名空间引入到当前的(全局的)声明中,这个声明就是说,你不用为了使用ns3的代码而必须在所有的ns-3代码前打上ns3::作用域操作符。
using namespace ns3;
  • 日志:
NS_LOG_COMPONENT_DEFINE ("FirstScriptExample");

查阅API文档,可以看到 NS_LOG_COMPONENT_DEFINE原来是如下宏定义,该语句的定义语句出现在log.h头文件中,功能是用特殊的名字定义一个日志组件。语句实际是生成一个LogComponent类型的对象g_log,并且通过构造函数LogComponent(name)初始化,变量name通过宏定义传递参数。

#define NS_LOG_COMPONENT_DEFINE(name)                           \
  static ns3::LogComponent g_log = ns3::LogComponent (name, __FILE__)

此时继续在Doxygen文档中追踪, 得到log Component::log Component源代码如下

LogComponent::LogComponent (char const * name)
  : m_levels (0), m_name (name)
{
  EnvVarCheck (name);
ComponentList *components = GetComponentList ();
  for (ComponentListI i = components->begin ();
       i != components->end ();
       i++)
    {
      if (i->first == name)
        {
          NS_FATAL_ERROR ("Log component \""<<name<<"\" has already been registered once.");
        }
    }
  components->push_back (std::make_pair (name, this));
}
 NS_LOG_COMPONENT_DEFINE ("FirstScriptExample");
  • 主函数
int
main (int argc, char *argv[])
{
....
} 

ns-3脚本没有什么特别的, 就是和一个普通的C++程序一样,需要定义一个会被第一个执行的主函数.下边两行脚本是用来使2个日志组件生效的, 他们被内嵌在Echo Client和Echo Server的应用中:

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

这两行代码将"UdupEcho"应用程序的客户端和服务器端的日志级别分别设置为"INFO"级别

  • 生成网络节点
    这两行将会创造ns-3节点对象,他们在仿真中代表计算机. 第一行只是声明了一个名为"nodes"NodeContainer节点容器类. 第二行调用nodes对象的Create()方法创建了2个节点, 并把指向这两个对象的指针存储在系统之中, 节点代表一台能够加入诸如协议栈, 应用,以及外设卡等的计算机.
 NodeContainer nodes;
  nodes.Create (2);

NodeContainer类是一个ns-3的一个Helper类, 能一次操作多个节点. 如果一个设备Helper类想要安装设备到大量相似的节点, 就可以使用NodeContainer类似的变量做参数. 在Doxygen中列出了很多具体方法,在此处使用的是Create(uint32_t n)

  • 物理连接计算机: ns3中将对应的物理实体抽象为网络设备和信道2个概念,下面语句就实现了网络节点物理连接:
  PointToPointHelper pointToPoint;
  pointToPoint.SetDeviceAttribute ("DataRate", StringValue ("5Mbps"));
  pointToPoint.SetChannelAttribute ("Delay", StringValue ("2ms"));

其实,Helper类是一族类,几乎所有的模块都有相应的一个或若干个Heiper类, 负责把网络设备连接到节点, 信道, 配置Ip地址等普遍任务, ns3把这些工作抽象出来,便于程序开发者使用. PointToPointHelp类负责设置网络设备和信道属性, 并通过Install方法把设备安装到节点中. 信道和网络设备是对应的, 比如以太网设备和无线信道就不能一起使用.
第一行初始化了一个PointerToPointerHelper的对象PointToPointers. 下一行从上层的角度告诉PointToPointHelper对象当创造一个PointerToPointerNetDevice对象时使用"5Mbit/s"来作为传输速率. 从细节上讲, 字符串"DataRate"与PointerToPointerNetDevice的一个属性相对应. 类似的, PointerToPointerChannel也有一个Delay属性告诉PointToPointHelper使用"2ms"作为每一个被创建的点到点信道的传输延时值.

 NetDeviceContainer devices;
 devices = pointToPoint.Install (nodes);

使用一个NetDeviceContainer对象来存放创建的NetDevice对象列表. 现在有一个包含2个节点的NodeContainer对象, 有一个准备在2个节点之间创建PointToPointNetDevices和wirePointToPointerChannel对象的PointerToPointerHelper 对象. 上边两行代码完成设备和信道的配置. 第一行声明了上边提到的设备容器, 第二行完成了主要工作. PointToPointHelper的Install()方法一个NodeContainer对象作为一个参数. 在Install() 方法内, 一个NetDevice Container被创建了. 对于在NodeContainer对象中的每一个节点, 一个PointToPointHelper对象被创建和保存在设备容器内. 一个PointToPointChannel对象被创建, 2个PointToPointNetDevices与之连接. 当PointToPointHelper对像创建时, 那些在助手中被预先设置的属性被用来初始化对象对应的属性值.
当调用了pointToPoint.install(nodes)后, 会有2个节点, 每个节点安装了点到点网络设备, 在他们之间是一个点到点信道. 两个设备会被配置在一个有2ms传输时延的信道上以5Mbit/s的速率传输数据.

  • 为计算机安装协议栈:
 InternetStackHelper stack;
  stack.Install (nodes);

类InternetStackHelper会为每一个节点容器中的节点安装一个网络协议栈,主要是IP层

Ipv4AddressHelper address;
  address.SetBase ("10.1.1.0", "255.255.255.0");

Ipv4AddressHelper为节点上的设备设置IP地址. 提供一个拓扑助手来管理IP地址的分配. 当执行实际的地址分配时, 唯一用户可见的API是设置IP地址和子网掩码. 第二行声明了一个地址助手对象, 并且告诉它应该从10.1.1.0开始以子网掩码为255.255.255.0分配地址. 地址分配默认是从1开始, 以1单调增长.

Ipv4InterfaceContainer interfaces = address.Assign (devices);

底层ns-3系统事实上会记住所有分配的IP地址. 这行代码完成了真正的地址配置. 在ns-3中使用Ipv4Interface对象将一个IP地址同一个设备关联起来. 现在有了一个安装了协议栈, 配置IP地址类的点到点的网络. 这时候所需要做的事情是运用它来产生数据通信.

  • 安装应用层
    下面代码用来安装服务器端应用程序, 设置端口号.
 UdpEchoServerHelper echoServer (9);
 ApplicationContainer serverApps = echoServer.Install (nodes.Get (1));

第一行声明了UdpEchoServerHelper。像往常一样,这个并非应用本身,是一个用来帮助创建真正应用的对象。同其他助手对象类似,UdpEchoServerHelper对象有一个Install方法。实际上是这个方法的执行,才初始化回显服务器的应用,并将应用连接到一个节点上去。安装方法把NodeContainter当作一个参数。这里有一个C++隐式转换,此转换以nodes.Get(1)的结果作为输入,并把它作为一个未命名的Node Container构造函数的参数,最终这个未命名的NodeContainer被送入install方法中。echoServer.Install将会在管理节点的NodeContainer容器引索号为1的机节点上安装一个UdpEchoServerApplication。安装会返回一个容器,这个容器中包含了指向所有被助手创建的应用指针。

  serverApps.Start (Seconds (1.0));
  serverApps.Stop (Seconds (10.0));

这两行会使echo服务应用在1s时开始,并在10s时停止。时间点是用Application Container的方法Start和Stop来设置的,这些方法以“Time”对象为参数。在这种情况下,使用了一种明确的C++转换序列来获得C++双精度(double)的1.0,并且用一个Seconds准换来把它转换到ns-3的Timer对象。

  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));

这里是进行客户端应用层的设置,客户端应用的设置与服务器端类似,也有一个UdpEchoClientHelper来管理UdpEchoClientApplication。然而,对于echo客户端,需要设置五个不同的属性。首先2个属性是在UdpEchoClientHelper的构建过程中被设置的。按照助手构造函数的格式,本文把“Remote Address”和“RemotePort‘
属性传递给助手。
在上边第一行代码中,本文创建了一个助手并告诉它设置客户端的远端地址为服务器节点IP地址。同样告诉它准备发送第二个数据分组到端口9。”Max Packets“属性告诉客户端所允许它在模拟期间能发送的最大数据分组个数。“Interval”属性告诉客户端在2个数据分组之间要等待多长时间,“PacketSize”属性告诉客户端它的数据分组应该承载多少数据。最后两行告诉echo客户端何时来开始和停止,但是这里本文使客户端在服务器端生效1s后才开始(在模拟器中时间2秒的时候)。

  • 启动模拟器
  Simulator::Run ();

这是用全局变量来运行模拟器。当 Simulator::Run 被调用时,系统会开始遍历预设事件的列表并执行。首先他会在1.0时运行事件,这个事件会使echo服务端应用生效。接下来仿真器会运行在t=2.0s时的事件,即让echo客户端应用开始。同样地,这个事件可能会预定更多的其他事件。在echo客户端应用中,开始事件的执行可能会通过给服务器传送一个数据分组来开始仿真的数据传送阶段。发送一个数据分组给服务端会引发一系列更多的事情。这个事件会被预设在此事件之后,并根据已经在脚本中设定的时间参数来执行数据分组的应答。
我们只发送一个数据分组,在此之后,那个被单独的客户端应答请求所引发的连锁反应会停止,并且模拟器会进入空闲状态。当这些都发生时,接下来的事件就是服务端和客户端的Stop事件。当这些事件被执行后,就没有将来的事件来执行了,函数 Simulator::Run 会被返回。整个模拟过程结束。

 Simulator::Destroy ();
  return 0;

下面剩下的事情就是清理。清理通过调用全局函数来完成,助手函数被执行后,助手安排的钩子函数就被插入到模拟器中来销毁所被创建的对象,ns-3系统就会帮你料理繁杂的任务。

你可能感兴趣的:(NS3)