本章参考书籍:《ns-3网络模拟器基础与应用》

本章在ns-3核心模块的基础上介绍 ns-3中相对比较通用的其他模块,具体内容包括如下内容。

  • 网络模块:讨论网络数据分组的相关问题;

  • Internet模块:介绍Internet网络的基本协议,包括路由、 传输层;

  • 网络设备模块:ns-3提供的典型网络设备,包括点到点、有线和无线局域网;

  • 应用层模块:介绍ns-3提供的应用程序;

  • 移动模块:介绍ns-3提供的丰富的移动模型;

  • 能量模块:讨论关于节点能量消耗的话题。

1、Internet模块

1.1、Internet协议栈

由一个 Node 创建的节点仅仅是一个躯壳,在为其继承各种功能模块前它几乎没有任何作用的。那么 Internet 协议栈也是要为节点添加的模型之一,因此ns-3提供了诸多像TCP/IPv4和IPv6相关的协议栈组件,比如IPv4、ARP、UDP、TCP、IP6、邻居发现和其他协议。这些协议都能在src/Internet文件中找到源代码。

上文也讲到现实网络中的节点并非是本文使用 Node 节点类创建的节点,而是将大多模型集成的功能性节点。这些模型可以手动,也可以通过Helper 聚集到一起 。 对于 Internet 协议栈,使用InternetStackHelper::Install()将其集成到Node创建的节点中。

void
InternetStackHelper::Install(Ptr<Node> node) const
{
    if (m_ipv4Enabled)
    {
        /* IPv4 stack */
        CreateAndAggregateObjectFromTypeId(node, "ns3::ArpL3Protocol");
        CreateAndAggregateObjectFromTypeId(node, "ns3::Ipv4L3Protocol");
        CreateAndAggregateObjectFromTypeId(node, "ns3::Icmpv4L4Protocol");
        if (!m_ipv4ArpJitterEnabled)
        {
            Ptr<ArpL3Protocol> arp = node->GetObject<ArpL3Protocol>();
            NS_ASSERT(arp);
            arp->SetAttribute("RequestJitter",
                              StringValue("ns3::ConstantRandomVariable[Constant=0.0]"));
        }

        // Set routing
        Ptr<Ipv4> ipv4 = node->GetObject<Ipv4>();
        if (!ipv4->GetRoutingProtocol())
        {
            Ptr<Ipv4RoutingProtocol> ipv4Routing = m_routing->Create(node);
            ipv4->SetRoutingProtocol(ipv4Routing);
        }
    }

    if (m_ipv6Enabled)
    {
        /* IPv6 stack */
        CreateAndAggregateObjectFromTypeId(node, "ns3::Ipv6L3Protocol");
        CreateAndAggregateObjectFromTypeId(node, "ns3::Icmpv6L4Protocol");
        if (!m_ipv6NsRsJitterEnabled)
        {
            Ptr<Icmpv6L4Protocol> icmpv6l4 = node->GetObject<Icmpv6L4Protocol>();
            NS_ASSERT(icmpv6l4);
            icmpv6l4->SetAttribute("SolicitationJitter",
                                   StringValue("ns3::ConstantRandomVariable[Constant=0.0]"));
        }
        // Set routing
        Ptr<Ipv6> ipv6 = node->GetObject<Ipv6>();
        if (!ipv6->GetRoutingProtocol())
        {
            Ptr<Ipv6RoutingProtocol> ipv6Routing = m_routingv6->Create(node);
            ipv6->SetRoutingProtocol(ipv6Routing);
        }
        /* register IPv6 extensions and options */
        ipv6->RegisterExtensions();
        ipv6->RegisterOptions();
    }

    if (m_ipv4Enabled || m_ipv6Enabled)
    {
        CreateAndAggregateObjectFromTypeId(node, "ns3::TrafficControlLayer");
        CreateAndAggregateObjectFromTypeId(node, "ns3::UdpL4Protocol");
        CreateAndAggregateObjectFromTypeId(node, "ns3::TcpL4Protocol");
        if (!node->GetObject<PacketSocketFactory>())
        {
            Ptr<PacketSocketFactory> factory = CreateObject<PacketSocketFactory>();
            node->AggregateObject(factory);
        }
    }

    if (m_ipv4Enabled)
    {
        Ptr<ArpL3Protocol> arp = node->GetObject<ArpL3Protocol>();
        Ptr<TrafficControlLayer> tc = node->GetObject<TrafficControlLayer>();
        NS_ASSERT(arp);
        NS_ASSERT(tc);
        arp->SetTrafficControl(tc);
    }
}

代码中多个模块(TCP、IP路由)在ns-3中都有实现,这些对象可以通过对象工厂机制和Helper类添加到Node节点。

那么如何使用 InternetStackHelper 类为节点添加网络协议栈呢?最简单的方法是在为Node节点分配IP地址前,添加如下代码:

InternetStackHelper stack;
stack.Install (nodes);//为节点配置协议栈

一个Internet Stack Node包含如下几个组件:

  • Layer-3 protocols

大家知道在 TCP/IP 网络体系结构中,在网络接口之上的为网际层 IP,在这一层中包括:IPv4、IP6和ARP等协议。类IPv4L3Protocol 是实现网际层IP的类,但是这个类不是对外的公共接口,而是供接口类IPv4调用。

大家知道网际层IP从下层获取分组,然后分析出其源地址IP和目的地址IP,底层协议会调用IPv4L3Protocol类中的函数:

void Receive(
    Ptr<NetDevice> device, 
    Ptr<const Packet> p, 
    uint16_t protocol, 
    const Address &from, 
    const Address &to, 
    NetDevice::PacketType packetType
);

首先,观察函数Receive()的第一个参数指向Device节点,这个Device是在配置节点前预先安装在 Node 节点中的协议,而这个函数能够被下层自动调用的前提是通过如下代码来注册该函数:

RegisterProtocolHandler(
    MakeCallback(&Ipv4Protocol::Receive, ipv4), 
    Ipv4L3Protocol::PROT_NUMBER, 
    0
);

IPv4L3Protocol对象被聚合到Node节点中,每个节点中仅有一个IPv4L3Protocol对象,高层的协议(比如TCP)要发送一个TCP数据分组给IPv4L3Protocol对象是通过调用函数GetObject<Ipv4L3Protocol >() 来获取该节点的底层协议,如下:

// 在一个节点上发送 IPv4 数据包
Ptr<Ipv4L3Protocol> ipv4 = m_node->GetObject<Ipv4L3Protocol> ();
if (ipv4 != 0)
{
  ipv4->Send (packet,saddr,daddr,PROT_NUMBER);
}

