The Transport Layer: A Practical Overview

Let’s continue with a networking theory, now with the transport layer:

Index:

#TopicShort Description
1Transport servicesLogical communication between processes (network layer = between hosts)
2Mux / demuxUDP socket = (dst IP, dst port); TCP socket = 4-tuple
3UDP8-byte header, checksum, no handshake, no congestion control
4Reliable data transferChecksum + ACK + sequence numbers + timer = reliability over a lossy channel
5PipeliningGo-Back-N resends everything after the gap; Selective Repeat resends only what’s missing
6TCP overviewByte-stream sequence numbers; ACK = next byte expected (cumulative)
7TCP reliabilityAdaptive timeout from RTT samples; fast retransmit on 3 duplicate ACKs
8Flow controlReceiver advertises rwnd so the sender can’t overflow its buffer
9Connection management3-way handshake SYN / SYN-ACK / ACK; FIN-based close with TIME_WAIT
10Congestion controlSlow start (exponential) → AIMD (linear up, halve on loss)

Transport-layer services

The transport layer gives logical communication between application processes on different hosts. The network layer (IP) only gets packets between hosts; transport extends that to the right process.

Details

  • Transport protocols run only in end systems, never in routers.
  • Send side: takes application messages, breaks them into segments, adds a transport header, passes them to the network layer.
  • Receive side: reassembles segments into messages and passes them up to the right socket.
  • Household analogy: houses = hosts, kids = processes, letters = messages, the postal service = network layer, the sibling who collects and hands out mail inside each house = transport layer.
TCPUDP
DeliveryReliable, in orderBest effort: may lose, duplicate or reorder
Connection setupYes (3-way handshake)None
Flow controlYesNo
Congestion controlYesNo
Header20 bytes minimum8 bytes
Not provided by eitherDelay guarantees, bandwidth guaranteessame

Multiplexing and demultiplexing

Multiplexing (sender) = collect data from many sockets and add a transport header to each chunk.

Demultiplexing (receiver) = use header fields to hand each arriving segment to the right socket.

How demux actually works:

  1. The host receives an IP datagram carrying a source and destination IP address.
  2. Each datagram carries one transport-layer segment with a source and destination port number.
  3. The host uses IP addresses + port numbers to pick the socket.

Connectionless demux (UDP):

  1. The host looks only at the destination port (and destination IP).
  2. Datagrams with the same destination port but different source IPs or source ports go to the same socket. Example: every client of a DNS server lands in its one socket on port 53.
  3. The source IP and port are still in the segment, so the server knows where to send the reply (it swaps them).

Connection-oriented demux (TCP)

  1. A TCP socket is identified by the 4-tuple: source IP, source port, destination IP, destination port. All four are used.
  2. A web server has a separate socket per client connection, all on port 80. With non-persistent HTTP, a new socket per request.
  3. Two browsers on the same laptop to the same site: same source IP, same destination IP and port 80: only the source port differs, and that’s enough to keep them apart.
demux-mux

UDP: User Datagram Protocol

This protocol doesn’t have a handshake, it also has no state, no retransmission and no congestion control.

More Details:

  • Connectionless: no handshake; each segment is handled independently.
  • Segments may be lost, duplicated or delivered out of order.

Segment structure: 8-byte header. Four 16-bit fields = 8 bytes. Length = header + data, in bytes.

0               16              31   (bits)
+---------------+---------------+
|  source port  |   dest port   |
+---------------+---------------+
|    length     |   checksum    |
+---------------+---------------+
|     application data          |
|        (payload)              |
+-------------------------------+

Checksum

  1. Sender: treat the segment (plus a pseudo-header with the IP addresses) as a sequence of 16-bit words, add them with one’s-complement arithmetic, put the complement of the sum in the checksum field.
  2. Receiver: recompute; mismatch → error detected (the segment is typically discarded). A match doesn’t prove there’s no error because two flipped bits can cancel.
  3. Optional in IPv4 (0 = not computed), mandatory in IPv6.

So why to chose UDP?

  1. No connection setup: no handshake RTT before the first byte.
  2. Simple, stateless: a server can handle far more clients.
  3. Small header: 8 bytes vs 20+ for TCP.
  4. No congestion control: the application sends at whatever rate it chooses (good for real-time media, dangerous for the network if abused).

Principles of reliable data transfer (rdt):

Interface: rdt_send() (called by the app above) → udt_send() (unreliable send, below) → rdt_rcv() (packet arrives) → deliver_data() (up to the app). Each version is specified as a finite-state machine (FSM): an event causes a transition from one state to the next, with actions along the way.

The evolution of rdt:

