Skip to content

Session 7

Mixed TCP and UDP pairs

Updated View as Markdown

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
QuestionRequirementStatus
Q16Setup 4 nodes, two TCP client and server pair and two UDP client and server pairComplete
Q17Send packets to respective clients from both the serversComplete
Q18Monitor the traffic for both the pair and plot the no. of bytes receivedComplete

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 record

Setup 4 nodes, two TCP client and server pair and two UDP client and server pair.

Solution

Write in lab record

One 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

  1. Copy the listing into scratch/four-nodes.cc.
  2. NodeContainer nodes; nodes.Create(4); CsmaHelper with DataRate 2 Mbps and Delay 6560 ns (the LAN delay used in the ns-3 tutorial); csma.Install(nodes) puts all four on one channel.
  3. InternetStackHelper on all nodes, Ipv4AddressHelper 10.1.1.0/24, so n0 to n3 get 10.1.1.1 to 10.1.1.4.
  4. The four pairs are held in a small table (sender, receiver, port, protocol, name). A loop installs, for every row, a PacketSinkHelper on the receiver and either a BulkSendHelper (TCP) or an OnOffHelper at 500 kb/s with 1000-byte packets (UDP) on the sender. The socket factory string, ns3::TcpSocketFactory or ns3::UdpSocketFactory, is the only thing that differs between a TCP pair and a UDP pair.
  5. Build and run: ./ns3 run scratch/four-nodes.
PairSenderReceiverPortSource application
TCP1n0 (10.1.1.1)n2 (10.1.1.3)8080BulkSend, unlimited bytes
TCP2n1 (10.1.1.2)n3 (10.1.1.4)8081BulkSend, unlimited bytes
UDP1n2 (10.1.1.3)n0 (10.1.1.1)9000OnOff CBR 500 kb/s, 1000 bytes
UDP2n3 (10.1.1.4)n1 (10.1.1.2)9001OnOff CBR 500 kb/s, 1000 bytes

Program

four-nodes.cccpp
/*
 * 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 <---------------------------------- n3

Output

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/s

Explanation

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 record

Send packets to respective clients from both the servers.

Solution

Write in lab record

The 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

  1. 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))).
  2. BulkSendHelper with MaxBytes 0 keeps the TCP socket’s send buffer full until the stop time, so TCP sends as fast as its congestion window allows.
  3. 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.
  4. Confirm the traffic in a pcap if the examiner asks: add csma.EnablePcapAll("four-nodes") before Simulator::Run() and open four-nodes-2-0.pcap in 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 bytes

Explanation

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 record

Monitor the traffic for both the pair and plot the no. of bytes received.

Solution

Write in lab record

Steps

  1. PacketSink::GetTotalRx() already keeps the cumulative byte count for each sink, so monitoring means sampling it. SampleBytes is scheduled at 0 s and re-schedules itself every 0.5 s; on each call it writes time bytes for every sink to that sink’s own stream.
  2. The streams come from AsciiTraceHelper::CreateFileStream, one per pair: tcp1-bytes.txt, tcp2-bytes.txt, udp1-bytes.txt, udp2-bytes.txt.
  3. After the run, gnuplot plot_bytes.gp reads the four files and writes bytes.png with all four series on one chart.
  4. 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.gptext
# 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 38056

Expected 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 viva

Throughput

Bytes received at the sink divided by the time the flow was active, converted to bits per second:

Throughput=8×bytes receivedtend−tstartbit/s

Bandwidth-delay product

The amount of data in flight on a link of capacity B and round-trip time RTT. A TCP window smaller than this cannot fill the link:

BDP=B×RTT

For the Session 10 defaults, B=5Mbit/s and one-way delay 5ms give RTT=10ms and BDP=5×106×0.010=50,000bits≈6.25kB.

Transmission and propagation delay

ttrans=LB,tprop=dv,ttotal=ttrans+tprop+tqueue+tproc

where L is packet size in bits, B link rate, d distance and v signal speed (about 2×108m/s in copper or fibre).

Constant bit rate traffic

An OnOff application with packet size L bytes and rate R bit/s sends one packet every

Δt=8LR seconds

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

cwndss(n)=2nMSS,cwndca←cwnd+MSS2cwnd,cwndloss←cwnd2

The maximum throughput of one TCP flow is bounded by

Throughputmax⁡=cwndmax⁡RTT

Packet loss

Loss rate=packets sent−packets receivedpackets sent

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 PacketSinkHelper and to the source helper: ns3::TcpSocketFactory against ns3::UdpSocketFactory.
  • Q: Why does BulkSendHelper take MaxBytes 0? 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 Delay of 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 SampleBytes once 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.cc with 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 PacketSink log lines showing both a UDP and a TCP delivery
  • The first lines of udp1-bytes.txt and tcp1-bytes.txt
  • plot_bytes.gp and the chart bytes.png with the four series, annotated with which pair suffers when the bus saturates and why
Navigation

Type to search…

↑↓ navigate↵ selectEsc close