---
title: TCP
source: 'https://academia.sh/en/courses/network-models/tcp'
course: 'Network Models and Protocols'
language: en
updated: '2026-08-17T18:07:13+00:00'
license: 'CC BY-SA 4.0'
---

# TCP

Three-way handshake, sequence and acknowledgement numbers, computing the retransmission timer, flow control, and the connection state machine.

The previous lesson established the four-tuple that names a flow. Naming says nothing about
whether the flow will be reliable: the network layer can lose a packet, reorder it, deliver
the same packet twice, and feed the receiver faster than it can process.

**TCP (Transmission Control Protocol)** takes on all four of these problems at once and
offers the application a single abstraction: an ordered, loss-free, duplicate-free byte
stream. This lesson's question is which mechanisms keep that promise.

## Promises Made

| Promise | How it is kept |
|---|---|
| Delivery | Unacknowledged data is retransmitted |
| Order | Every byte gets a sequence number, the receiver reorders |
| No duplication | A second copy at the same sequence number is discarded |
| Flow control | The receiver announces how many bytes it can accept |
| Integrity | The checksum is computed together with the pseudo header |

What is not promised must also be known. TCP does **not** preserve message boundaries: an
application's three separate write calls can arrive on the other side merged into a single
read. It gives no upper bound on delay; in the event of loss, retransmission noticeably
increases delay. Nor does it guarantee that the other side has processed the data; an
acknowledgement shows only that the data has reached the transport layer's buffer.

## Header

The TCP header is 20 bytes without options.

| Field | Size | Function |
|---|---|---|
| Source port | 2 bytes | Sending endpoint |
| Destination port | 2 bytes | Receiving endpoint |
| Sequence number | 4 bytes | The position of this segment's first byte in the stream |
| Acknowledgement number | 4 bytes | The number of the next byte expected |
| Data offset | 4 bits | Header length (in 32-bit words) |
| Flags | 8 bits | SYN, ACK, FIN, RST, PSH, URG, and congestion bits |
| Window | 2 bytes | The number of bytes the receiver can accept |
| Checksum | 2 bytes | Computed over the header, payload, and pseudo header |
| Urgent pointer | 2 bytes | Meaningful only together with the URG flag |
| Options | 0–40 bytes | MSS, window scale, timestamp, selective acknowledgement |

Computing the checksum over the **pseudo header** means the source and destination IP
addresses are also factored in. The purpose is detecting a segment delivered to the wrong
machine. A side effect of this, as seen in the NAT and PAT lesson, is that an address
translator must recompute the checksum.

## Three-Way Handshake

The connection is established before any data is sent. Setup takes three segments:

| Step | Direction | Flags | Carries |
|---|---|---|---|
| 1 | Client → Server | SYN | The client's initial sequence number, MSS, and options |
| 2 | Server → Client | SYN + ACK | The server's initial sequence number, acknowledgement of the client's |
| 3 | Client → Server | ACK | Acknowledgement of the server's sequence number |

The necessity of three steps is symmetric: **each direction is established separately.**
TCP is bidirectional; each direction has its own sequence-number series. The client
announces its own number and has it acknowledged, and the server does the same. Because one
of the two symmetric exchanges (the server's acknowledgement together with its own
announcement) is merged into a single segment, the total comes to three segments.

The **initial sequence number** is not zero; it is chosen at random. The reason is twofold.
Delayed segments from an old connection must not be mixed into a new connection established
with the same four-tuple. The second is security: if the number were predictable, an
outside party could inject segments into the flow that appear valid.

The handshake carries a round-trip-time cost. At least one full round trip is waited out
before any data is sent; in short exchanges this cost can make up the largest part of the
total time.

## Sequence and Acknowledgement Numbers

TCP assigns a sequence number not to the segment but to the **byte**. A segment's sequence
number is the position, within the stream, of the first byte it carries.

A flow with initial number 1000 and MSS 1460:

| Segment | Sequence number | Range covered |
|---|---|---|
| 1 | 1000 | 1000 – 2459 |
| 2 | 2460 | 2460 – 3919 |
| 3 | 3920 | 3920 – 5379 |
| 4 | 5380 | 5380 – 6839 |

