原创 首页 科研

NS-3 网络仿真:从零搭建到三个样例

LinJHS 3

前言

NS-3 是一个用于 Internet 系统的离散事件网络模拟器,主要用于研究和教育用途

NS-3 是一个免费的开源软件,在 GNU GPL v2 许可下授权,并由全球社区维护

安装

依赖:

sudo apt install g++ python3 cmake ninja-build git
sudo apt install ccache
sudo apt install python3-pip
python3 -m pip install --user cppyy
sudo apt install gir1.2-goocanvas-2.0 python3-gi python3-gi-cairo python3-pygraphviz gir1.2-gtk-3.0 ipython3
sudo apt install python3-setuptools git
sudo apt install qtbase5-dev qtchooser qt5-qmake qtbase5-dev-tools
sudo apt install openmpi-bin openmpi-common openmpi-doc libopenmpi-dev
sudo apt install mercurial unzip
sudo apt install gdb valgrind 
sudo apt install clang-format
sudo apt install doxygen graphviz imagemagick
sudo apt install texlive texlive-extra-utils texlive-latex-extra texlive-font-utils dvipng latexmk
sudo apt install python3-sphinx dia
sudo apt install gsl-bin libgsl-dev libgslcblas0
sudo apt install tcpdump
sudo apt install sqlite sqlite3 libsqlite3-dev
sudo apt install libxml2 libxml2-dev
sudo apt install libgtk-3-dev
sudo apt install vtun lxc uml-utilities
sudo apt install libxml2 libxml2-dev libboost-all-dev

安装 NS-3:

  # 下载
mkdir repos
cd repos
git clone https://gitlab.com/nsnam/ns-3-allinone.git # 下载安装脚本
cd ns-3-allinone
python3 download.py -n ns-3.40

  # 构建
./build.py --enable-examples --enable-tests

  # 测试模块
cd ns-3.40
./test.py

运行

构建:

  # 进入 ~/repos/ns-3-allinone/ns-3.40
cp examples/tutorial/first.cc scratch/myfirst.cc
./ns3 build

运行:

./ns3 run scratch/myfirst

样例

1. p2p

// SPDX-License-Identifier: GPL-2.0-only

#include "ns3/applications-module.h"
#include "ns3/core-module.h"
#include "ns3/internet-module.h"
#include "ns3/network-module.h"
#include "ns3/point-to-point-module.h"

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

using namespace ns3;

/**
 * https://www.nsnam.org/docs/release/3.40/doxygen/d8/dff/group__logging.html#ga225a95395fa117b7309aa3c43518d02e
 *
 * 这个宏定义了一个新的“日志组件”
 */
NS_LOG_COMPONENT_DEFINE("FirstScriptExample");

