Transmission Control Protocol (TCP) plays a vital role in ensuring reliable data delivery across networks. However, different TCP variants respond differently when packet losses occur. In this experiment, we used the NS-3 network simulator to compare four popular TCP congestion control algorithms—TCP Reno, TCP NewReno, TCP Cubic, and TCP Vegas—under a wired topology with a 5% random packet loss rate.
/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
/*
* TCP Variants Comparison under 5% Packet Loss
* Comparing: Reno, NewReno, Cubic, Vegas
* Wired Topology with Random Packet Loss
*/
#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"
#include "ns3/flow-monitor-module.h"
#include "ns3/netanim-module.h"
#include "ns3/traffic-control-module.h"
#include <fstream>
#include <string>
#include <map>
using namespace ns3;
NS_LOG_COMPONENT_DEFINE("TcpVariantsComparison");
// Global variables for tracing
std::map<std::string, std::ofstream*> cwndFiles;
std::map<std::string, std::ofstream*> throughputFiles;
std::map<std::string, uint64_t> lastTotalRx;
std::map<std::string, Ptr<PacketSink>> sinkApps;
// Congestion window tracer
void CwndTracer(std::string context, uint32_t oldCwnd, uint32_t newCwnd)
{
std::string variant = context;
if (cwndFiles.find(variant) != cwndFiles.end() && cwndFiles[variant]->is_open())
{
*cwndFiles[variant] << Simulator::Now().GetSeconds() << " " << newCwnd << std::endl;
}
}
// Throughput calculator
void CalculateThroughput(std::string variant)
{
if (sinkApps.find(variant) != sinkApps.end())
{
uint64_t totalRx = sinkApps[variant]->GetTotalRx();
double throughput = (totalRx - lastTotalRx[variant]) * 8.0 / 0.1 / 1000000; // Mbps
lastTotalRx[variant] = totalRx;
if (throughputFiles.find(variant) != throughputFiles.end() && throughputFiles[variant]->is_open())
{
*throughputFiles[variant] << Simulator::Now().GetSeconds() << " " << throughput << std::endl;
}
}
if (Simulator::Now().GetSeconds() < 30.0)
{
Simulator::Schedule(Seconds(0.1), &CalculateThroughput, variant);
}
}
// Run simulation for a specific TCP variant
void RunSimulation(std::string tcpVariant, double packetLossRate)
{
std::cout << "\n========================================" << std::endl;
std::cout << "Running simulation for TCP " << tcpVariant << std::endl;
std::cout << "Packet Loss Rate: " << packetLossRate * 100 << "%" << std::endl;
std::cout << "========================================\n" << std::endl;
// Set TCP variant
std::string tcpTypeId;
if (tcpVariant == "Reno")
{
tcpTypeId = "ns3::TcpLinuxReno";
}
else if (tcpVariant == "NewReno")
{
tcpTypeId = "ns3::TcpNewReno";
}
else if (tcpVariant == "Cubic")
{
tcpTypeId = "ns3::TcpCubic";
}
else if (tcpVariant == "Vegas")
{
tcpTypeId = "ns3::TcpVegas";
}
else
{
NS_LOG_ERROR("Unknown TCP variant: " << tcpVariant);
return;
}
Config::SetDefault("ns3::TcpL4Protocol::SocketType", TypeIdValue(TypeId::LookupByName(tcpTypeId)));
Config::SetDefault("ns3::TcpSocket::SegmentSize", UintegerValue(1448));
Config::SetDefault("ns3::TcpSocket::InitialCwnd", UintegerValue(1));
// Create nodes
NodeContainer senderNode, receiverNode, routerNodes;
senderNode.Create(1);
receiverNode.Create(1);
routerNodes.Create(2);
// Create point-to-point links
PointToPointHelper p2pSender, p2pReceiver, p2pBottleneck;
// Sender to Router1: 100 Mbps, 2ms delay
p2pSender.SetDeviceAttribute("DataRate", StringValue("100Mbps"));
p2pSender.SetChannelAttribute("Delay", StringValue("2ms"));
// Router2 to Receiver: 100 Mbps, 2ms delay
p2pReceiver.SetDeviceAttribute("DataRate", StringValue("100Mbps"));
p2pReceiver.SetChannelAttribute("Delay", StringValue("2ms"));
// Bottleneck link: 10 Mbps, 10ms delay
p2pBottleneck.SetDeviceAttribute("DataRate", StringValue("10Mbps"));
p2pBottleneck.SetChannelAttribute("Delay", StringValue("10ms"));
// Install links
NetDeviceContainer senderDevices = p2pSender.Install(senderNode.Get(0), routerNodes.Get(0));
NetDeviceContainer bottleneckDevices = p2pBottleneck.Install(routerNodes.Get(0), routerNodes.Get(1));
NetDeviceContainer receiverDevices = p2pReceiver.Install(routerNodes.Get(1), receiverNode.Get(0));
// Add random packet loss to bottleneck link (5%)
Ptr<RateErrorModel> errorModel = CreateObject<RateErrorModel>();
errorModel->SetAttribute("ErrorRate", DoubleValue(packetLossRate));
errorModel->SetAttribute("ErrorUnit", EnumValue(RateErrorModel::ERROR_UNIT_PACKET));
bottleneckDevices.Get(1)->SetAttribute("ReceiveErrorModel", PointerValue(errorModel));
// Install internet stack
InternetStackHelper internet;
internet.Install(senderNode);
internet.Install(receiverNode);
internet.Install(routerNodes);
// Assign IP addresses
Ipv4AddressHelper ipv4;
ipv4.SetBase("10.1.1.0", "255.255.255.0");
Ipv4InterfaceContainer senderInterfaces = ipv4.Assign(senderDevices);
ipv4.SetBase("10.1.2.0", "255.255.255.0");
Ipv4InterfaceContainer bottleneckInterfaces = ipv4.Assign(bottleneckDevices);
ipv4.SetBase("10.1.3.0", "255.255.255.0");
Ipv4InterfaceContainer receiverInterfaces = ipv4.Assign(receiverDevices);
// Set up routing
Ipv4GlobalRoutingHelper::PopulateRoutingTables();
// Create applications
uint16_t port = 9;
// Packet sink on receiver
PacketSinkHelper sinkHelper("ns3::TcpSocketFactory",
InetSocketAddress(Ipv4Address::GetAny(), port));
ApplicationContainer sinkApp = sinkHelper.Install(receiverNode.Get(0));
sinkApp.Start(Seconds(0.0));
sinkApp.Stop(Seconds(30.0));
sinkApps[tcpVariant] = DynamicCast<PacketSink>(sinkApp.Get(0));
lastTotalRx[tcpVariant] = 0;
// Bulk send on sender
BulkSendHelper sourceHelper("ns3::TcpSocketFactory",
InetSocketAddress(receiverInterfaces.GetAddress(1), port));
sourceHelper.SetAttribute("MaxBytes", UintegerValue(0)); // Unlimited
sourceHelper.SetAttribute("SendSize", UintegerValue(1448));
ApplicationContainer sourceApp = sourceHelper.Install(senderNode.Get(0));
sourceApp.Start(Seconds(1.0));
sourceApp.Stop(Seconds(29.0));
// Open output files
std::string cwndFilename = "tcp-" + tcpVariant + "-cwnd.dat";
std::string throughputFilename = "tcp-" + tcpVariant + "-throughput.dat";
cwndFiles[tcpVariant] = new std::ofstream(cwndFilename.c_str());
throughputFiles[tcpVariant] = new std::ofstream(throughputFilename.c_str());
// Set up congestion window tracing
Simulator::Schedule(Seconds(1.001), [tcpVariant]() {
Config::ConnectWithoutContext("/NodeList/0/$ns3::TcpL4Protocol/SocketList/0/CongestionWindow",
MakeBoundCallback(&CwndTracer, tcpVariant));
});
// Schedule throughput calculation
Simulator::Schedule(Seconds(1.1), &CalculateThroughput, tcpVariant);
// Flow monitor
FlowMonitorHelper flowHelper;
Ptr<FlowMonitor> flowMonitor = flowHelper.InstallAll();
// NetAnim setup
std::string animFilename = "tcp-" + tcpVariant + "-animation.xml";
AnimationInterface anim(animFilename);
// Set node positions for visualization
anim.SetConstantPosition(senderNode.Get(0), 10.0, 50.0);
anim.SetConstantPosition(routerNodes.Get(0), 40.0, 50.0);
anim.SetConstantPosition(routerNodes.Get(1), 70.0, 50.0);
anim.SetConstantPosition(receiverNode.Get(0), 100.0, 50.0);
// Set node descriptions
anim.UpdateNodeDescription(senderNode.Get(0), "Sender");
anim.UpdateNodeDescription(routerNodes.Get(0), "Router1");
anim.UpdateNodeDescription(routerNodes.Get(1), "Router2");
anim.UpdateNodeDescription(receiverNode.Get(0), "Receiver");
// Set node colors
anim.UpdateNodeColor(senderNode.Get(0), 0, 255, 0); // Green
anim.UpdateNodeColor(routerNodes.Get(0), 0, 0, 255); // Blue
anim.UpdateNodeColor(routerNodes.Get(1), 0, 0, 255); // Blue
anim.UpdateNodeColor(receiverNode.Get(0), 255, 0, 0); // Red
// Run simulation
Simulator::Stop(Seconds(30.0));
Simulator::Run();
// Print flow monitor statistics
flowMonitor->CheckForLostPackets();
Ptr<Ipv4FlowClassifier> classifier = DynamicCast<Ipv4FlowClassifier>(flowHelper.GetClassifier());
FlowMonitor::FlowStatsContainer stats = flowMonitor->GetFlowStats();
std::cout << "\n--- Flow Statistics for TCP " << tcpVariant << " ---\n" << std::endl;
for (auto const &flow : stats)
{
Ipv4FlowClassifier::FiveTuple t = classifier->FindFlow(flow.first);
std::cout << "Flow " << flow.first << " (" << t.sourceAddress << " -> " << t.destinationAddress << ")" << std::endl;
std::cout << " Tx Packets: " << flow.second.txPackets << std::endl;
std::cout << " Rx Packets: " << flow.second.rxPackets << std::endl;
std::cout << " Lost Packets: " << flow.second.lostPackets << std::endl;
std::cout << " Packet Loss Ratio: " << (double)flow.second.lostPackets / flow.second.txPackets * 100 << "%" << std::endl;
if (flow.second.rxPackets > 0)
{
double throughput = flow.second.rxBytes * 8.0 /
(flow.second.timeLastRxPacket.GetSeconds() - flow.second.timeFirstTxPacket.GetSeconds()) / 1000000;
std::cout << " Throughput: " << throughput << " Mbps" << std::endl;
std::cout << " Mean Delay: " << flow.second.delaySum.GetSeconds() / flow.second.rxPackets * 1000 << " ms" << std::endl;
std::cout << " Mean Jitter: " << flow.second.jitterSum.GetSeconds() / flow.second.rxPackets * 1000 << " ms" << std::endl;
}
std::cout << std::endl;
}
// Close output files
if (cwndFiles[tcpVariant]->is_open())
{
cwndFiles[tcpVariant]->close();
}
if (throughputFiles[tcpVariant]->is_open())
{
throughputFiles[tcpVariant]->close();
}
delete cwndFiles[tcpVariant];
delete throughputFiles[tcpVariant];
Simulator::Destroy();
}
int main(int argc, char *argv[])
{
// Command line arguments
double packetLossRate = 0.05; // 5% packet loss
std::string variant = "all";
CommandLine cmd(__FILE__);
cmd.AddValue("lossRate", "Packet loss rate (0.0 to 1.0)", packetLossRate);
cmd.AddValue("variant", "TCP variant (Reno/NewReno/Cubic/Vegas/all)", variant);
cmd.Parse(argc, argv);
// Enable logging
LogComponentEnable("TcpVariantsComparison", LOG_LEVEL_INFO);
std::cout << "\n********************************************************" << std::endl;
std::cout << "* TCP Variants Comparison under " << packetLossRate * 100 << "% Packet Loss *" << std::endl;
std::cout << "* Wired Topology: Sender -- R1 -- R2 -- Receiver *" << std::endl;
std::cout << "********************************************************\n" << std::endl;
// List of TCP variants to test
std::vector<std::string> variants;
if (variant == "all")
{
variants = {"Reno", "NewReno", "Cubic", "Vegas"};
}
else
{
variants = {variant};
}
// Run simulation for each variant
for (const auto &v : variants)
{
RunSimulation(v, packetLossRate);
}
// Generate gnuplot script for comparison
std::ofstream gnuplotScript("plot-comparison.plt");
gnuplotScript << "# Gnuplot script for TCP variants comparison\n\n";
// Throughput comparison plot
gnuplotScript << "set terminal png size 1200,800 enhanced font 'Arial,12'\n";
gnuplotScript << "set output 'tcp-throughput-comparison.png'\n";
gnuplotScript << "set title 'TCP Throughput Comparison under 5% Packet Loss'\n";
gnuplotScript << "set xlabel 'Time (seconds)'\n";
gnuplotScript << "set ylabel 'Throughput (Mbps)'\n";
gnuplotScript << "set grid\n";
gnuplotScript << "set key right top\n";
gnuplotScript << "plot 'tcp-Reno-throughput.dat' with lines lw 2 title 'TCP Reno', \\\n";
gnuplotScript << " 'tcp-NewReno-throughput.dat' with lines lw 2 title 'TCP NewReno', \\\n";
gnuplotScript << " 'tcp-Cubic-throughput.dat' with lines lw 2 title 'TCP Cubic', \\\n";
gnuplotScript << " 'tcp-Vegas-throughput.dat' with lines lw 2 title 'TCP Vegas'\n\n";
// Congestion window comparison plot
gnuplotScript << "set output 'tcp-cwnd-comparison.png'\n";
gnuplotScript << "set title 'TCP Congestion Window Comparison under 5% Packet Loss'\n";
gnuplotScript << "set xlabel 'Time (seconds)'\n";
gnuplotScript << "set ylabel 'Congestion Window (bytes)'\n";
gnuplotScript << "set grid\n";
gnuplotScript << "set key right top\n";
gnuplotScript << "plot 'tcp-Reno-cwnd.dat' with lines lw 2 title 'TCP Reno', \\\n";
gnuplotScript << " 'tcp-NewReno-cwnd.dat' with lines lw 2 title 'TCP NewReno', \\\n";
gnuplotScript << " 'tcp-Cubic-cwnd.dat' with lines lw 2 title 'TCP Cubic', \\\n";
gnuplotScript << " 'tcp-Vegas-cwnd.dat' with lines lw 2 title 'TCP Vegas'\n";
gnuplotScript.close();
std::cout << "\n========================================" << std::endl;
std::cout << "Simulation Complete!" << std::endl;
std::cout << "========================================" << std::endl;
std::cout << "\nOutput files generated:" << std::endl;
std::cout << "- Animation files: tcp-<variant>-animation.xml" << std::endl;
std::cout << "- Throughput data: tcp-<variant>-throughput.dat" << std::endl;
std::cout << "- CWND data: tcp-<variant>-cwnd.dat" << std::endl;
std::cout << "- Gnuplot script: plot-comparison.plt" << std::endl;
std::cout << "\nTo generate graphs, run:" << std::endl;
std::cout << " gnuplot plot-comparison.plt" << std::endl;
std::cout << "\nTo view animation, run:" << std::endl;
std::cout << " ./netanim tcp-<variant>-animation.xml" << std::endl;
return 0;
}
Among all variants, TCP Cubic delivered the highest average throughput. Its aggressive congestion window growth allows it to utilize available bandwidth more efficiently. However, this comes with increased fluctuations and higher packet loss.
TCP NewReno showed better resilience against multiple packet losses within the same congestion window. Its enhanced fast recovery mechanism helped maintain more stable throughput compared to classic Reno.
Unlike the loss-based variants, TCP Vegas uses delay measurements to anticipate congestion. This resulted in:
However, Vegas sacrificed throughput to maintain network stability.
The 5% random packet loss significantly affected loss-based TCP variants. Since packet loss is interpreted as network congestion, Reno, NewReno, and Cubic repeatedly reduced their sending rates, impacting overall performance.
This NS-3 study demonstrates how different TCP congestion control algorithms react under adverse network conditions. TCP Cubic offers the highest throughput, making it suitable for high-speed networks, while TCP Vegas provides superior stability and lower delays. TCP NewReno presents a balanced approach and remains a strong improvement over classic Reno. The results highlight the importance of selecting the appropriate TCP variant based on application requirements and network conditions.
#NS3 #NetworkSimulation #TCP #TCPReno #TCPNewReno #TCPCubic #TCPVegas #ComputerNetworks #CongestionControl #PacketLoss #NetworkPerformance #WirelessNetworks #WiredNetworks #NetworkResearch #PhDResearch #SimulationStudy #Networking #DataCommunication #NetAnim #Gnuplot #ResearchBlog #Engineering #ComputerScience #NetworkEngineering #AcademicResearch #TechBlogging #InternetProtocols #PerformanceAnalysis #OpenSource #NetworkTesting
Comments
Post a Comment