In this guide, we'll walk through a complete mini-project: creating a simple Wi-Fi network from scratch, generating traffic, and measuring the actual throughput. By the end, you'll not only have a working simulation but also a powerful template for your own network experiments. 🔬
Our goal is simple but practical:
-
Build a Wi-Fi network with one Access Point (AP) and three user devices (stations).
-
Send data from one station to the AP.
-
Measure the throughput in real-time to see how well our network is performing.
-
Run an experiment by changing the Wi-Fi standard to see how it impacts performance.
Step 1: The Basic Setup
Every ns-3 script starts with the essentials. We create a new file, scratch/wifi-throughput-project.cc, and lay the foundation. This involves including the necessary modules and creating our nodes and the wireless channel they will communicate over.
#include "ns3/core-module.h"
#include "ns3/network-module.h"
#include "ns3/internet-module.h"
#include "ns3/mobility-module.h"
#include "ns3/wifi-module.h"
#include "ns3/applications-module.h"
using namespace ns3;
int main (int argc, char *argv[])
{
// Create one AP node and three station (STA) nodes
NodeContainer wifiApNode;
wifiApNode.Create(1);
NodeContainer wifiStaNodes;
wifiStaNodes.Create(3);
// Create the wireless channel and the physical layer (PHY)
YansWifiChannelHelper channel = YansWifiChannelHelper::Default();
YansWifiPhyHelper phy;
phy.SetChannel(channel.Create());
// Create Wi-Fi devices for the stations and the AP
WifiMacHelper mac;
WifiHelper wifi;
wifi.SetStandard(WIFI_STANDARD_802_11n); // We'll experiment with this later!
mac.SetType("ns3::StaWifiMac");
NetDeviceContainer staDevices = wifi.Install(phy, mac, wifiStaNodes);
mac.SetType("ns3::ApWifiMac");
NetDeviceContainer apDevice = wifi.Install(phy, mac, wifiApNode);
// ... more to come ...
}
Step 2: Placing Your Nodes
Even if our devices are stationary, ns-3 needs to know where they are in the simulated world. We use the MobilityHelperto place the AP at the center and arrange the three stations around it.
MobilityHelper mobility;
Ptr<ListPositionAllocator> positionAlloc = CreateObject<ListPositionAllocator>();
positionAlloc->Add(Vector(0.0, 0.0, 0.0)); // AP at (0,0,0)
positionAlloc->Add(Vector(5.0, 0.0, 0.0)); // STA 1
positionAlloc->Add(Vector(0.0, 5.0, 0.0)); // STA 2
positionAlloc->Add(Vector(-5.0, 0.0, 0.0)); // STA 3
mobility.SetPositionAllocator(positionAlloc);
mobility.SetMobilityModel("ns3::ConstantPositionMobilityModel");
mobility.Install(wifiApNode);
mobility.Install(wifiStaNodes);
Step 3: Generating Traffic 🌐
A network isn't much fun without data flowing through it. We install the TCP/IP internet stack on all our nodes and assign them IP addresses.
Then, we set up two applications:
-
A
PacketSinkon the AP. Its only job is to receive and consume any traffic sent to it on port 9. -
An
OnOffApplicationon the first station. This app will generate a constant stream of data and send it to the AP's IP address.
// Install TCP/IP stacks and assign IP addresses
InternetStackHelper stack;
stack.Install(wifiApNode);
stack.Install(wifiStaNodes);
Ipv4AddressHelper address;
address.SetBase("10.1.1.0", "255.255.255.0");
Ipv4InterfaceContainer staInterfaces = address.Assign(staDevices);
Ipv4InterfaceContainer apInterface = address.Assign(apDevice);
// Install a sink on the AP to receive traffic
uint16_t port = 9;
PacketSinkHelper sinkHelper("ns3::Udp", InetSocketAddress(Ipv4Address::GetAny(), port));
ApplicationContainer sinkApp = sinkHelper.Install(wifiApNode.Get(0));
sinkApp.Start(Seconds(0.0));
// Install a traffic generator on the first station to send to the AP
OnOffHelper onoff("ns3::UdpSocketFactory", InetSocketAddress(apInterface.GetAddress(0), port));
onoff.SetConstantRate(DataRate("54Mbps"));
ApplicationContainer sourceApp = onoff.Install(wifiStaNodes.Get(0));
sourceApp.Start(Seconds(1.0));
Step 4: Tracing for Data Collection
This is the most important part. How do we actually measure throughput? We use ns-3's powerful tracing system. We "hook" into the simulation's events to collect data.
First, we define a function, RxTrace, that will be called every single time our sink application on the AP receives a packet. This function simply adds the packet's size to a global counter.
Next, we create a function, ShowThroughput, that calculates the throughput in Mbps based on the bytes we've counted, prints it to the screen, and then—crucially—schedules itself to run again in one second.
Finally, we connect our RxTrace function to the sink's "Rx" trace source and schedule the first call to ShowThroughput.
// Global variable and functions defined at the top of the file
uint64_t totalBytesReceived = 0;
static void RxTrace(Ptr<const Packet> packet, const Address &from) {
totalBytesReceived += packet->GetSize();
}
void ShowThroughput() {
double throughput = (totalBytesReceived * 8.0) / 1000000; // Mbps
std::cout << Simulator::Now().GetSeconds() << "s, Throughput: " << throughput << " Mbps" << std::endl;
totalBytesReceived = 0;
Simulator::Schedule(Seconds(1.0), &ShowThroughput);
}
// Code added inside main() before Simulator::Run()
sinkApp.Get(0)->TraceConnectWithoutContext("Rx", MakeCallback(&RxTrace));
Simulator::Schedule(Seconds(1.0), &ShowThroughput);
Step 5: The Experiment - Let's See the Results!
With the script complete, we run it from the terminal: ./ns3 run scratch/wifi-throughput-project
The output is a clean, second-by-second report of our network's performance!
1s, Throughput: 23.4528 Mbps
2s, Throughput: 24.1192 Mbps
3s, Throughput: 24.0800 Mbps
...
Now for the fun part. We go back into our code, change one line to upgrade our network's hardware...
// From...
wifi.SetStandard(WIFI_STANDARD_802_11n);
// To...
wifi.SetStandard(WIFI_STANDARD_802_11ac);
...and run it again. The new throughput numbers will be significantly higher, instantly demonstrating the power of simulation for comparative analysis.
Lessons from Debugging
No project is complete without a few bugs! We ran into a classic typo that's a great lesson for any C++ developer. Our code initially had "ns3:Udp" instead of the correct "ns3::Udp". The double colon (::) is the C++ namespace separator, and this tiny mistake was causing the simulation to crash. It's a reminder that in programming, the details matter!
Conclusion
In this short project, we built a custom Wi-Fi network, installed applications, generated traffic, and used the tracing system to collect and analyze real-time performance data. This workflow is the foundation for almost any simulation you can imagine in ns-3. From here, you can explore adding node mobility, simulating different types of network interference, or tracing other metrics like packet loss and delay.