int
main(int argc, char* argv[])
{
    CommandLine cmd(__FILE__);
    cmd.Parse(argc, argv);

    // 设置时间分辨率为 1ns(默认也为 1 纳秒)
    Time::SetResolution(Time::NS);
    // 启用两个日志记录组件,内置在 Echo 客户端和 Echo 服务器应用程序中
    LogComponentEnable("UdpEchoClientApplication", LOG_LEVEL_INFO);
    LogComponentEnable("UdpEchoServerApplication", LOG_LEVEL_INFO);

    /**
     * https://www.nsnam.org/docs/release/3.40/doxygen/d7/db2/classns3_1_1_node_container.html
     *
     * 用于跟踪一组的节点指针
     */
    NodeContainer nodes;
    // 让容器创建两个节点
    nodes.Create(2);

    /**
     * https://www.nsnam.org/docs/release/3.40/doxygen/d3/d84/classns3_1_1_point_to_point_helper.html
     *
     * 相当于一个拓扑帮助器,用于配置和连接 以太网设备 与 无线信道
     * 生成一组 PointToPointNetDevice 对象
     */
    PointToPointHelper pointToPoint;
    // 设置信息传输速率和延迟
    pointToPoint.SetDeviceAttribute("DataRate", StringValue("5Mbps"));
    pointToPoint.SetChannelAttribute("Delay", StringValue("2ms"));

    /**
     * https://www.nsnam.org/docs/release/3.40/doxygen/d3/d84/classns3_1_1_point_to_point_helper.html#ab9162fea3e88722666fed1106df1f9ec
     *
     * 此方法(Install)创建一个 无线信道,并对于输入容器中的每个节点创建一个 以太网设备
     * 并将生成的这个 以太网设备 的队列与 无线信道 相关联
     */
    NetDeviceContainer devices;
    devices = pointToPoint.Install(nodes);

    // 接下来是配置协议栈
    InternetStackHelper stack;
    stack.Install(nodes);

    /**
     * https://www.nsnam.org/docs/release/3.40/doxygen/d5/d4f/classns3_1_1_ipv4_address_helper.html
     *
     * 声明一个地址助手,进行简单的 IPv4 地址分配
     * 参数分别为
     * - 初始网络号
     * - 掩码
     * - (可选)起始的 IP 地址
     *
     * 下面的 IP 地址将从 10.1.1.1 开始分配
     */
    Ipv4AddressHelper address;
    address.SetBase("10.1.1.0", "255.255.255.0");

    // 使用 Ipv4Interface 将 IP 地址与设备进行关联
    Ipv4InterfaceContainer interfaces = address.Assign(devices);

    /**
     * https://www.nsnam.org/docs/release/3.40/doxygen/d6/d64/classns3_1_1_udp_echo_server_helper.html
     *
     * 创建一个等待输入 UDP 数据包并将其发送回原始发送方的服务器应用程序
     * 设置端口为 9
     */
    UdpEchoServerHelper echoServer(9);

    // 在指定节点上创建一个 UdpEchoServerApplication
    ApplicationContainer serverApps = echoServer.Install(nodes.Get(1));
    // 指定生成流量的开始时间以及结束时间
    serverApps.Start(Seconds(1.0));
    serverApps.Stop(Seconds(10.0));

    /**
     * https://www.nsnam.org/docs/release/3.40/doxygen/d5/d2f/classns3_1_1_udp_echo_client_helper.html
     *
     * 创建一个应用程序,它发送UDP数据包并等待此数据包的回显
     * echoClinet 的参数:
     * - 远程 UDP 回送服务器的 IP 地址
     * - 远程 UDP 回送服务器的端口号
     */
    UdpEchoClientHelper echoClient(interfaces.GetAddress(1), 9);
    // 允许它在模拟过程中最多能发送的数据包
    echoClient.SetAttribute("MaxPackets", UintegerValue(1));
    // 在数据包之间等待多长时间
    echoClient.SetAttribute("Interval", TimeValue(Seconds(1.0)));
    // 数据包有效载荷大小(单位字节)
    echoClient.SetAttribute("PacketSize", UintegerValue(1024));

    // 在指定节点上创建一个 UdpEchoClientApplication
    ApplicationContainer clientApps = echoClient.Install(nodes.Get(0));
    // 指定生成流量的开始时间以及结束时间
    clientApps.Start(Seconds(2.0));
    clientApps.Stop(Seconds(10.0));

    // 运行模拟 以及 销毁和清理
    // 当然,也可以手动指定停止时间,而不是等待事件队列自动耗尽
    // Simulator::Stop(Seconds(11.0));
    Simulator::Run();
    Simulator::Destroy();
    return 0;
}

2. p2p + CSMA

// SPDX-License-Identifier: GPL-2.0-only

#include "ns3/applications-module.h"
#include "ns3/core-module.h"
#include "ns3/csma-module.h"
#include "ns3/internet-module.h"
#include "ns3/ipv4-global-routing-helper.h"
#include "ns3/network-module.h"
#include "ns3/point-to-point-module.h"