一旦IPv4路由发现有分组发送给该节点,相应地IPv4对象就会把分组发送给上层协议,方法是调用下面函数:

// 负责将接收到的数据包交付给本地协议栈的上层。
void Ipv4L3Protocol::LocalDeliver(
    Ptr<const Packet> packet,
    Ipv4Header const &ip,
    uint32_t iif
)

首先找到节点中基于IP协议的IPv4L4Protocol对象,然后在IPv4L4Protocol上调用 Receive 函数 。

所有相关的类都有或多或少的Traces以便跟踪己经发送、接收和丢失的分组,用户可以通过相关的 Trace 去发现己经丢失的分组以及分组在何时何地丢失。

一个常见的错误就是在发送数据分组之前忘了考虑ARP发送队列的大小,当使用UDP发送大的数据分组时就会给用户带来问题,由于ARP缓冲区有限,那么在发送数据时就有可能造成数据的溢出,在这种情况下增加缓冲区的大小是非常有必要的。

// 设置 ARP 缓存中挂起队列的大小(PendingQueueSize)
Config::SetDefault("ns3::ArpCache::PendingQueueSize", 
                   UintegerValue(MAX_BURST_SIZE / L2MTU * 3));

IPv6的实现方式和上述讲述的方法基本一致,不同之处在于,IPv6的节点包含2个协议栈,同时支持IPv4和IPv6两种协议,允许IPv6 套接字接收IPv4分组,并发送远端使用IPv4协议节点相同格式的数据分组,仅仅支持IPv6协议的套接字ns-3还不予提供。

  • Layer-4 protocols

在TCP/IP网络体系结构中,网际层的上层为传输层,下面讨论如何把传输层协议和套接字以及应用绑定在一起,每一个传输层协议的实现都是一个套接字工厂,每一个应用程序都需要一个套接字。比如创建一个UDP套接字,那么应用程序就要做如下工作:

Ptr<Udp> udpSocketFactory = GetNode ()->GetObject<Udp> ();
Ptr<Socket> m_socket = socketFactory->CreateSocket ();
m_socket->Bind (m_local_address);

首先,第一行代码是从Node节点中获取一个UDP套接字工厂指针来创建一个套接字(第二行代码),然后通过第三行代码把该套接字绑定到地址上,如果作为参数的地址己经被绑定了其他套接字,那么就会出现错误而不是覆盖。

1.2、路由

(1)global centralized routing

在很多时候被称为“God”路由,它实现的是在整个网络仿真过程中都使用最理想化的路由算法,即每次都是最短路径的方法,显然这种方法在现实中是不存在的,也就是在仿真中其实没有用任何路由协议。

当前,针对IPv4全局集中式路由支持Point-to-point和CSMA两种链路结构,当读者使用默认的Helper类InternetStackHelper创建网络协议栈时,全局路由会被默认绑定到节点上,这种路由的优先级是低于静态路由协议的,用户可以通过Ipv4StaticRouting API 设置指定的路由协议而不是使用优先级级别较低的全局路由。全局路由协议的API规模很小,用户在使用该路由协议前要在脚本中包括如下头文件:#include "ns3/internet-module.h"

在使用InternetStackHelper类创建网络协议栈时,全局路由协议会被默认地集成到 Node 节点。当 IP 地址被配置到 Node 节点时,Ipv4GlobalRoutingHelper::PopulateRoutingTables () 函数会使每一个节点拥有一个IPv4接口接收路由表,而路由表是由GlobalRouteManager注册。

路由表更新可以通过下面的函数来实现:Ipv4GlobalRoutingHelper::RecomputeRoutingTables ()

(2)unicast routing

ns-3现在为IPv4支持7种单播路由协议,而对IPv6只有2个单播路由协议。

  • IPv4 optimized link state routing (OLSR)

  • IPv4 Static Routing(支持单播和多播)

  • IPv4 ad hoc on demand distance vector (AODV)

  • IPv4 destination sequenced distance vector (DSDV)

  • IPv4 ListRouting(用来保存路由协议的优先列表)

  • IPv4 GlobalRouting(用来保存由GlobalRouteManager给出的路由)

  • IPv4 NixVectorRouting

  • IPv6 ListRouting

  • IPv6 StaticRouting

因为ns-3是一个开源的软件,任何一个人都可以编写自己的路由,随后讲述几个经典的路由

  • IPv4ListRouting

这一部分将讲述现在默认的IPv4 RoutingProtocol,IPv4ListRouting提供一个优先级列表保存多种路由并支持用户编写自己的路由,在这个列表中,ns-3实现了多种路由协议重塑或保持自己状态,IPv4会依据这个队列来调用这些路由协议。

类 IPv4ListRouting 提供了一个纯虚函数void AddRoutingProtocol (Ptr<Ipv4 RoutingProtocol> routingProtocol,int16_t priority) 允许用户添加路由协议,这个函数的实现代码在 Internet-Stack 模块中的类 Ipv4ListRoutingImpl 中。

第一个参数就是路由协议,第二个参数是整型数据作为协议的优先级别,在ns-3中,Helper类初始化一个IPv4 ListRoutingImpl对象时,同时会将其优先级设置为0,从内部来说,一个IPv4 RoutingProtocols列表中所保存的路由协议会根据优先级别的高低一一被调用,直至有一个协议被匹配成功。

因此,如果想要你自己添加的 IPv4RoutingProtocol 并且想要其优先权小于静态路由协议,你就要将其优先值设置为负值:

Ptr<MyRoutingProtocol> myRoutingProto = CreateObject<MyRoutingProtocol> ();
listRoutingPtr->AddRoutingProtocol (myRoutingProto,-10);

(3)multicast routing

下面一行代码简单地说明了如何把多播路由添加到Node节点中。

void Ipv4StaticRouting::AddMulticastRoute(
    Ipv4Address origin,
    Ipv4Address group,
    uint32_t inputInterface,
    std::vector<uint32_t> outputInterfaces
);

从上面函数的参数可以看出,一个多播路由需要一个源地址、一组目的地址、一个输入网络接口和一组输出网络接口。

路由通配符在源地址和多播组中可能会被用到,比如通过函数Ipv4Address::GetAny ()返回的地址可以标识为“0.0.0.0”,如果源地址和多播地址都用通配符标识,那么用户就可以创建默认的多播地址用于转发接口。

1.3、TCP