The acknowledgement number is the number of the next byte expected, and it is
**cumulative**: an acknowledgement of 6840 means everything up to 6839 has been received.

Cumulative acknowledgement has a consequence. If the second segment is lost and the third
and fourth arrive, the receiver is forced to hold the acknowledgement number at 2460 —
because there is a gap starting at 2460. It cannot report that it received the third and
fourth segments.

This shortcoming is fixed by the **selective acknowledgement** option: the receiver
additionally lists the contiguous ranges it has received. The sender can then retransmit
only the segment that is actually missing; without the option, it may be forced to
retransmit everything after the gap.

The SYN flag in the handshake and the FIN flag at closing each consume one sequence number.
Even though they carry no data, they need to be acknowledged; consuming a sequence number
is what makes that possible.

## Retransmission

Every segment sent is kept in a buffer until it is acknowledged. If no acknowledgement
arrives, the segment is retransmitted. How long to wait is decided by the **retransmission
timeout (RTO)**.

A fixed duration cannot be chosen: the round-trip time on the same connection varies along
the way. If the duration is chosen too short, unnecessary retransmissions occur and load an
already congested network further; if it is chosen too long, loss is noticed late and
throughput drops.

The solution is to produce a running estimate from measured round-trip times:

$$
\text{SRTT} \leftarrow (1-\alpha)\,\text{SRTT} + \alpha R
$$

$$
\text{RTTVAR} \leftarrow (1-\beta)\,\text{RTTVAR} + \beta \lvert \text{SRTT} - R \rvert
$$

$$
\text{RTO} = \text{SRTT} + 4 \cdot \text{RTTVAR}
$$

Here $R$ is the latest measurement, $\alpha = 1/8$, and $\beta = 1/4$. The second term
accounts for the **variability** of the duration: on a stable path RTO approaches the
measurement, on a fluctuating path it moves away from it.

```python
ALPHA, BETA, K = 1 / 8, 1 / 4, 4

measurements = [100.0, 104.0, 96.0, 180.0, 102.0, 98.0]   # ms

srtt = measurements[0]
rttvar = measurements[0] / 2
rto = srtt + K * rttvar
print(f"{'measurement':>11s} {'SRTT':>8s} {'RTTVAR':>8s} {'RTO':>8s}")
print(f"{measurements[0]:11.2f} {srtt:8.2f} {rttvar:8.2f} {rto:8.2f}")

for r in measurements[1:]:
    rttvar = (1 - BETA) * rttvar + BETA * abs(srtt - r)
    srtt = (1 - ALPHA) * srtt + ALPHA * r
    rto = srtt + K * rttvar
    print(f"{r:11.2f} {srtt:8.2f} {rttvar:8.2f} {rto:8.2f}")
```

```
measurement     SRTT   RTTVAR      RTO
     100.00   100.00    50.00   300.00
     104.00   100.50    38.50   254.50
      96.00    99.94    30.00   219.94
     180.00   109.95    42.52   280.01
     102.00   108.95    33.87   244.44
      98.00   107.58    28.14   220.15
```

The 180 ms jump in the fourth row alone has pushed the average to 110 ms, and pushed the
variability from 30 to 42.5; RTO rises from 220 ms to 280 ms. With the following stable
measurements, the value comes back down.

When a timeout occurs, RTO **doubles** and the segment is retransmitted. On consecutive
timeouts the duration grows exponentially. This behavior prevents retransmission traffic
from making the problem worse when the network is not responding.

There is a second mechanism for cases where waiting out the timeout is expensive. When the
receiver gets a segment that is out of order, it repeats the same acknowledgement number; if
the sender receives the same acknowledgement three times, it retransmits the segment
without waiting for the timeout. This is called **fast retransmit** and works together with
the congestion behavior in the next lesson.

## Flow Control

If the sender transmits faster than the receiver can process, the receiver's buffer
overflows and data is dropped. **Flow control** prevents this: the receiver announces, with
every acknowledgement, how many bytes it can accept. This value is called the **receive
window**.

