Running TCP and UDP side by side shows the central fairness problem of the Internet: UDP does not back off, so it takes whatever share it wants while TCP yields.
Objectives
Do not copy. Read for understanding and the viva- Complete questions 16 to 18 of the manual: mixed tcp and udp pairs
- Prepare the deliverable before the lab and finish it during the session
- Be ready to explain every step in the viva
Questions Covered
Do not copy. Read for understanding and the viva| Question | Requirement | Status |
|---|---|---|
| Q16 | Setup 4 nodes, two TCP client and server pair and two UDP client and server pair | Complete |
| Q17 | Send packets to respective clients from both the servers | Complete |
| Q18 | Monitor the traffic for both the pair and plot the no. of bytes received | Complete |
Preparation
Do not copy. Read for understanding and the viva- Four nodes, two application pairs, distinct ports; keep both pairs on the same bottleneck so they compete.
- Log bytes received per interval for each sink separately (two trace files) and plot both series on one chart.
- Note in the record which pair suffers when the link is saturated and why.
Question 16
Problem Statement
Write in lab recordSetup 4 nodes, two TCP client and server pair and two UDP client and server pair.
Solution
Write in lab recordOne script, four-nodes.cc, covers questions 16 to 18. The four nodes sit on one CSMA bus of 2 Mbit/s so every flow competes for the same medium. Each node sends exactly one flow and receives exactly one flow.
Steps
- Copy the listing into
scratch/four-nodes.cc. NodeContainer nodes; nodes.Create(4);CsmaHelperwithDataRate2 Mbps andDelay6560 ns (the LAN delay used in the ns-3 tutorial);csma.Install(nodes)puts all four on one channel.InternetStackHelperon all nodes,Ipv4AddressHelper10.1.1.0/24, so n0 to n3 get 10.1.1.1 to 10.1.1.4.- The four pairs are held in a small table (sender, receiver, port, protocol, name). A loop installs, for every row, a
PacketSinkHelperon the receiver and either aBulkSendHelper(TCP) or anOnOffHelperat 500 kb/s with 1000-byte packets (UDP) on the sender. The socket factory string,ns3::TcpSocketFactoryorns3::UdpSocketFactory, is the only thing that differs between a TCP pair and a UDP pair. - Build and run:
./ns3 run scratch/four-nodes.
| Pair | Sender | Receiver | Port | Source application |
|---|---|---|---|---|
| TCP1 | n0 (10.1.1.1) | n2 (10.1.1.3) | 8080 | BulkSend, unlimited bytes |
| TCP2 | n1 (10.1.1.2) | n3 (10.1.1.4) | 8081 | BulkSend, unlimited bytes |
| UDP1 | n2 (10.1.1.3) | n0 (10.1.1.1) | 9000 | OnOff CBR 500 kb/s, 1000 bytes |
| UDP2 | n3 (10.1.1.4) | n1 (10.1.1.2) | 9001 | OnOff CBR 500 kb/s, 1000 bytes |
Program
/*
* four-nodes.cc -- MCSL-223 Session 7, questions 16 to 18
*
* Purpose of the program:
* Four nodes share one 2 Mbit/s CSMA bus. Two TCP pairs and two UDP pairs
* run at the same time so that all four flows compete for the bus. Every
* 0.5 s the cumulative bytes received at each of the four sinks is written
* to its own trace file (tcp1-bytes.txt, tcp2-bytes.txt, udp1-bytes.txt,
* udp2-bytes.txt); plot_bytes.gp draws the four series on one chart.
*
* Topology (CSMA bus, 2 Mbit/s, 6560 ns):
* n0 -------- n1 -------- n2 -------- n3
* TCP1 n0 -> n2 port 8080 (BulkSend -> PacketSink)
* TCP2 n1 -> n3 port 8081 (BulkSend -> PacketSink)
* UDP1 n2 -> n0 port 9000 (OnOff 500 kb/s -> PacketSink)
* UDP2 n3 -> n1 port 9001 (OnOff 500 kb/s -> PacketSink)
* Each node sends exactly one flow, so the bus is the only shared resource.
*
* Build and run (ns-3.36 or later):
* cp four-nodes.cc scratch/
* ./ns3 run scratch/four-nodes
* gnuplot plot_bytes.gp (produces bytes.png)
*/
#include "ns3/applications-module.h"
#include "ns3/core-module.h"
#include "ns3/csma-module.h"
#include "ns3/internet-module.h"
#include "ns3/network-module.h"
#include <vector>
using namespace ns3;
NS_LOG_COMPONENT_DEFINE("FourNodes");
/*
* SampleBytes: writes "time bytes" for every sink into its own stream and
* re-schedules itself 0.5 s later. GetTotalRx() is the cumulative byte
* count that PacketSink keeps for us.
*/
static void
SampleBytes(ApplicationContainer sinks, std::vector<Ptr<OutputStreamWrapper>> streams)
{
for (uint32_t i = 0; i < sinks.GetN(); ++i)
{
Ptr<PacketSink> sink = DynamicCast<PacketSink>(sinks.Get(i));
*streams[i]->GetStream() << Simulator::Now().GetSeconds() << " " << sink->GetTotalRx()
<< std::endl;
}
Simulator::Schedule(Seconds(0.5), &SampleBytes, sinks, streams);
}
/*
* main: builds the bus, installs the four sender/sink pairs, starts the
* sampler, runs for 10 s and prints bytes and throughput per sink.
*/
int
main(int argc, char* argv[])
{
std::string busRate = "2Mbps";
std::string udpRate = "500kb/s";
double simTime = 10.0;
CommandLine cmd(__FILE__);
cmd.AddValue("busRate", "CSMA bus data rate", busRate);
cmd.AddValue("udpRate", "Rate of each UDP OnOff source", udpRate);
cmd.AddValue("simTime", "Simulation time in seconds", simTime);
cmd.Parse(argc, argv);
// ---- Q16: four nodes on one CSMA bus ---------------------------------
NodeContainer nodes;
nodes.Create(4);
CsmaHelper csma;
csma.SetChannelAttribute("DataRate", StringValue(busRate));
csma.SetChannelAttribute("Delay", TimeValue(NanoSeconds(6560)));
NetDeviceContainer devices = csma.Install(nodes);
InternetStackHelper stack;
stack.Install(nodes);
Ipv4AddressHelper address;
address.SetBase("10.1.1.0", "255.255.255.0");
Ipv4InterfaceContainer ifaces = address.Assign(devices);
// sender index, receiver index, port, "tcp" or "udp"
struct Pair
{
uint32_t from;
uint32_t to;
uint16_t port;
bool tcp;
std::string name;
};
std::vector<Pair> pairs = {{0, 2, 8080, true, "tcp1"},
{1, 3, 8081, true, "tcp2"},
{2, 0, 9000, false, "udp1"},
{3, 1, 9001, false, "udp2"}};
ApplicationContainer sinks;
std::vector<Ptr<OutputStreamWrapper>> streams;
AsciiTraceHelper ascii;
for (const Pair& p : pairs)
{
std::string factory = p.tcp ? "ns3::TcpSocketFactory" : "ns3::UdpSocketFactory";
InetSocketAddress remote(ifaces.GetAddress(p.to), p.port);
// ---- Q16 / Q17: sink on the receiver, source on the sender --------
PacketSinkHelper sinkHelper(factory, InetSocketAddress(Ipv4Address::GetAny(), p.port));
ApplicationContainer sinkApp = sinkHelper.Install(nodes.Get(p.to));
sinkApp.Start(Seconds(0.0));
sinkApp.Stop(Seconds(simTime));
sinks.Add(sinkApp);
ApplicationContainer srcApp;
if (p.tcp)
{
BulkSendHelper bulk(factory, remote);
bulk.SetAttribute("MaxBytes", UintegerValue(0)); // send until stopped
srcApp = bulk.Install(nodes.Get(p.from));
}
else
{
OnOffHelper onoff(factory, remote);
onoff.SetConstantRate(DataRate(udpRate), 1000);
srcApp = onoff.Install(nodes.Get(p.from));
}
srcApp.Start(Seconds(1.0));
srcApp.Stop(Seconds(simTime));
// ---- Q18: one trace file per sink ----------------------------------
streams.push_back(ascii.CreateFileStream(p.name + "-bytes.txt"));
}
Simulator::Schedule(Seconds(0.0), &SampleBytes, sinks, streams);
Simulator::Stop(Seconds(simTime));
Simulator::Run();
// ---- report ---------------------------------------------------------
double active = simTime - 1.0;
for (uint32_t i = 0; i < pairs.size(); ++i)
{
Ptr<PacketSink> sink = DynamicCast<PacketSink>(sinks.Get(i));
std::cout << pairs[i].name << " n" << pairs[i].from << "->n" << pairs[i].to << " port "
<< pairs[i].port << " : " << sink->GetTotalRx() << " bytes, "
<< sink->GetTotalRx() * 8.0 / active / 1000.0 << " kbit/s" << std::endl;
}
Simulator::Destroy();
return 0;
}Diagram
n0 10.1.1.1 n1 10.1.1.2 n2 10.1.1.3 n3 10.1.1.4
| | | |
===+=================+================+================+=== CSMA bus, 2 Mbit/s
TCP1: n0 ----------------------------> n2 :8080
TCP2: n1 ----------------------------------------> n3 :8081
UDP1: n0 :9000 <---------------------- n2
UDP2: n1 :9001 <---------------------------------- n3Output
Expected output (ns-3 is not installed here; the listing was checked by reading against the ns-3.36 helper API). The TCP figures depend on how the two TCP flows share what the UDP flows leave; treat them as approximate:
$ ./ns3 run scratch/four-nodes
tcp1 n0->n2 port 8080 : 428000 bytes, 380.444 kbit/s
tcp2 n1->n3 port 8081 : 421000 bytes, 374.222 kbit/s
udp1 n2->n0 port 9000 : 562000 bytes, 499.556 kbit/s
udp2 n3->n1 port 9001 : 562000 bytes, 499.556 kbit/sExplanation
The bus is the bottleneck. The two UDP sources together offer 1 Mbit/s of payload, about 1.06 Mbit/s on the wire with UDP, IP and Ethernet headers, and they keep sending at that rate no matter what. The two TCP flows share what is left, roughly 0.9 Mbit/s, and their ACK frames also take bus time. That is why each TCP flow ends near 380 kbit/s while each UDP flow gets its full 500 kbit/s. Both UDP flows deliver 562 packets of 1000 bytes: one packet every 16 ms (from the formula sheet, 8 times 1000 over 500000) for 9 s.
Question 17
Problem Statement
Write in lab recordSend packets to respective clients from both the servers.
Solution
Write in lab recordThe manual calls the node that pushes data the server. In the script that is the BulkSendHelper node for TCP and the OnOffHelper node for UDP; the PacketSink node is the client that receives.
Steps
- Sinks start at 0 s so they are listening before any data arrives; every source starts at 1 s and stops at 10 s (
srcApp.Start(Seconds(1.0))). BulkSendHelperwithMaxBytes0 keeps the TCP socket’s send buffer full until the stop time, so TCP sends as fast as its congestion window allows.OnOffHelper::SetConstantRate(DataRate("500kb/s"), 1000)makes each UDP source send a 1000-byte datagram every 16 ms regardless of what the bus is doing.- Confirm the traffic in a pcap if the examiner asks: add
csma.EnablePcapAll("four-nodes")beforeSimulator::Run()and openfour-nodes-2-0.pcapin Wireshark; you will see TCP segments to port 8080 and UDP datagrams from port 9000 on the same interface.
Output
With LogComponentEnable("PacketSink", LOG_LEVEL_INFO) added at the top of main, every delivery prints a line like these (expected format, first four shown):
At time +1.00419s packet sink received 1000 bytes from 10.1.1.3 port 49153 total Rx 1000 bytes
At time +1.00838s packet sink received 1000 bytes from 10.1.1.4 port 49153 total Rx 1000 bytes
At time +1.01262s packet sink received 536 bytes from 10.1.1.1 port 49153 total Rx 536 bytes
At time +1.01566s packet sink received 536 bytes from 10.1.1.2 port 49153 total Rx 536 bytesExplanation
The UDP sinks see 1000-byte datagrams at a steady 16 ms spacing from the first second onwards. The TCP sinks see 536-byte segments (the ns-3 default SegmentSize) arriving in bursts that grow as the congestion window opens, then settle to whatever rate the bus leaves free. Source port 49153 is the first ephemeral port ns-3 hands out on each node, which is why all four flows show it.
Question 18
Problem Statement
Write in lab recordMonitor the traffic for both the pair and plot the no. of bytes received.
Solution
Write in lab recordSteps
PacketSink::GetTotalRx()already keeps the cumulative byte count for each sink, so monitoring means sampling it.SampleBytesis scheduled at 0 s and re-schedules itself every 0.5 s; on each call it writestime bytesfor every sink to that sink’s own stream.- The streams come from
AsciiTraceHelper::CreateFileStream, one per pair:tcp1-bytes.txt,tcp2-bytes.txt,udp1-bytes.txt,udp2-bytes.txt. - After the run,
gnuplot plot_bytes.gpreads the four files and writesbytes.pngwith all four series on one chart. - Paste the chart in the record and mark on it where the UDP lines are straight (constant rate) and where the TCP lines bend.
Program
# plot_bytes.gp -- MCSL-223 Session 7, Q18
# Run after the simulation: gnuplot plot_bytes.gp
# Reads the four "time bytes" files and draws them on one chart.
set terminal pngcairo size 900,540
set output "bytes.png"
set title "Cumulative bytes received at each sink (2 Mbit/s CSMA bus)"
set xlabel "Time (s)"
set ylabel "Bytes received"
set key left top
set grid
plot "tcp1-bytes.txt" using 1:2 with lines lw 2 title "TCP1 n0 to n2 (8080)", \
"tcp2-bytes.txt" using 1:2 with lines lw 2 title "TCP2 n1 to n3 (8081)", \
"udp1-bytes.txt" using 1:2 with lines lw 2 title "UDP1 n2 to n0 (9000)", \
"udp2-bytes.txt" using 1:2 with lines lw 2 title "UDP2 n3 to n1 (9001)"Output
Expected first lines of two of the trace files:
$ head -5 udp1-bytes.txt
0 0
0.5 0
1 0
1.5 31000
2 62000
$ head -5 tcp1-bytes.txt
0 0
0.5 0
1 0
1.5 14472
2 38056Expected chart: four cumulative lines starting at 1 s. The two UDP lines are straight with slope 62.5 kB/s and lie on top of each other. The two TCP lines start below them, curve upward during slow start and then run straight with a smaller slope, about 47 kB/s, ending near 425 kB at 10 s.
Explanation
The slope of each line is that flow’s throughput (the formula-sheet throughput is exactly rise over run on this plot, times 8). UDP is straight because the OnOff source never changes its rate and the bus still has room for it. TCP bends because its rate is set by the congestion window: it grows until a queue overflows at the sending device, halves, and grows again, so the line is a sequence of slightly different slopes averaging to the share the bus leaves.
If you raise --udpRate=1Mb/s, both UDP sources together offer 2 Mbit/s, the whole bus. The UDP lines stay nearly straight while the TCP lines almost flatten: TCP interprets every lost segment as congestion and slows down; UDP has no such rule. Write that observation in the record as the answer to “which pair suffers and why”.
Formula Sheet
Do not copy. Read for understanding and the vivaThroughput
Bytes received at the sink divided by the time the flow was active, converted to bits per second:
Bandwidth-delay product
The amount of data in flight on a link of capacity and round-trip time . A TCP window smaller than this cannot fill the link:
For the Session 10 defaults, and one-way delay give and .
Transmission and propagation delay
where is packet size in bits, link rate, distance and signal speed (about in copper or fibre).
Constant bit rate traffic
An OnOff application with packet size bytes and rate bit/s sends one packet every
TCP congestion window
Slow start doubles the window every RTT until the threshold; congestion avoidance adds one segment per RTT; a loss halves it (TCP NewReno):
The maximum throughput of one TCP flow is bounded by
Packet loss
Viva Questions
Do not copy. Read for understanding and the viva- Q: Why a CSMA bus and not four point-to-point links? A: With only four nodes, a shared bus is the simplest way to give all four flows one common bottleneck; separate point-to-point links would never compete.
- Q: What is the only difference between installing a TCP pair and a UDP pair? A: The socket factory string given to
PacketSinkHelperand to the source helper:ns3::TcpSocketFactoryagainstns3::UdpSocketFactory. - Q: Why does
BulkSendHelpertakeMaxBytes0? A: Zero means unlimited; the application keeps the socket buffer full until its stop time. - Q: Which application counts the bytes you plot? A:
PacketSink;GetTotalRx()returns the cumulative bytes delivered to it. - Q: Why are the UDP lines straight and the TCP lines bent? A: UDP sends at a fixed rate with no feedback; TCP’s rate follows its congestion window, which grows and halves with losses.
- Q: Which flow suffers when the bus is saturated? A: TCP. It treats every loss as congestion and slows down; UDP keeps sending, so it keeps most of its share.
- Q: What is the slope of a line in the bytes-versus-time plot? A: The throughput in bytes per second; multiply by 8 for bit/s.
- Q: Why sample every 0.5 s instead of logging every packet? A: A sampled cumulative count gives a smooth, small file that plots directly; per-packet logging gives thousands of lines that still need summing.
Common Mistakes
Do not copy. Read for understanding and the viva- Using a CSMA
Delayof milliseconds. With a propagation delay comparable to a frame time the bus spends most of its time in collisions and backoff and the throughput figures make no sense; use the tutorial’s 6560 ns. - Starting the sources before the sinks. A TCP SYN sent to a port with no listener is refused and the flow never starts.
- Writing all four sinks to one stream but reading it as one series; either keep one file per sink, as here, or one file with five columns.
- Scheduling
SampleBytesonce and forgetting to re-schedule it inside the function, which gives a single line per file. - Plotting the trace file and calling the y axis throughput. The file holds cumulative bytes; throughput is its slope.
- Putting two sources on the same node without noting that they share that node’s device queue; the record should explain where the competition happens.
Session Summary
Write in lab record- Source listing of
four-nodes.ccwith the header comment and the bus topology diagram with the four pairs marked - The pair table (sender, receiver, port, protocol)
- Run output with bytes and throughput for all four sinks
- Four
PacketSinklog lines showing both a UDP and a TCP delivery - The first lines of
udp1-bytes.txtandtcp1-bytes.txt plot_bytes.gpand the chartbytes.pngwith the four series, annotated with which pair suffers when the bus saturates and why