ns-3提供了关于TCP的实现,相关代码可以在src/network目录中找到,因此用户可以把一些代码化为自用。下面介绍2个抽象类。

  • TcpSocket:这个类定义在src/internet/model/tcp-socket. {cc,h}中,这个类的主要作用是用成员变量保存TCP套接字的属性,这样就能保证这些属性的可重用性(虚函数的主要特点),例如成员变量 InitialCwnd 可以被每一个从该类继承的任何子类调用。

  • TcpSocketFactory:这个类的作用是被layer-4 protocol对象使用来创建TCP套接字。ns-3中TCP实现了2种版本:TCP和network simulation cradle (NSC)TCP

(1)ns-3 TCP

TCP实现文件包括:

src/internet/model/tcp-header.{cc,h}

src/internet/model/tcp-l4-protocol.{cc,h}

src/internet/model/tcp-socket-factory-impl.{cc,h}

src/internet/model/tcp-socket-base.{cc,h}

src/internet/model/tcp-tx-buffer.{cc,h}

src/internet/model/tcp-rx-buffer.{cc,h}

src/internet/model/tcp-rfc793.{cc,h}

src/internet/model/tcp-tahoe.{cc,h}

src/internet/model/tcp-reno.{cc,h}

src/internet/model/tcp-newreno.{cc,h}

src/internet/model/rtt-estimator.{cc,h}

src/network/model/sequence-number.{cc,h}

从上面可以知道ns-3提供了不同种类的TCP,这些类的基类都为TcpSocket-Base,包括RFC 793 (没有拥塞控制)、Tahoe、Reno和 NewReno,其中,NewReno为默认的TCP版本。

在大多数情况下,用户是通过告知应用层的应用程序使用哪一种套接字代理工厂来使用TCP的,而所使用的Helper类函数定义在文件夹 src/applications/ helper和src/network/helper中,下面就给出如何创建一个TCP接收器:

uint16_t port = 50000;
// Address是个类 sinkLocalAddress是实例化对象,后面的是构造函数的参数,InetSocketAddress(Ipv4Address::GetAny(), port) 表示创建一个监听地址,可以接收发送到任意本地 IP 的、指定端口(50000)的数据
Address sinkLocalAddress(InetSocketAddress(Ipv4Address::GetAny(), port));
PacketSinkHelper sinkHelper("ns3::TcpSocketFactory", sinkLocalAddress);
ApplicationContainer sinkApp = sinkHelper.Install(serverNode);
sinkApp.Start(Seconds(1.0));
sinkApp.Stop(Seconds(10.0));

同样地,创建为服务器端OnOff应用程序发送的TCP数据:

OnOffHelper clientHelper("ns3::TcpSocketFactory",Address ());

上面的代码仅仅是使用类TcpSocketFactory,而没有涉及到所创建的TCP是哪个版本,事实上,如果没有做任何工作直接使用 TcpSocketFactory创建的TCP是默认的版本。

用户在仿真程序的顶部以及在为节点创建网络协议栈前添加下面的代码就可以改变默认的TCP版本。

Config::SetDefault("ns3::TcpL4Protocol::SocketType",StringValue ("ns3::TcpTahoe"));

如果用户想要一个指向套接字的指针以方便设置套接字的属性,那么可以通过Socket::CreateSocket()函数创建套接字,其返回值就是一个套接字指针,其参数必须是 ns3::SocketFactory 对象,因此要通过配置套接字来把属性和TcpL4Protocol对象相夫联,最简单的方法就是配置系统函数Config::Set(),大家来看下面的代码:

TypeId tid = TypeId::LookupByName ("ns3::TcpTahoe");
Config::Set("/NodeList/*/$ns3::TcpL4Protocol/SocketType",TypeIdValue
(tid));
Ptr<Socket> localSocket = Socket::CreateSocket (n0n1.Get (0),TcpSocketFactory::GetTypeId ());

首先创建了ns3::TcpTahoe的一个TypeId实例tid,然后通过函数Set()把变量SocketType赋值为tid(TcpTahoe),最后调用函数 Socket::CreateSocket创建套接字指针。

Set()函数第一个参数中通配符“*”意思是所有的,因此在后面将要为所有节点创建的套接字都会默认是Tahoe。

之后用Socket::CreateSocket为节点1创建socket。

2、网络设备模块

2.1、PointToPoint

ns-3的点到点通信模型是一个非常简单的点对点数据链路,通过点到点信道连接了整整2个点到点网络设备

(1)点到点网络设备

PointToPointNetDevice提供以下属性:

  • Address:设备的ns3::Mac48Address

  • DataRate:设备的数据传输速率(ns3::DataRate)

  • TxQueue:设备所使用的传输队列(ns3::Queue)

  • InterframeGap:可选ns3::Time来等待

  • RX:接收数据分组的跟踪源

  • Drop:丢弃数据分组的跟踪源

通过设置连接到 PointToPointChannel 的 sender 上的数据率可以模拟对称的信道;或通过设置不同的数据率可以模拟非对称通道(如ADSL)。

(2)点到点信道模型

点对点网络设备通过PointToPointChannel连接。这个信道模拟了以源网络设备指定数据率传输比特的两根线。没有超出每个发送数据分组的8 byte开销。 也就是说,没有模型化标志序列。 PointToPointChannel提供以下属性:

  • Delay:ns3::Time指定信道的光传输延迟速度。

(3)使用点到点网络设备

通常使用PointToPointHelper对象创建和配置点到点网络设备和信道。各种ns3设备助手一般以类似的方式工作,在我们的许多例子中可以看到。

概念化的模型是一个光秃秃的电脑“稻壳”,其中可以插入网络设备。使用NodeContainer助手创建光秃秃的电脑,也可以创造尽可能多的电脑(称其为节点)。

NodeContainer nodes;
nodes.Create (2);
PointToPointHelper pointToPoint;
pointToPoint.SetQueue ("ns3::DropTailQueue");
pointToPoint.SetDeviceAttribute("DataRate",StringValue("5Mbit/s"));
pointToPoint.SetChannelAttribute("Delay",StringValue("2ms"));

设置这些属性后,所有剩下的工作就是创建设备并安装在所需的节点上,使用点到点通道将设备连接在一起。当创建网络设备时,需要将它们添加到一个容器来允许你使用。这一切只需要一行代码即可。如下:

NetDeviceContainer devices = pointToPoint.Install(nodes);

(4)点到点跟踪

像所有ns-3设备一样,点至点模型提供了大量的跟踪源。这些跟踪源可以使用自己自定义的跟踪代码,或者使用Helper函数来安排跟踪您指定的设备。

  • 上层(MAC)钩子

网络上传输的数据分组注定通过网络设备单一的“发送队列”传送。我们提供数据分组流的跟踪挂钩对应从网络到数据链路层的传输,最后调用它们。这些钩子统称为设备的MAC钩。

当数据分组被发送到点至点网络设备时,它总是通过发送队列 PointToPointNet-Device 中的队列继承Queue,因此继承了3个跟踪源:

入队操作源:(见ns3::Queue::m_traceEnqueue);

出队操作源:(见ns3::Queue::m_traceDequeue);

放弃操作源:(见ns3::Queue::m_traceDrop)

当一个数据分组被放置在发送队列中,m_traceEnqueue事件被触发。这发生在 ns3:: PointtoPointNetDevice::Send 或 ns3:: PointToPointNetDevice:: SendFrom被一个更高的层调用且用于将传输数据分组排队。

当一个数据分组从发送队列中删除时,m_traceDequeue事件被触发。从发送队列中取出队列可能会发生2种情况:一是如果底层的通道是空闲且PointToPoint-NetDevice::Send被调用,一个数据分组从发送队列中出列并立即传送;二是数据分组可能会出列,并立即传送一个内部TransmitCompleteEvent,其职能与发送完成中断服务程序很像。

  • 较低级别(PHY)挂钩

类似上层的跟踪挂钩,也有可能在较低级别的网络设备使用跟踪挂钩,称为PHY 钩 。 这些事件由设备方法引发且直接与PointToPointChannel对话。

跟踪源m_dropTrace被调用去表示设备所丢弃的分组。当一个数据分组由于接收错误模型而被丢弃时,m_dropTrace就会被调用。

2.2、CSMA

在通常情况下,用户会想到总线网络以太网或IEEE 802.3。以太网采用CSMA/CD(载波侦听多路访问/碰撞检测),成倍地增加退避以争夺共享传输介质。

CSMA的ns-3设备模型化只是这个过程的一部分,利用全局可用信道的性质提供瞬时(比光更快)载波侦听和基于优先级的碰撞“回避”。在某种意义上,以太网的碰撞永远不会发生,所以 ns-3 的 CSMA 设备不会模型化碰撞检测,也没有任何正在进行中的传输将被“卡住”。

文献和教科书有一些公约用于描述分层通信体系结构。最常见的分层模型是ISO 七层参考模型。在该模型中,CsmaNetDevice 和 CsmaChannel占据最低两层的位置:物理(第一层)和数据链路层(第二层)。另一个重要的参考模型是指定的RFC1122,在该模型中, CsmaNetDevice和CsmaChannel占据最低层:链路层。还有一个在教科书和在文献中可以找到的另一种描述,采用在IEEE 802标准中命名约定的LLC、MAC、MII和PHY分层。这些缩写的定义如下:

  • LLC:逻辑链路控制

  • MAC:媒体访问控制

  • MLL:媒体独立接口

  • PHY:物理层

CSMA设备的“顶部”定义从网络层到数据链路层的转变。这种转变是由更高的层通过调用CsmaNetDevice::Send或CsmaNetDevice:: SendFrom实现的。

(1)CSMA信道模型

类CsmaChannel模拟实际的传输介质。连接到通道的设备数量是没有固定限制的。CsmaChannel 模拟数据传输速率和光速度的延迟通过 “数据速率”和“延迟”2种属性获取。

提供给信道的数据速率是用来设置连接到信道的CSMA设备中发射器所使用的数据速率,没有办法独立地设置设备中数据的传输速率。由于数据传输速率是仅用于计算的延迟时间,所以CSMA信道和设备可以操作的速度没有限制(除持有值的数据类型),基于任何PHY特性的操作都没有限制。

CsmaChannel 有 3 种状态:

  • IDLE(空闲)

  • TRANSMITTING(发送)

  • PROPAGATING(传播)。

这3种状态可以被信道上所有的设备瞬间“看到”。即如果一个设备开始或结束模拟传输,所有信道上的设备会立刻意识到状态的变化。因此,没有必要在CsmaChannel模型中进行碰撞检测。

正如其名称所示,模型有载波侦听功能。由于模拟器是单线程的,访问共同信道将被模拟器序列化。这提供了一个确定性的信道争用机制,该通道采用先到先得机制来分配(过渡从状态IDLE到状态 TRANSMITTING)。通道总是通过3种状态呈现:

IDLE -> TRANSMITTING -> PROPAGATING -> IDLE

TRANSMITTING为状态模拟源网络设备在电线上摆动信号的时间。PROPAGATING为状态模拟发送最后一位后的时间,此时信号传播到电线的“远端”。

过渡到发射状态是通过调用CsmaChannel:: TransmitStart实现的,网络设备发送的数据分组时可以调用 CsmaChannel:: TransmitStart。设备可以在恰当的时间调用CsmaChannel::TransmitEnd 来结束传播。

CsmaChannel 模型化广播媒体,所以在传播时间结束时数据分组被传递到通道上所有的设备(包括源)。发送设备可以确定它是否接收到该信道上广播发送的数据分组。

CsmaChannel提供以下属性

  • DataRate:连接设备上的数据分组传输比特率

  • Delay:信道中传输延迟的速度

(2)CSMA网络设备模型

CSMA网络设备有点像以太网设备。CsmaNetDevice提供以下属性:

  • Address:设备的Mac48Address

  • SendEnable:如果为true,允许数据分组传输

  • ReceiveEnable:如果为true,启用数据分组接收

  • EncapsulationMode:使用链路层封装类型

  • RxErrorModel:接收错误模型

  • TxQueue:发送设备所使用的队列

  • InterframeGap:“帧”之间可选的等待时间

  • RX:接收数据分组的跟踪源

  • Drop:丢弃数据分组的跟踪源

(3)使用CSMA网络设备模型

CSMA网络设备和信道通常是使用CsmaHelper对象被创建和配置。

NodeContainer csmaNodes;
csmaNodes.Create (nCsmaNodes);
CsmaHelper csma;
csma.SetChannelAttribute("DataRate",StringValue("100Mbit/s"));
csma.SetChannelAttribute("Delay",TimeValue(NanoSeconds (6560)));
csma.SetDeviceAttribute("EncapsulationMode",StringValue ("Dix"));
csma.SetDeviceAttribute("FrameSize",UintegerValue(2000));

一旦属性被设置,下一步需要创建设备并且将其安装在需要的节点上,使用CSMA信道将这些设备连接到一起。当创建网络设备时,把它们加到容器中以备后续使用,可以用如下代码实现:

NetDeviceContainer csmaDevices = csma.Install(csmaNodes);

2.3、Wi-Fi

ns-3的节点可以包含许多 NetDevice 对象,很像一个实际的计算机包含单独的接口卡、以太网、Wi-Fi、蓝牙等。