VersionChannel assumptionMechanism addedRemaining problem
rdt1.0Perfectly reliableNothing — just send and deliverHypothetical; never true
rdt2.0Bit errors, no lossChecksum + ACK / NAK + retransmit on NAK (stop-and-wait)ACK/NAK itself can be corrupted → sender doesn’t know what happened; blind retransmit creates duplicates
rdt2.1Bit errors, no lossSequence numbers (0/1 is enough for stop-and-wait); receiver discards duplicatesStill needs NAKs
rdt2.2Bit errors, no lossNAK-free: receiver ACKs the last good packet; a duplicate ACK means “resend the next one”Can’t handle loss
rdt3.0Bit errors and lossCountdown timer: no ACK in a “reasonable” time → retransmitCorrect, but stop-and-wait performance is terrible

rdt3.0 scenarios:

ScenarioWhat happensWhy it still works
(a) No losspkt0 → ack0 → pkt1 → ack1—
(b) Packet lostTimer expires, sender resendsReceiver never saw it, so no duplicate
(c) ACK lostTimer expires, sender resends; receiver sees a duplicateSequence number reveals the duplicate → discard, re-ACK
(d) Premature timeout / delayed ACKSender resends although the ACK is only lateReceiver discards the duplicate; sender ignores the extra ACK

Scheme scenario (c), lost ACK:

sender                         receiver
  | --- pkt0 -----------------> |   deliver pkt0
  | <-------------- ack0 ------ |
  | --- pkt1 -----------------> |   deliver pkt1
  |        X<------ ack1 ------ |   (ACK lost)
  |  [timeout]                  |
  | --- pkt1 (resend) --------> |   duplicate seq 1: discard
  | <-------------- ack1 ------ |   ...but ACK again
  | --- pkt0 -----------------> |

Stop-and-wait performance: the sender is busy only while pushing one packet onto the wire, then idles for a full RTT:

U_sender = (L/R) / (RTT + L/R)

Classic textbook example: 1 Gbps link, 8,000-bit packet, 30 ms RTT → L/R = 8 µs → U = 0.008 / 30.008 ≈ 0.00027, i.e. ~267 kbps effective throughput on a 1 Gbps link. The protocol, not the link, is the bottleneck → pipelining (section 5).

Pipelining: Go-Back-N vs Selective Repeat

The main idea to let the sender have many packets in flight before any ACK comes back. That needs a larger sequence-number range and buffering, and a rule for recovering from loss: Go-Back-N or Selective Repeat.

Why pipelining helps: with 3 packets in flight per RTT, sender utilization is ~3× the stop-and-wait figure (as long as the window fits in one RTT’s worth of transmission time). Now let’s look on sender window for both protocols, for the simplicity color analogue will be used:

  [ ACKed ][ sent, not yet ACKed ][ usable, not sent ][ not usable ]
            <------------ window of size N ------------>
   (green)        (yellow)              (blue)            (grey)

Each arriving ACK slides the window right: yellow → green, blue → yellow.

Go-Back-N (GBN)Selective Repeat (SR)
Receiver ACKsCumulative: “everything up to n arrived”Individual: each correctly received packet
Out-of-order packetsDiscarded (receiver buffers nothing)Buffered until the gap is filled
TimersOne, for the oldest unACKed packetOne per packet (logically)
On timeoutResend the lost packet and every packet after itResend only the packet that timed out
Receiver complexityMinimalBuffer + reordering logic
Waste on a lossy linkHighLow
pipelining-picture
Go-Back-N vs Selective Repeat · same loss, different recovery

With packet 2 lost, GBN re-sends four packets; SR re-sends one.

Another example: ten packets sent, packets 1–6 arrive, 7–10 are lost → GBN receiver keeps ACKing 6 (cumulative), sender resends 7–10. If instead only packets 2 and 6 are lost, SR resends just 2 and 6.

TCP overview, segment structure, sequence and ACK numbers

The main properties:

PropertyMeaning
Point-to-pointOne sender, one receiver (no multicast)
Reliable, in-order byte streamNo message boundaries; the app reads bytes in the order they were sent
PipelinedMany segments in flight; window size set by congestion + flow control
Full duplexData flows both ways on the same connection
Connection-orientedHandshake sets up state (sequence numbers, buffers) before data
Flow controlledSender never overwhelms the receiver’s buffer
Congestion controlledSender backs off when the network is overloaded

Segment structure (20-byte header without options)

 0                   16                  31  (bits)
+-------------------+-------------------+
|    source port    |     dest port     |
+-------------------+-------------------+
|            sequence number            |
+---------------------------------------+
|        acknowledgment number          |
+----+------+-------+-------------------+
|hlen|unused|  flags|   receive window  |
+----+------+-------+-------------------+
|     checksum      |  urgent pointer   |
+-------------------+-------------------+
|        options (variable length)      |
+---------------------------------------+
|        application data (payload)     |
+---------------------------------------+
 flags: CWR ECE URG ACK PSH RST SYN FIN

