Ports, UDP and TCP

IP gets a packet to the right computer. The transport layer gets the data to the right program on it, using port numbers. A web server listens on port 80 (HTTP) or 443 (HTTPS); your browser picks a free port of its own for each connection. An IP address plus a port, such as 93.184.216.34:443, is a socket address.

well_known = {20: "FTP data", 22: "SSH", 25: "SMTP email", 53: "DNS", 80: "HTTP", 443: "HTTPS"}

def describe(port):
    if port in well_known:
        return well_known[port]
    if port < 1024:
        return "well-known (system) port"
    if port < 49152:
        return "registered port"
    return "dynamic port, picked by a client"

for port in [443, 53, 8080, 51234]:
    print(port, ":", describe(port))
Open in compiler

UDP: just send it

UDP adds ports and a checksum to IP, and nothing else. No connection, no order, no resending: a lost datagram is gone. That sounds bad, but it is exactly right when late data is useless or the program handles loss itself: DNS questions, voice and video calls, online games. (HTTP/3 uses UDP and does its own reliability on top.)

TCP: a reliable byte stream

TCP gives programs a reliable, ordered stream of bytes over an unreliable network. It numbers every byte (sequence numbers), the receiver confirms what it got (acknowledgements, ACKs), and anything not confirmed in time is sent again (next lesson).

A TCP connection starts with the three-way handshake:

  1. Client → server: SYN, "my bytes start at sequence number x".
  2. Server → client: SYN-ACK, "mine start at y; I expect your byte x + 1 next".
  3. Client → server: ACK, "I expect your byte y + 1 next".

The starting numbers are random, which makes it hard for an attacker to fake packets for someone else's connection. After the handshake, every ACK number means "I have received every byte before this one":

client_seq, server_seq = 1000, 5000          # random starting numbers

print("client -> SYN       seq", client_seq)
print("server -> SYN-ACK   seq", server_seq, "ack", client_seq + 1)
client_seq += 1                             # a SYN uses up one number
server_seq += 1
print("client -> ACK       seq", client_seq, "ack", server_seq)

for size in [100, 100, 50]:                 # the client sends 250 bytes
    print("client -> data      seq", client_seq, "(" + str(size), "bytes)")
    client_seq += size
    print("server -> ACK       ack", client_seq)
Open in compiler

Each side ends its half of the connection with a FIN, which the other side acknowledges.

FeatureUDPTCP
connectionnonehandshake first
reliable, in ordernoyes
header8 bytes20 bytes or more
slows down when the network is busynoyes (congestion control)
used byDNS, video calls, gamesweb, email, file transfer

Common mistakes

  • Thinking sequence numbers count segments. TCP counts bytes: after 100 bytes starting at 1001, the next byte is 1101.
  • Thinking the ACK number is the last byte received. It is the next byte expected.
  • Thinking UDP is "broken TCP". It is a deliberate choice for programs that value speed over guaranteed delivery.

Quick check

1. What tells the transport layer which program should get the data?
2. What is the second message of the three-way handshake?
3. A segment carries bytes 1001 to 1100. Which ACK number confirms it?
4. Which program is better served by UDP?
My notes

Write down what you want to remember from this lesson. Notes are saved on this device as you type, and appear on your dashboard.