本节介绍了ns-3的 WifiNetDevice及相关模型,通过向 ns-3 节点加入WifiNetDevice对象,可以创建基于IEEE 802.11设施和ad hoc网络的模型。

  • WiFiNetDevice: 该类与node类类似,都不具备任何协议行为,该类是 Wi-Fi网络设备与上层协议通信的接口。可以将其看作网络层与 WifiMac之间的通信接口。

  • WifiMac: Wi-Fi协议的 MAC 层实现。支持功能包括关联(association)、探测(probe)、信标(beacon)、分组重传、RTS与CTS消息等。

  • RemoteStationManager:速率控制(rate control),根据网络环境选择最佳数据速率(data rate)。

  • WifiPhy: Wi-Fi协议的物理层实现。支持功能包括计算发射功率、传输延迟、接收功率阈值等。

  • Channel:模拟Wi-Fi信道,也属于物理层的一部分。结合传播(propagation)和移动(mobility)模型,Channel负责计算一个分组的接收功率、传播延迟等变量。

选取ns3的aodv.cc作为示例脚本

WifiHelper wifi;

// 设置两个助手类, 为MAC, PHY的安装提供帮助
WifiMacHelper wifiMac;
YansWifiPhyHelper wifiPhy = YansWifiPhyHelper::Default ();
// 为wifiPhy设置相关信道(yansWifiChannel)
YansWifiChannelHelper wifiChannel = YansWifiChannelHelper::Default ();
wifiPhy.SetChannel (wifiChannel.Create ());
  
Ssid ssid = Ssid ("wifi-default");
// 速率控制
wifi.SetRemoteStationManager ("ns3::ArfWifiManager");
// setup stas.
wifiMac.SetType ("ns3::StaWifiMac", "Ssid", SsidValue (ssid));          
staDevs = wifi.Install (wifiPhy, wifiMac, stas);
// setup ap.
wifiMac.SetType("ns3::AdhocWifiMac");
// wifi助手类提供将phy,mac相关参数绑定到节点上的函数
wifi.Install (wifiPhy, wifiMac, ap);

wifi的可用模式有AP,STA,ADHOC等, 可以用WiFiMacHelper的SetType()指定

mac.setType("ns3::StaWifiMac", ...);
mac.setType("ns3::ApWifiMac", ...);
mac.setType("ns3::AdhocWifiMac", ...);

3、应用层模块

Application 类是所有应用程序的基类,可以在src/network/model的application.h和application.cc中找到代码。

Application类提供了应用层程序的一些行为的基本原型,如 SetStartTime()、SetStopTime()、GetNode()、SetNode()等。

class Application : public Object {
public:
    static TypeId GetTypeId(void);
    Application();
    virtual ~Application();
    void SetStartTime(Time start);
    void SetStopTime(Time stop);
    Ptr<Node> GetNode() const;
    void SetNode(Ptr<Node> node);

private:
    virtual void StartApplication(void);
    virtual void StopApplication(void);

protected:
    virtual void DoDispose(void);
    virtual void DoStart(void);

    Ptr<Node> m_node;
    Time m_startTime;
    Time m_stopTime;
    EventId m_startEvent;
    EventId m_stopEvent;
};

ns-3作为一个完备和易用的仿真工具为用户提供了多种网络应用模型,如下:

  • UdpClientServer

  • UdpEcho

  • Radvd

  • Ping6

  • PacketSink

  • OnOffApplication

  • BulkSendApplication

用户可以根据自己的需求从上述应用中选择符合需求的一个在仿真程序中使用,下面将一一讲述。

3.1、UdpClientServer应用程序

通过英文名字大家就应该明白该应用层模块是基于UDP协议的应用程序,在该模块中一共有4个类需要学习,分别是SeqTsHeader类、UdpClient类、UdpServer类和UdpTraceClient类。

SeqTsHeader类定义了一些关于设置和获取UDP协议分组的头部函数,在仿真实验中一般采用默认的头部,因此这里不再细述,需要的用户可以参考 API,定义如下:

class SeqTsHeader : public Header
{
  public:
    SeqTsHeader();
    void SetSeq(uint32_t seq);
    uint32_t GetSeq(void) const;
    Time GetTs(void) const;
    static TypeId GetTypeId(void);
  …
  private:
  …
    uint32_t m_seq;
    uint64_t m_ts;
};

UdpServer 类作为一个定义服务器端的类,提供了一系列配置服务器端的函数,定义如下:

class UdpServer : public Application
{
  public:
    static TypeId GetTypeId(void);
    UdpServer();
    virtual ~UdpServer();
    //返回分组丢失量
    uint32_t GetLost(void) const;
    //返回接收分组的数量
    uint32_t GetReceived(void) const;
    //返回用于检测分组丢失量的窗口的大小
    uint16_t GetPacketWindowSize() const;
    //设置用于检测分组丢失量的窗口的大小,其数值必须为8的倍数
    void SetPacketWindowSize(uint16_t size); …private:
    …
};

UdpClient类是与UdpServer相对应的类,提供了在客户端所要使用的一些配置函数,定义如下:

class UdpClient : public Application
{
  public:
    static TypeId GetTypeId(void);
    UdpClient();
    virutal ~UdpClient();
    //set the remote address and port
    void SetRemote(Ipv4Address ip,uint16_t port);
    void SetRemote(Ipv6Address ip,uint16_t port);
    void SetRemote(Address ip,uint16_t port);
  …
  protect:
  …
  private:
  …
};

其中,重载函数SetRemote()的作用是将远端服务器的地址和端口告知客户端节点,即把服务器和客户端相关联,参数ip为远程服务器的IP地址,port为远程服务器端口。

UdpTraceClient类与UdpClient类似,不同点是UdpTraceClient类使用指定文件作为数据流,而UdpClient类是系统自动给出的数据流,定义如下:

class UdpTraceClient : public Application
{
  public:
    static TypeId GetTypeId(void);
    UdpTraceClient();
    UdpTraceClient(Ipv4Address ip,uint16_t port,char *traceFile);
    void SetRemote(Address ip,uint16_t port);
    void SetRemote(Ipv4Address ip,uint16_t port);
    void SetRemote(Ipv6Address ip,uint16_t port);
    …
};

现在大家基本上了解了UdpClientServer应用所涉及到的类,在实际仿真编写脚本时,并不是直接使用这些类,而是使用ns-3为方便用户给出的Helper类。与该应用对应的 Helper 类的相关文件和源代码可以在 ns-3.16/src/applications/helper中找到,分别为:udp-client-server-helper.hudp-client-server-helper.cc

下面通过一个具体脚本udp-client-serer.cc( 可以在ns3.16/examples文件夹中找到)来分析如何使用Helper类以及其成员函数:

uint16_t port = 4000;// 定义无符号的16 bit变量的端口号为4000
UdpServerHelper server (port);