// Default Network Topology
//
//       10.1.1.0
// n0 -------------- n1   n2   n3   n4
//    point-to-point  |    |    |    |
//                    ================
//                      LAN 10.1.2.0

using namespace ns3;

NS_LOG_COMPONENT_DEFINE("SecondScriptExample");

int
main(int argc, char* argv[])
{
    bool verbose = true;
    uint32_t nCsma = 3;

    CommandLine cmd(__FILE__);
    cmd.AddValue("nCsma", "Number of \"extra\" CSMA nodes/devices", nCsma);
    cmd.AddValue("verbose", "Tell echo applications to log if true", verbose);

    cmd.Parse(argc, argv);

    if (verbose)
    {
        LogComponentEnable("UdpEchoClientApplication", LOG_LEVEL_INFO);
        LogComponentEnable("UdpEchoServerApplication", LOG_LEVEL_INFO);
    }

    nCsma = nCsma == 0 ? 1 : nCsma;

    NodeContainer p2pNodes;
    p2pNodes.Create(2);

    /**
     * CSMA:载波侦听多路访问
     * - 载波侦听(Carrier Sense)
     *   指任何连接到介质的设备在欲发送帧前,必须对介质进行侦听,当确认其空闲时,才可以发送。
     * - 多路访问(Multiple Access)
     *   指多个设备可以同时访问介质,一个设备发送的帧也可以被多个设备接收。
     *
     * 注:真实的以太网使用 CSMA/CD(带有冲突检测的载波侦听多路存取)方案与指数退避方法共享传输介质
     */
    NodeContainer csmaNodes;
    // 创建一个 LAN,p2p 的一个节点作为必要节点,除此之外还有三个额外节点(nCsma==3)
    csmaNodes.Add(p2pNodes.Get(1));
    csmaNodes.Create(nCsma);

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

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

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

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

    // 配置协议注意:p2p 的 1 号节点在 CSMA 网络里面,不要重复安装协议
    InternetStackHelper stack;
    stack.Install(p2pNodes.Get(0));
    stack.Install(csmaNodes);

    // 这个是配置 p2p 的 IP 地址
    Ipv4AddressHelper address;
    address.SetBase("10.1.1.0", "255.255.255.0");
    Ipv4InterfaceContainer p2pInterfaces;
    p2pInterfaces = address.Assign(p2pDevices);

    // 这个是配置 CSMA 的 IP 地址
    address.SetBase("10.1.2.0", "255.255.255.0");
    Ipv4InterfaceContainer csmaInterfaces;
    csmaInterfaces = address.Assign(csmaDevices);

    // 设置 echo 服务器
    UdpEchoServerHelper echoServer(9);

    // 安装在 CSMA 最后一个节点上
    ApplicationContainer serverApps = echoServer.Install(csmaNodes.Get(nCsma));
    serverApps.Start(Seconds(1.0));
    serverApps.Stop(Seconds(10.0));

    // 设置 echo 客户端
    UdpEchoClientHelper echoClient(csmaInterfaces.GetAddress(nCsma), 9);
    echoClient.SetAttribute("MaxPackets", UintegerValue(1));
    echoClient.SetAttribute("Interval", TimeValue(Seconds(1.0)));
    echoClient.SetAttribute("PacketSize", UintegerValue(1024));

    // 安装在 p2p 第一个节点上
    ApplicationContainer clientApps = echoClient.Install(p2pNodes.Get(0));
    clientApps.Start(Seconds(2.0));
    clientApps.Stop(Seconds(10.0));

    // 基本上,每个节点的行为就像是一个 OSPF(开放式最短路径优先)路由器,下面是进行设置
    Ipv4GlobalRoutingHelper::PopulateRoutingTables();

    // 启用 pcap 跟踪
    pointToPoint.EnablePcapAll("second");
    // 最后一个参数是启用混杂模式,可以嗅探整个网络所有的数据包(而不是单纯本网络设备发出或接收的)
    // 注意,CSMA 的第二个网络并不参与直接的数据包传输,但是他可以嗅探到
    csma.EnablePcap("second", csmaDevices.Get(1), true);

    Simulator::Run();
    Simulator::Destroy();
    return 0;
}

