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))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:
- Client → server: SYN, "my bytes start at sequence number x".
- Server → client: SYN-ACK, "mine start at y; I expect your byte x + 1 next".
- 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)Each side ends its half of the connection with a FIN, which the other side acknowledges.
| Feature | UDP | TCP |
|---|---|---|
| connection | none | handshake first |
| reliable, in order | no | yes |
| header | 8 bytes | 20 bytes or more |
| slows down when the network is busy | no | yes (congestion control) |
| used by | DNS, video calls, games | web, 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
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.