ApplicationContainer apps = server.Install (n.Get (1));
apps.Start (Seconds (1.0));
apps.Stop (Seconds (10.0));

创建一个UdpServerHelper对象,这个对象以端口号4000为参数, 使用这个对象可以方便用户使用UdpClientServer应用程序来配置仿真程序。这个构造函数原型为:UdpServerHelper(uint16_t port)。

Install()函数的作用是为包含在节点容器(node container)中的每一个节点创建一个UdpClientServer应用程序,并返回一个应用程序容器。该函数的原型为 ApplicationContainer Install(NodeConstainer c)

start和stop为设置应用程序容器中的应用程序开始执行时间和结束时间。

上述几行代码是配置服务器端的应用程序代码。接下来是配置客户端应用程序的代码,使用的 Helper 为 UdpClientHelper。

uint32_t MaxPacketSize = 1024;
Time interPacketInterval = Seconds (0.05);
uint32_t maxPacketCount = 320;

UdpClientHelper client(serverAddress, port);

client.SetAttribute ("MaxPackets",UintegerValue(maxPacketCount));
client.SetAttribute ("Interval",TimeValue(interPacketInterval));
client.SetAttribute ("PacketSize",UintegerValue(MaxPacketSize));

apps = client.Install (n.Get (0));
apps.Start (Seconds (2.0));
apps.Stop (Seconds (10.0));

上述三行代码是定义配置客户端应用程序所需要的参数,依次为:分组的大小、发送分组间隔以及发送分组的最大数量。

创建一个UdpClientHelper对象,其中,serverAddress为服务器端的IP地址,port为端口。

之后的三行代码是设置应用程序的参数,从而在客户端创建具有该属性的应用程序。

为了方便大家进一步学习和参考,给出UdpClientHelper和UdpServerHelper类的定义,当然也可以去ns-3中自己查看。如下

class UdpServerHelper
{
  public:
    UdpServerHelper();
    UdpServerHelper(uint16_t port);
    void SetAttribute(std::string name,const AttributeValue& value);
    Application Install(NodeContainer c);
    Ptr<Udp> GetServer(void);
  private:
  …
};
class UdpClientHelper
{
  public:
    UdpClientHelper();
    UdpClientHelper(Ipv4Address ip,uint16_t port);
    UdpClientHelper(Ipv6Address ip,uint16_t port);
    UdpClientHelper(Address ip,uint16_t port);
    void SetAttribute(std::string name, const AttributeValue& value);
    Application Install(NodeContainer c);
  private:
  …
};

3.2、UdpEcho应用程序

Ehco应答协议主要用于调试和检测中。这个协议的作用十分简单,接收到什么原封发回就是了。

通过对UdpClientServer应用程序的学习,大家己经知道在使用应用程序时基本上不会用到UdpEchoClient和UdpEchoServer这2个类,所以在这里依然仅仅给出其定义和重要函数的注释。

class UdpEchoClient : public Application
{
  public:
    static TypeId GetTypeId(void);
    UdpEchoClient();
    virtual ~UdpEchoClient();
    void SetRemote(Ipv4Address ip,uint16_t port);
    void SetRemote(Ipv6Address ip,uint16_t port);
    void SetRemote(Address ip,uint16_t port);
    void SetDataSize(uint32_t dataSize);
    uint32_t GetDataSize() const;
    void SetFill(std::string fill);
    void SetFill(uint8_t fill,uint32_t dataSize);
    void SetFill(uint8_t fill,uint32_t fillSize,uint32_t dataSize);
  
  protected:
  …
  private:
  …
};

其中,重载函数SetRemote()的作用是将远端服务器的地址和端口告知客户端节点,即把服务器和客户端相联,参数ip为远程服务器的IP地址,port为远程服务器端口。

SetDataSize()函数的作用是设置所要发送分组的大小。

GetDataSize()函数的作用是返回要发送分组的大小。

SetFill()函数的作用是设置填充要发送数据分组的内容,参数 fill 是要填充的字符串,dataSize 为期望的最后分组的大小,fillSize为fill的大小。

class UdpEchoSever : public Application
{
  public:
    static TypeId GetTypeId();
    UdpEchoSever();
    virtual ~UdpEchoServer();protected:
  
  …
  private:
  …
};

由于Echo协议本身的特点,服务器端只需要返回从客户端收到的数据分组,服务器端不需要配置太多东西,因此大家可以看出其对应的类也非常简单。

同 样 , UdpEcho 应用程序也有其对应的helper类UdpEchoServerHelper和UdpEchoClientHelper。

在first.cc脚本中有如下几行代码,其中为了理解方便,在每一行代码后面都做出一一解释:

UdpEchoServerHelper echoServer (9)

ApplicationContainer serverApps = echoServer.Install(nodes.Get(1));

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

创建UdpEchoServerHelper(服务器端应用程序辅助类)对象,并 以9号端口为服务器端口。

为节点1创建服务器端 UdpEcho 应用程序并返回一个 ApplicationContainer(应用程序容器)类型对象,可以参考前面给出的类的定义。

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

上述三行代码是定义配置客户端应用程序所需要的参数,依次为:分组的大小、发送分组间隔以及发送分组的最大数量。

3.3、Ping6

Ping6 应用程序是基于 IPv6 的。因为 Ping6 应用程序的使用方法和函数与 UdpClientServer、UdpEcho 有很多相似之处,用户可以参考上文学习。

为了方便理解,也给出一个具体实例,为了简洁只是给出了使用到的Ping6Helper 的代码,ns-3.16/examples/ipv6/ping6.cc如下:

…
uint32_t packetSize = 1024;
uint32_t maxPacketCount = 5;
Time interPacketInterval = Seconds(1.);
//定义属性变量
Ping6Helper ping6;
ping6.SetIfIndex(i.GetInterfaceIndex(0));
ping6.SetRemote(Ipv6Address::GetAllNodesMulticast());

ping6.SetAttribute("MaxPackets",UintegerValue(maxPacketCount));
ping6.SetAttribute("Interval",TimeValue(interPacketInterval));
ping6.SetAttribute("PacketSize",UintegerValue(packetSize));
ApplicationContainer apps = ping6.Install(n.Get(0));
apps.Start(Seconds(2.0));
apps.Stop(Seconds(10.0));

因为这个脚本创建的4个节点在一个局域网内,i.GetInterfaceIndex(0)解释为获取 IP 地址容器中的第一个节点地址,SetIfIndex()函数的作用是将 i.GetInterfaceIndex(0) 获取的地址所在的节点作为 Ping 程序的运行主机,而 ping6.SetRemote()的作用是设置 Ping 命令的参数,即远程 PC,这里的远程PC为该局域网内的所有PC。因此使用Ipv6Address::GetAllNodesMulticast()获取所有主机的 IP。

