The Transport Layer: A Practical Overview
Let’s continue with a networking theory, now with the transport layer:
Index:
| # | Topic | Short Description |
|---|---|---|
| 1 | Transport services | Logical communication between processes (network layer = between hosts) |
| 2 | Mux / demux | UDP socket = (dst IP, dst port); TCP socket = 4-tuple |
| 3 | UDP | 8-byte header, checksum, no handshake, no congestion control |
| 4 | Reliable data transfer | Checksum + ACK + sequence numbers + timer = reliability over a lossy channel |
| 5 | Pipelining | Go-Back-N resends everything after the gap; Selective Repeat resends only what’s missing |
| 6 | TCP overview | Byte-stream sequence numbers; ACK = next byte expected (cumulative) |
| 7 | TCP reliability | Adaptive timeout from RTT samples; fast retransmit on 3 duplicate ACKs |
| 8 | Flow control | Receiver advertises rwnd so the sender can’t overflow its buffer |
| 9 | Connection management | 3-way handshake SYN / SYN-ACK / ACK; FIN-based close with TIME_WAIT |
| 10 | Congestion control | Slow 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.
- As you remember from the application layer article Internet offers two transport protocols:
| TCP | UDP | |
|---|---|---|
| Delivery | Reliable, in order | Best effort: may lose, duplicate or reorder |
| Connection setup | Yes (3-way handshake) | None |
| Flow control | Yes | No |
| Congestion control | Yes | No |
| Header | 20 bytes minimum | 8 bytes |
| Not provided by either | Delay guarantees, bandwidth guarantees | same |
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:
- The host receives an IP datagram carrying a source and destination IP address.
- Each datagram carries one transport-layer segment with a source and destination port number.
- The host uses IP addresses + port numbers to pick the socket.
Connectionless demux (UDP):
- The host looks only at the destination port (and destination IP).
- 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.
- 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)
- A TCP socket is identified by the 4-tuple: source IP, source port, destination IP, destination port. All four are used.
- A web server has a separate socket per client connection, all on port 80. With non-persistent HTTP, a new socket per request.
- 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.

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.
- Typically used by DNS, SNMP, DHCP, real-time voice/video (RTP, WebRTC), online games, and QUIC / HTTP/3. Apps that need reliability over UDP build it themselves at the application layer (that’s exactly what QUIC does).
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
- 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.
- 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.
- Optional in IPv4 (0 = not computed), mandatory in IPv6.
So why to chose UDP?
- No connection setup: no handshake RTT before the first byte.
- Simple, stateless: a server can handle far more clients.
- Small header: 8 bytes vs 20+ for TCP.
- 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):
The core idea is to build a reliable channel on top of an unreliable one by adding, step by step, the tools each new failure needs: checksum (bit errors) → ACK/NAK (feedback) → sequence numbers (duplicates) → timer (loss). It matters becasue for example protocols like SMTP, HTTP and file transfer need every byte, but IP can corrupt, lose, reorder and duplicate packets. Reliability must be built into the protocol.
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:
| Version | Channel assumption | Mechanism added | Remaining problem |
|---|---|---|---|
| rdt1.0 | Perfectly reliable | Nothing — just send and deliver | Hypothetical; never true |
| rdt2.0 | Bit errors, no loss | Checksum + 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.1 | Bit errors, no loss | Sequence numbers (0/1 is enough for stop-and-wait); receiver discards duplicates | Still needs NAKs |
| rdt2.2 | Bit errors, no loss | NAK-free: receiver ACKs the last good packet; a duplicate ACK means “resend the next one” | Can’t handle loss |
| rdt3.0 | Bit errors and loss | Countdown timer: no ACK in a “reasonable” time → retransmit | Correct, but stop-and-wait performance is terrible |
rdt3.0 scenarios:
| Scenario | What happens | Why it still works |
|---|---|---|
| (a) No loss | pkt0 → ack0 → pkt1 → ack1 | — |
| (b) Packet lost | Timer expires, sender resends | Receiver never saw it, so no duplicate |
| (c) ACK lost | Timer expires, sender resends; receiver sees a duplicate | Sequence number reveals the duplicate → discard, re-ACK |
| (d) Premature timeout / delayed ACK | Sender resends although the ACK is only late | Receiver 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 ACKs | Cumulative: “everything up to n arrived” | Individual: each correctly received packet |
| Out-of-order packets | Discarded (receiver buffers nothing) | Buffered until the gap is filled |
| Timers | One, for the oldest unACKed packet | One per packet (logically) |
| On timeout | Resend the lost packet and every packet after it | Resend only the packet that timed out |
| Receiver complexity | Minimal | Buffer + reordering logic |
| Waste on a lossy link | High | Low |

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
TCP is a connection-oriented, reliable, in-order byte stream between exactly two endpoints, with pipelining, flow control and congestion control.
The main properties:
| Property | Meaning |
|---|---|
| Point-to-point | One sender, one receiver (no multicast) |
| Reliable, in-order byte stream | No message boundaries; the app reads bytes in the order they were sent |
| Pipelined | Many segments in flight; window size set by congestion + flow control |
| Full duplex | Data flows both ways on the same connection |
| Connection-oriented | Handshake sets up state (sequence numbers, buffers) before data |
| Flow controlled | Sender never overwhelms the receiver’s buffer |
| Congestion controlled | Sender 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
Another example (5 bytes case)
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
| Scenario | Timeline | Outcome |
|---|---|---|
| Lost ACK | Seq=92 (8 B) arrives; ACK=100 is lost; timer fires | Sender resends Seq=92; receiver discards the duplicate and re-ACKs 100 |
| Premature timeout | Seq=92 (8 B) and Seq=100 (20 B) sent; their ACKs are delayed; timer fires | Sender resends Seq=92 only; the late ACK=120 confirms both, so it moves on to byte 120 |
| Cumulative ACK saves a retransmit | ACK=100 is lost but ACK=120 arrives before the timeout | No retransmission — ACK=120 covers bytes 92–11 |
When does the receiver send an ACK? (RFC 1122 / 5681)
| Event at receiver | TCP receiver action |
|---|---|
| In-order segment, all earlier data already ACKed | Delayed 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 ACK | Immediately 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 gap | Immediately 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.

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
rwndreaches 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:
| Protects | The receiver | The network |
| Signal | rwnd advertised by the receiver | Inferred from loss (timeouts, dup ACKs) or delay/ECN |
| Variable | rwnd | cwnd |
| Sender limit | In-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
- The server is in LISTEN (passive open). The client starts in CLOSED.
- Client sends SYN with a random ISN
x→ client enters SYN_SENT. - Server replies SYN-ACK (its own ISN
y, ACK = x + 1) → server enters SYN_RCVD. - Client sends ACK = y + 1 (may already carry data) → ESTABLISHED; when it arrives, the server is ESTABLISHED too.
Closing the connection
- Either side may close first. It sends FIN → FIN_WAIT_1: “I have nothing more to send” (it can still receive).
- The other side ACKs → CLOSE_WAIT; it may keep sending data (half-close).
- When done, it sends its own FIN → LAST_ACK.
- 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 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 delay | Routers mark packets, e.g. ECN bits in IP/TCP headers, and the receiver echoes the mark back |
TCP mechanics
- Sending rate ≈
cwnd / RTTbytes 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 signal | Meaning | TCP Reno | TCP Tahoe (older) |
|---|---|---|---|
| 3 duplicate ACKs | Mild: later segments are still getting through | ssthresh = cwnd/2, cwnd = ssthresh (+3 MSS), fast recovery, then congestion avoidance | cwnd = 1 MSS, slow start |
| Timeout | Severe: nothing is getting through | ssthresh = cwnd/2, cwnd = 1 MSS, slow start | same |

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.
That is it for today, next time see you in an article about Network Layer 🙂