Key fields: ports (demux), sequence and ACK numbers (reliability), receive window (flow control), flags SYN / FIN / RST (connection management), checksum (errors), options such as MSS, window scaling, SACK and timestamps.

Sequence and ACK numbers:

  • Sequence number = byte-stream number of the first byte in this segment’s data (not a packet counter).
  • ACK number = sequence number of the next byte expected from the other side. ACKs are cumulative.
  • The initial sequence number (ISN) is chosen randomly per connection.

Telnet example: host A types C, host B echoes it back (1 byte case):

Host A                                         Host B
  | --- Seq=42, ACK=79, data='C' ------------> |   B got byte 42, wants 43
  | <-- Seq=79, ACK=43, data='C' ------------- |   echo; B's own byte 79
  | --- Seq=43, ACK=80 (no data) ------------> |   A ACKs the echo, wants 80
Client                                          Server
  | --- Seq=42, ACK=79, data='test\r' (5B) ----> |  Server got bytes 42-46, wants 47
  | <-- Seq=79, ACK=47, data='TEST\r' (5B) ----- |  echo; Server's bytes 79-83
  | --- Seq=47, ACK=84 (no data) --------------> |  Client ACKs the echo, wants 84

Basically Each side’s ACK = the other side’s sequence number + bytes received.

TCP reliability: timeouts, retransmissions, ACKs, fast retransmit

TCP = rdt3.0 ideas on a byte stream: cumulative ACKs, a single retransmission timer for the oldest unACKed segment, a timeout computed from measured RTTs, and fast retransmit so most losses are repaired before the timer even fires.

Setting the timeout: if too short = needless retransmissions, if too long = slow reaction to loss. TCP measures SampleRTT (time from sending a segment to its ACK, ignoring retransmitted segments) and smooths it:

EstimatedRTT  = (1 - α) · EstimatedRTT + α · SampleRTT            α = 0.125
DevRTT        = (1 - β) · DevRTT + β · |SampleRTT - EstimatedRTT|   β = 0.25
TimeoutInterval = EstimatedRTT + 4 · DevRTT

EWMA is an exponentially weighted moving average: recent samples count more. The 4 · DevRTT term is the safety margin — jittery paths get a longer timeout. After a timeout the interval is doubled (exponential backoff) until a fresh ACK arrives. Initial value: 1 sec.

Retransmission scenarios

ScenarioTimelineOutcome
Lost ACKSeq=92 (8 B) arrives; ACK=100 is lost; timer firesSender resends Seq=92; receiver discards the duplicate and re-ACKs 100
Premature timeoutSeq=92 (8 B) and Seq=100 (20 B) sent; their ACKs are delayed; timer firesSender resends Seq=92 only; the late ACK=120 confirms both, so it moves on to byte 120
Cumulative ACK saves a retransmitACK=100 is lost but ACK=120 arrives before the timeoutNo retransmission — ACK=120 covers bytes 92–11
Event at receiverTCP receiver action
In-order segment, all earlier data already ACKedDelayed ACK: wait up to 500 ms for another segment; if none arrives, send the ACK
In-order segment, another in-order segment is waiting for its ACKImmediately send one cumulative ACK covering both
Out-of-order segment with a higher-than-expected sequence number (gap detected)Immediately send a duplicate ACK naming the next expected byte
Segment that partially or completely fills the gapImmediately ACK, if it starts at the lower end of the gap

Fast retransmit: if the sender gets three duplicate ACKs for the same data, it resends the one unACKed segment with the smallest sequence number immediately without waiting for the timeout. Three duplicates present because a single duplicate may just be reordering.

Fast-retransmit
TCP fast retransmit · triple duplicate ACK

The lost 20-byte segment is repaired after the third duplicate ACK=100; the receiver’s buffered segments then let one cumulative ACK jump past everything.

TCP flow control

The receiver controls the sender so the sender never overflows the receiver’s buffer. It advertises its free buffer space as rwnd (the receive window header field) in every segment it sends back.

More details

  • The app may read from the socket buffer more slowly than the network delivers data; without flow control the buffer would overflow and data would be dropped.
  • Receiver: rwnd = RcvBuffer − (LastByteRcvd − LastByteRead).
  • Sender: keeps unACKed in-flight data ≤ rwnd: LastByteSent − LastByteAcked ≤ rwnd.
  • If rwnd reaches 0, the sender keeps probing with 1-byte segments (the persist timer) so it learns when space frees up.
  • RcvBuffer is set by socket options (typically a few hundred KB, auto-tuned by modern OSes); window scaling lets rwnd exceed the 16-bit field’s 64 KB.