3. p2p + CSMA + WiFi

// SPDX-License-Identifier: GPL-2.0-only

#include "ns3/applications-module.h"
#include "ns3/core-module.h"
#include "ns3/csma-module.h"
#include "ns3/internet-module.h"
#include "ns3/mobility-module.h"
#include "ns3/network-module.h"
#include "ns3/point-to-point-module.h"
#include "ns3/ssid.h"
#include "ns3/yans-wifi-helper.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

using namespace ns3;

NS_LOG_COMPONENT_DEFINE("ThirdScriptExample");

// 这个函数将输出移动节点的坐标,我们自己的追踪源
void
CourseChange(std::string context, Ptr<const MobilityModel> model)
{
    Vector position = model->GetPosition();
    NS_LOG_UNCOND(context << " x = " << position.x << ", y = " << position.y);
}

int
main(int argc, char* argv[])
{
    // 是否开启日志
    bool verbose = true;
    // CSMA 额外节点个数
    uint32_t nCsma = 3;
    // WiFi 额外节点个数
    uint32_t nWifi = 3;
    // 是否开启 pcap 追踪
    bool tracing = false;

    CommandLine cmd(__FILE__);
    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.
    // 18 个节点的基本限制是由于网格位置分配器的配置造成的;
    // 如果提供的节点超过 18 个,网格布局就会超出边界框。
    if (nWifi > 18)
    {
        std::cout << "nWifi should be 18 or less; otherwise grid layout exceeds the bounding box"
                  << std::endl;
        return 1;
    }

    if (verbose)
    {
        LogComponentEnable("UdpEchoClientApplication", LOG_LEVEL_INFO);
        LogComponentEnable("UdpEchoServerApplication", LOG_LEVEL_INFO);
    }

    // 创建两个 p2p 节点
    NodeContainer p2pNodes;
    p2pNodes.Create(2);

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

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

    // 其中一个 p2p 加入了 CSMA,并创建了 nCsma 个 CSMA 节点
    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);

    // 创建 nWifi 个 WiFi 节点,并将另一个 p2p 节点设置为 WiFi 的 AP(无线接入点)
    NodeContainer wifiStaNodes;
    wifiStaNodes.Create(nWifi);
    NodeContainer wifiApNode = p2pNodes.Get(0);

    /**
     * 构建 WiFi 设备之间互联的信道(使用默认的 PHY 层配置)
     * 由这个创建的所有 PHY 层对象共享相同的底层通道
     */
    YansWifiChannelHelper channel = YansWifiChannelHelper::Default();
    YansWifiPhyHelper phy;
    phy.SetChannel(channel.Create());

    // 接下来是 MAC 层
    WifiMacHelper mac;
    // 创建 802.11 服务集标识符(SSID)对象
    Ssid ssid = Ssid("ns-3-ssid");

    /**
     * 默认情况下,WifiHelper 使用的标准是 802.11ax(商业上称为 Wi-Fi 6)
     * 并且配置了兼容的速率控制算法
     */
    WifiHelper wifi;

    // 使用 NetDevice 容器来存储这些 WifiNetDevice 对象
    NetDeviceContainer staDevices;
    /**
     * 当标准新于 802.11n 时,QosSupported 默认为 true
     * - 这两种配置的结合意味着,下一个创建的 MAC 实例将是
     *   一个具有 QoS 感知的非 AP 站(STA),并且在 infrastructure BSS(即带有 AP 的 BSS)中。
     *   注:
     *   - BSS 基本服务集(Basic Service Set),BSS=STA+AP+传输媒介。
     *   - STA 工作站(Station)
     * - ActiveProbing 设置为 false,这意味着由该辅助程序创建的 MAC 将不会发送探测请求,站点将侦听
     * AP 信标。
     */
    //
    mac.SetType("ns3::StaWifiMac", "Ssid", SsidValue(ssid), "ActiveProbing", BooleanValue(false));
    staDevices = wifi.Install(phy, mac, wifiStaNodes);

    NetDeviceContainer apDevices;
    mac.SetType("ns3::ApWifiMac", "Ssid", SsidValue(ssid));
    apDevices = wifi.Install(phy, mac, wifiApNode);

    /**
     * 接着,我们想让 STA 节点是移动的,在一个边界框内游荡;同时 AP 是静止的
     */
    MobilityHelper mobility;

    /**
     * https://www.nsnam.org/docs/release/3.40/doxygen/de/d2e/classns3_1_1_grid_position_allocator.html
     *
     * 在矩形二维网格上分配位置
     * - MinX 布局开始的 X 坐标
     * - MinY 布局开始的 Y 坐标
     * - DeltaX 两个 X 连续位置之间的 X 间隔
     * - DeltaY 两个 Y 连续位置之间的 Y 间隔
     * - GridWidth 在一条线上布局的对象数
     * - LayoutType 布局类型(RowFirst|ColumnFirst)
     */
    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"));

    /**
     * https://www.nsnam.org/docs/release/3.40/doxygen/d0/d59/classns3_1_1_random_walk2d_mobility_model.html
     *
     * 二维随机游走模型
     * - Bounds 设置了边界
     */
    mobility.SetMobilityModel("ns3::RandomWalk2dMobilityModel",
                              "Bounds",
                              RectangleValue(Rectangle(-50, 50, -50, 50)));
    mobility.Install(wifiStaNodes);

    // AP 是固定位置的
    mobility.SetMobilityModel("ns3::ConstantPositionMobilityModel");
    mobility.Install(wifiApNode);

    // 安装协议栈
    InternetStackHelper stack;
    stack.Install(csmaNodes);
    stack.Install(wifiApNode);
    stack.Install(wifiStaNodes);

    // 分配 IP 地址
    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);

    // 设置 echo 服务器,安装在 CSMA 最后一个节点上
    UdpEchoServerHelper echoServer(9);

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

    // 设置 echo 客户端,安装在 WiFi STA 最后一个节点上
    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));

    // 由于构建了一个互联网络,因此需要启用互联网络路由
    Ipv4GlobalRoutingHelper::PopulateRoutingTables();

    // 但由于我们要求 AP 生成信标(beacons),他会无限期生成下去,所以我们要告诉模拟器停止的时间
    Simulator::Stop(Seconds(10.0));

    if (tracing)
    {
        // 在两个 p2p 节点上开启混杂(监视)模式
        phy.SetPcapDataLinkType(WifiPhyHelper::DLT_IEEE802_11_RADIO);
        pointToPoint.EnablePcapAll("third");
        phy.EnablePcap("third", apDevices.Get(0));
        csma.EnablePcap("third", csmaDevices.Get(0), true);
    }

    /**
     * 下面这部分,将用于输出移动设备的坐标
     * 我们跟踪 WiFi 最后一个节点的坐标
     * 我们将该节点的追踪源和我们的追踪源联系起来,以便于我们能够打印它的位置
     */
    std::ostringstream oss;
    oss << "/NodeList/" << wifiStaNodes.Get(nWifi - 1)->GetId()
        << "/$ns3::MobilityModel/CourseChange";

    Config::Connect(oss.str(), MakeCallback(&CourseChange));

    // 运行
    Simulator::Run();
    Simulator::Destroy();
    return 0;
}

参考资料:

  1. 【ns-3】零基础安装教程 https://blog.csdn.net/Graduate2015/article/details/129027889

分享
如果这篇文章帮到你,可以请我喝一杯茶。

微信扫码打开

请我喝杯茶

如果这篇文章帮到你,可以请我喝一杯茶。

微信收款码
在商城请我一杯