3.4、PacketSink、OnOffApplication和BulkSentApplication

class PacketSink : public Application {
public:
    static TypeId GetTypeId();
    PacketSink();
    virtual ~PacketSink();
    uint32_t GetTotalRx() const;  // 返回该应用接收到的数据量
    Ptr<Socket> GetListeningSocket() const;  // 返回指向监听套接字的指针
    std::list<Ptr<Socket>> GetAcceptedSocket() const;

    // Additional members...
};

class PacketSinkHelper {
public:
    PacketSinkHelper(std::string protocol, Address address);
    void SetAttribute(std::string name, const Attribute& value);
    ApplicationContainer Install(NodeContainer c) const;
    ApplicationContainer Install(Ptr<Node> node) const;
    ApplicationContainer Install(std::string nodeName) const;

private:
    // Additional members...
};

class OnOffHelper {
public:
    OnOffHelper(std::string protocol, Address address);
    void SetAttribute(std::string name, const Attribute& value);
    void SetConstantRate(DataRate dataRate, uint32_t packetSize = 512);
    ApplicationContainer Install(NodeContainer c) const;
    ApplicationContainer Install(Ptr<Node> node) const;
    ApplicationContainer Install(std::string nodeName) const;

private:
    // Additional members...
};

class BulkHelper {
public:
    BulkHelper(std::string protocol, Address address);
    void SetAttribute(std::string name, const Attribute& value);
    ApplicationContainer Install(NodeContainer c) const;
    ApplicationContainer Install(Ptr<Node> node) const;
    ApplicationContainer Install(std::string nodeName) const;

private:
    // Additional members...
};

1. PacketSink

PacketSink 用于接收传入的数据包并计算接收到的总数据量。这个类通常用作网络传输模拟的目标端点,用来收集和统计传输性能的指标,例如吞吐量和延迟。PacketSink 可以设置为监听 TCP 或 UDP 端口,并统计接收到的所有数据。

它对应有一个帮助类:ns3::PacketSinkHelper.

PacketSinkHelper sinkHelper2 ("ns3::TcpSocketFactory", sinkLocalAddress2);
ApplicationContainer sinkApp2 = sinkHelper2.Install (serverCmts.Get (0));

2. OnOffApplication

OnOffApplication 是用来生成网络流量的应用类。根据开关模式(onoff pattern)向一个目的地发送流量。这个类按照指定的时间间隔(on-time 和 off-time)和数据速率(DataRate)发送数据。OnOffApplication 对于研究网络在不同流量负载下的表现非常有用,可以设置目标 IP 地址和端口以及其他参数,如包大小和发送间隔。

它对应有一个帮助类ns3::OnOffHelper,进一步简化了ns3::OnOffApplication的使用

  OnOffHelper clientHelper1 ("ns3::TcpSocketFactory", Address ());
  clientHelper1.SetAttribute ("OnTime", StringValue ("ns3::ConstantRandomVariable[Constant=1]"));
  clientHelper1.SetAttribute ("OffTime", StringValue ("ns3::ConstantRandomVariable[Constant=0]"));
  clientHelper1.SetAttribute ("PacketSize", UintegerValue (1000));

  // Connection two
  OnOffHelper clientHelper2 ("ns3::TcpSocketFactory", Address ());
  clientHelper2.SetAttribute ("OnTime", StringValue ("ns3::ConstantRandomVariable[Constant=1]"));
  clientHelper2.SetAttribute ("OffTime", StringValue ("ns3::ConstantRandomVariable[Constant=0]"));
  clientHelper2.SetAttribute ("PacketSize", UintegerValue (1000));

  clientHelper1.SetAttribute ("DataRate", DataRateValue (DataRate ("100Mb/s")));
  clientHelper2.SetAttribute ("DataRate", DataRateValue (DataRate ("100Mb/s")));

  ApplicationContainer clientApps1;
  AddressValue remoteAddress (InetSocketAddress (i3i4.GetAddress (1), port));
  clientHelper1.SetAttribute ("Remote", remoteAddress);
  clientApps1.Add (clientHelper1.Install (n0n2.Get (0)));

  clientApps1.Start (Seconds (client_start_time));
  clientApps1.Stop (Seconds (client_stop_time));

  ApplicationContainer clientApps2;
  clientHelper2.SetAttribute ("Remote", remoteAddress);
  clientApps2.Add (clientHelper2.Install (n1n2.Get (0)));

  clientApps2.Start (Seconds (client_start_time));
  clientApps2.Stop (Seconds (client_stop_time));

3. BulkSendApplication

发送尽可能多的流量,试图填补带宽。
流量产生器尽量以maxbytes发送数据,或者止到应用停止(如果maxbaytes=0).一旦底层发送缓存被填充满,则等待释放空间以发送更多的数据,本质上保证一个常数据流。只有SOCK_STREAM SOCK_SEQPACKET类型的sockets是支持的。例如,TCP socket可以使用,但是UDP sockets不能使用

它对应有一个帮助类:ns3::BulkSendHelper。

  BulkSendHelper sourceHelper ("ns3::TcpSocketFactory", Address ());
  sourceHelper.SetAttribute ("Remote", remoteAddress);
  sourceHelper.SetAttribute ("SendSize", UintegerValue (pktSize));
  sourceHelper.SetAttribute ("MaxBytes", UintegerValue (0));
  ApplicationContainer sourceApp = sourceHelper.Install (sender);
  sourceApp.Start (Seconds (0));
  sourceApp.Stop (Seconds (stopTime - 3));

4、移动模块

在ns-3中对移动的模块一直包括以下几个部分。

  • ns-3提供一系列的移动模型供用户选择使用

  • 可以为移动模型提供Trace Source,供用户追踪节点的动态

  • 提供了大量的 Helper 类以便安排节点的初始分布和节点的移动方式(节点移动方向、移动速度)

4.1、移动模型简述

针对移动模型的设计,ns-3主要提供了3点:移动模型、初始位置分布和辅助Helper类。

在ns-3中,为可移动节点设计的网络模型都是基于坐标机制的, 在仿真中移动模型会集成到可移动的节点中,可以使用 GetObject< MobilityModel>() 函数从己绑定移动模型的节点中提取移动模型。 ns-3提供的大量不同的移动模型供不同用户使用,它们都是继承自 ns3::MobilityModel 类。

移动节点初始位置的分布是由类 PositionAllocator 负责,每个 节点的初始位置在仿真进行前就由该类指定完毕,在仿真进行中不会 再使用到该类或者在特定的移动模型中用于选择下一位置。