Receive buffer scheme

  from the network                           to the application
 ------------------->  [ free: rwnd | buffered data ]  ------------------>
                        <-------- RcvBuffer -------->

Flow control vs congestion control:

ProtectsThe receiverThe network
Signalrwnd advertised by the receiverInferred from loss (timeouts, dup ACKs) or delay/ECN
Variablerwndcwnd
Sender limitIn-flight data ≤ min(rwnd, cwnd)same

TCP connection management: handshake and close

 Both ends agree to connect and exchange initial sequence numbers with a 3-way handshake; at the end, each direction is closed separately with FIN + ACK.

Why not a 2-way handshake? On a real network messages are delayed, retransmitted and reordered, and neither side can see the other. With only request + accept, a delayed duplicate SYN from an old connection could open a half-open connection the client knows nothing about, or replay old data. The third message proves the client is live and has seen the server’s ISN.

3-way handshake

  1. The server is in LISTEN (passive open). The client starts in CLOSED.
  2. Client sends SYN with a random ISN x → client enters SYN_SENT.
  3. Server replies SYN-ACK (its own ISN y, ACK = x + 1) → server enters SYN_RCVD.
  4. Client sends ACK = y + 1 (may already carry data) → ESTABLISHED; when it arrives, the server is ESTABLISHED too.

Closing the connection

  1. Either side may close first. It sends FIN → FIN_WAIT_1: “I have nothing more to send” (it can still receive).
  2. The other side ACKs → CLOSE_WAIT; it may keep sending data (half-close).
  3. When done, it sends its own FIN → LAST_ACK.
  4. The first side ACKs and waits in TIME_WAIT for 2 × MSL (maximum segment lifetime) before CLOSED; the other side goes straight to CLOSED on receiving that ACK.

Important note: flag RST aborts a connection immediately (no FIN exchange): e.g. a SYN to a closed port gets a RST.

tcp-three-way-handshake

TCP connection lifecycle · states on both ends

Only the side that closes first sits in TIME_WAIT because in case its last ACK is lost and the peer’s FIN comes again, and so old segments of this connection die before the same 4-tuple is reused.

TCP Congestion control

Congestion happens when too many sources sending too much, too fast for the network (not for one receiver — that’s flow control). It shows up as long queueing delays and packet loss at routers. TCP’s answer: each sender keeps a congestion window cwnd, probes upward for bandwidth, and backs off sharply on loss — AIMD, with slow start at the beginning.

Causes and costs (the lecture’s scenarios)

  • As the offered load approaches link capacity, queueing delay grows sharply.
  • Senders must retransmit lost packets, so part of the capacity carries duplicates, not new data.
  • Premature timeouts create unneeded duplicate transmissions.
  • Multi-hop, four-sender scenario: when a packet is dropped at a later router, all upstream capacity used to carry it was wasted. Pushed far enough, throughput collapses (congestion collapse) so-called “the traffic-jam effect”.

Two approaches

End-to-end (classic TCP)Network-assisted
Routers give no explicit signal; the sender infers congestion from loss (timeouts, duplicate ACKs) or rising delayRouters mark packets, e.g. ECN bits in IP/TCP headers, and the receiver echoes the mark back

TCP mechanics

  • Sending rate ≈ cwnd / RTT bytes per second; in-flight data ≤ min(cwnd, rwnd).
  • Slow start: at connection start cwnd = 1 MSS and grows by 1 MSS per ACK → doubles every RTT (1, 2, 4, 8 …). Starts slow, ramps up exponentially, until a loss or until cwnd reaches the threshold ssthresh
  • Congestion avoidance (AIMD): above ssthresh, additive increase of 1 MSS per RTT; on loss, multiplicative decrease (halve).

Reaction to loss

Loss signalMeaningTCP RenoTCP Tahoe (older)
3 duplicate ACKsMild: later segments are still getting throughssthresh = cwnd/2, cwnd = ssthresh (+3 MSS), fast recovery, then congestion avoidancecwnd = 1 MSS, slow start
TimeoutSevere: nothing is getting throughssthresh = cwnd/2, cwnd = 1 MSS, slow startsame

The classic sawtooth: exponential climb, linear probing, halve on duplicate ACKs, fall back to 1 MSS on a timeout.

Beyond Reno: Linux, Windows and macOS default to CUBIC (window grows as a cubic function of time since the last loss, better on fast long-distance links); Google’s BBR estimates bottleneck bandwidth and RTT instead of reacting to loss. Fairness: AIMD pushes competing TCP flows toward an equal share of a bottleneck, but UDP apps without congestion control, or apps opening many parallel TCP connections, grab more.