The amount of data the sender can transmit without acknowledgement cannot exceed the
window. This has a direct throughput consequence:

$$
\text{max throughput} = \frac{\text{window}}{\text{RTT}}
$$

```python
for window, rtt in ((65535, 0.020), (65535, 0.100), (262144, 0.100)):
    print(f"window {window:7d} bytes, RTT {rtt*1000:5.0f} ms -> max {window*8/rtt/1e6:8.2f} Mb/s")
```

```
window   65535 bytes, RTT    20 ms -> max    26.21 Mb/s
window   65535 bytes, RTT   100 ms -> max     5.24 Mb/s
window  262144 bytes, RTT   100 ms -> max    20.97 Mb/s
```

The window field is 16 bits; its largest value is 65,535 bytes. On a high-latency path this
value caps throughput at 5 Mb/s — regardless of the connection's capacity. The problem is
solved with the **window scale** option: a shift amount is announced during the handshake,
and the window value is multiplied by $2^k$. The largest shift is 14, which takes the
window to just under a gibibyte.

When the receiver's buffer fills, a window of zero is announced and the sender stops. When
the receiver frees up space, it sends a window update; if this update is lost, the
connection locks up. To prevent this, the sender regularly sends one-byte **probe**
segments.

Flow control watches only the **receiver's** capacity. The path's capacity is a separate
problem and is the subject of the next lesson.

## State Machine

A TCP endpoint transitions among defined states.

| State | Meaning |
|---|---|
| CLOSED | No connection |
| LISTEN | The server is waiting for incoming requests |
| SYN-SENT | The client has sent SYN and is waiting for a reply |
| SYN-RECEIVED | The server received SYN and sent SYN+ACK |
| ESTABLISHED | Data exchange can take place |
| FIN-WAIT-1 / FIN-WAIT-2 | The wait of the side that initiated closing |
| CLOSE-WAIT | The other side closed, the local application has not closed yet |
| LAST-ACK | The final acknowledgement is awaited |
| TIME-WAIT | Closing is complete, delayed segments are awaited |

Closing, unlike setup, takes **four** segments, and the two directions are closed
separately. When one side sends FIN, it announces only that the data it has to send is
finished; the other side can continue sending data. This state is called **half-closed**.

Connections that stay in `CLOSE-WAIT` for a long time are the signature of an application
bug: the other side has closed, and the local application has forgotten to close the
socket.

The `TIME-WAIT` state is held for a period on the side that initiated closing. It has two
reasons. If the final acknowledgement is lost, the other side repeats the FIN, and the
state needs to remain in place so it can be answered. The second is preventing delayed
segments belonging to this connection from mixing into a new connection established with
the same four-tuple.

A server getting an "address in use" error when it restarts stems from this state; the
`SO_REUSEADDR` option from the previous lesson loosens this restriction.

The **RST** flag follows a separate path: it terminates the connection instantly, without a
handshake. This is the response given to a segment arriving at a closed port and to
connections whose state has become corrupted.

## Summary

- TCP offers an ordered, loss-free, duplicate-free byte stream; it does not preserve message
  boundaries and gives no upper bound on delay.
- The handshake is three segments because the two directions are established separately;
  the initial sequence number is chosen at random to prevent old segments from mixing in
  and to prevent injection.
- The sequence number is assigned to the byte; the acknowledgement is cumulative and cannot
  advance once a gap forms — selective acknowledgement fixes this shortcoming.
- RTO is computed from a running average and its variability; it doubles on timeout, and
  three duplicate acknowledgements trigger fast retransmit.
- The receive window limits throughput by $\text{window}/\text{RTT}$; at 65,535 bytes and
  100 ms this limit is 5.24 Mb/s and is overcome with the window scale option.
- Closing is four segments and the directions are closed separately; `TIME-WAIT` is held for
  a lost final acknowledgement and for delayed segments.

## Next Step

Flow control protects the receiver's capacity; it says nothing about the path's capacity.
If the receiver announces a one-megabyte window, the sender will fill it regardless of the
network's condition. The next lesson covers, by calculation, how the sender estimates the
network's capacity, how it reads loss as a signal, and the window's effect on throughput.