ns-3提供的Helper类有助于用户方便地使用ns-3提供的服务,同样地,移动模型也有与之对应的Helper类:MobileHelper,该类把 MobilityModel PositionAllocator 整合到一起,从而方便为 Node 节点安装移动模型。

ns-3提供了类 ns3::Vector 作为坐标系统的基类。节点不光有位置的变化还有移动速率的变化,那么如何简单方便地将二者统一起来, 这里针对位置用坐标形式(x,y,z),针对速度使用单位矢量坐标标识形式:

这样就可以用一种数据结构来描述节点的移动,但是还有一部分数据形式是无法用上述形式表示的,为了尽量满足大多数用户对移动模型的要求,ns-3 还提供了 Rectangle、Box 和Waypoint作为补充。

4.2、MobilityModel

作为所有移动模型的基类,大家有必要了解一下该基类中实现了哪些必要的功能,该类定义在src/mobility/model/mobility-model.h 中,这里只针对一些公共函数API给出功能性解释:

class MobilityModel : public Object
{
  public:
    static TypeId GetTypeId (void);
    MobilityModel ();
    virtual ~MobilityModel () = 0;
    
    // 返回当前节点的位置
    Vector GetPosition (void) const;

    // 设置节点的位置
    void SetPosition (const Vector&;position);

    // 返回当前节点的移动速度
    Vector GetVelocity (void) const;  

    // 参数position为另一个节点的位置
    // 返回该节点和另一个节点间的距离
    double GetDistanceFrom (Ptr<const MobilityModel> position) const;

    // 参数other为另一个节点的模型
    // 返回2个节点的相对移动速度
    double GetRelativeSpeed (Ptr<const MobilityModel> other) const;

    //在一些模型中可能用到随机数,比如随机位置或者随机移动速度等,因此提供这么一个函数为移动模型提供随机数。
    int64_t AssignStreams (int64_t stream);

  protected:
    void NotifyCourseChange (void) const;

  private:
    virtual Vector DoGetPosition (void) const = 0;
    virtual void DoSetPosition (const Vector&;position) = 0;
    virtual Vector DoGetVelocity (void) const = 0;
    virtual int64_t DoAssignStreams (int64_t start);
    TracedCallback<Ptr<const MobilityModel>> m_courseChangeTrace;
};

4.3、各类移动模型 MobilityModel

前面提到ns-3提供多种移动模型供用户选择使用,主要有:ConstantPosition(固定位置模型)、ConstantVelocity(固定移动速度)、RandomWayPoint(随机路径)、RandomWalk2D(随机游走)、RandomDirection2D(随机方向)、 Waypoint、ConstantAcceleration、SteadyStateRandomWaypoint、GaussMarkov 和Hierarchical。

这里通过几个例子讲述如何使用这些模型,首先看第一个例子, 也是大家前面学习过的例子—third.cc:

MobilityHelper mobility;

mobility.SetPositionAllocator("ns3::GridPositionAllocator",
                                  "MinX",
                                  DoubleValue(0.0),
                                  "MinY",
                                  DoubleValue(0.0),
                                  "DeltaX",
                                  DoubleValue(5.0),
                                  "DeltaY",
                                  DoubleValue(10.0),
                                  "GridWidth",
                                  UintegerValue(3),
                                  "LayoutType",
                                  StringValue("RowFirst"));

mobility.SetMobilityModel("ns3::RandomWalk2dMobilityModel",
                              "Bounds",
                              RectangleValue(Rectangle(-50, 50, -50, 50)));
mobility.Install(wifiStaNodes);

    mobility.SetMobilityModel("ns3::ConstantPositionMobilityModel");
 
mobility.Install(wifiApNode);

代码分析:首先使用 MobilityHelper 类对象 mobility,然后通过调用SetPositionAllocation()函数初始化节点的初始位置,其原型为

void SetPositionAllocator(
    std::string type,
    std::string n1 = "", const AttributeValue& v1 = EmptyAttributeValue(),
    std::string n2 = "", const AttributeValue& v2 = EmptyAttributeValue(),
    std::string n3 = "", const AttributeValue& v3 = EmptyAttributeValue(),
    std::string n4 = "", const AttributeValue& v4 = EmptyAttributeValue(),
    std::string n5 = "", const AttributeValue& v5 = EmptyAttributeValue(),
    std::string n6 = "", const AttributeValue& v6 = EmptyAttributeValue(),
    std::string n7 = "", const AttributeValue& v7 = EmptyAttributeValue(),
    std::string n8 = "", const AttributeValue& v8 = EmptyAttributeValue(),
    std::string n9 = "", const AttributeValue& v9 = EmptyAttributeValue()
);

参数type为要初始化每个节点初始位置的位置模型,参数n1~n9为要赋值的变量,v1~v9 就是实参。

接着在后面调用 SetMobilityModel() 函数设置要使用的移动模型,第 1个参数为要使用的移动模型(RandomWalk2dMobility-Model),第2参数为限制节点移动的边界,第3个参数为边界的实参。

下面给出函数原型:

void SetMobilityModel(
    std::string type,
    std::string n1 = "", const AttributeValue& v1 = EmptyAttributeValue(),
    std::string n2 = "", const AttributeValue& v2 = EmptyAttributeValue(),
    std::string n3 = "", const AttributeValue& v3 = EmptyAttributeValue(),
    std::string n4 = "", const AttributeValue& v4 = EmptyAttributeValue(),
    std::string n5 = "", const AttributeValue& v5 = EmptyAttributeValue(),
    std::string n6 = "", const AttributeValue& v6 = EmptyAttributeValue(),
    std::string n7 = "", const AttributeValue& v7 = EmptyAttributeValue(),
    std::string n8 = "", const AttributeValue& v8 = EmptyAttributeValue(),
    std::string n9 = "", const AttributeValue& v9 = EmptyAttributeValue()
);

准备好了所需要的配置工作,然后就调用Install()函数将这些准备好的移动模型安装到每一个节点上。

4.4、PositionAllocator

从上面大家了解了在每次为节点安装移动模型前都要为每一个节 点的初始位置给出相应的方案,ns-3中提供了多种方案,对象的类如下:

  • ListPositionAllocator:用户根据自身要求自定义节点的初始位置列表

  • GridPositionAllocator:根据表格形式为节点初始化位置,当然表格的长度、宽度都可以根据用户要求自行分配

  • RandomRectanglePositionAllocator:给出一个随机矩形来初始化节点位置

  • RandomBoxPositionAllocator:给出一个随机立体矩形分配方案

5、能量模块 // TODO

6、添加模块 // TODO