Free lesson · Computer Networking
Server socket creation (simplified)
From Host-to-Host to Process-to-Process
The Network Layer (IP) solves the problem of getting packets from one machine to another machine across the internet. But that is not enough. A single machine runs dozens - sometimes hundreds - of applications simultaneously. Your laptop might have a web browser, a Slack client, a Spotify stream, an SSH session, and a background software update all communicating over the network at the same time. When a packet arrives at your machine, IP has delivered it to the correct host. But which application should receive it?
The Transport Layer solves this problem. It extends the Network Layer's host-to-host delivery into process-to-process delivery - ensuring that each packet reaches the correct application on the correct machine. It is the layer that makes it possible for multiple applications to share a single network connection without interfering with each other, and it provides the reliability and ordering guarantees that most applications depend on.
The Transport Layer sits between the Application Layer (which produces data - HTTP requests, database queries, video streams) and the Network Layer (which moves packets across the internet). It provides the interface that applications use to send and receive data over the network. When you write socket.connect() in your code, you are interacting with the Transport Layer.
The two dominant transport protocols are TCP (Transmission Control Protocol) and UDP (User Datagram Protocol). TCP provides reliable, ordered, connection-oriented delivery. UDP provides fast, unordered, connectionless delivery. This module dives deep into TCP - the protocol that carries the vast majority of internet traffic, including web browsing, API calls, database queries, email, file transfers, and most other applications. UDP is covered in the next module.
Port Numbers - The Transport Layer's Addressing System
IP addresses identify machines. Port numbers identify applications on those machines. Together, an IP address and a port number form a socket address that uniquely identifies a specific application on a specific machine anywhere on the internet.
Port numbers are 16-bit unsigned integers, ranging from 0 to 65,535. This gives each host up to 65,536 possible ports - enough for many simultaneous connections but not unlimited, which is why port exhaustion is a real operational concern in high-connection-count environments like load balancers and microservice gateways. Ports are divided into three ranges:
| Range | Name | Purpose |
|---|---|---|
| 0–1,023 | Well-Known Ports | Reserved for standard services. Assigned by IANA. Require root/admin privileges to bind on most operating systems. |
| 1,024–49,151 | Registered Ports | Used by specific applications. Registered with IANA but not as strictly controlled. |
| 49,152–65,535 | Dynamic/Ephemeral Ports | Used by the OS for temporary client-side connections. Assigned automatically when a client initiates a connection. |
Some well-known ports every engineer should memorize:
| Port | Protocol | Service |
|---|---|---|
| 22 | TCP | SSH (Secure Shell) |
| 25 | TCP | SMTP (Email sending) |
| 53 | TCP/UDP | DNS (Domain Name System) |
| 80 | TCP | HTTP (Web traffic) |
| 443 | TCP | HTTPS (Encrypted web traffic) |
| 3306 | TCP | MySQL |
| 5432 | TCP | PostgreSQL |
| 6379 | TCP | Redis |
| 8080 | TCP | HTTP alternative (common for dev servers) |
| 27017 | TCP | MongoDB |
When your browser connects to https://google.com, it creates a TCP connection from your machine's IP address on an ephemeral port (say, 192.168.1.10:52431) to Google's server on port 443 (142.250.80.46:443). The combination of source IP, source port, destination IP, and destination port - the 4-tuple - uniquely identifies this specific connection among all the connections on the internet.
Multiplexing and Demultiplexing
The Transport Layer performs two critical operations:
Multiplexing (at the sender): Multiple applications on the same machine each generate data. The Transport Layer collects data from each application, attaches the correct source and destination port numbers, and passes the resulting segments to the Network Layer for transmission. Multiple application streams are multiplexed into a single network connection.
Demultiplexing (at the receiver): When segments arrive at the destination machine, the Transport Layer examines the destination port number and delivers each segment to the correct application. Multiple incoming streams are demultiplexed to their respective applications.
This is why port numbers exist - they are the demultiplexing keys that tell the Transport Layer which application should receive each incoming segment.
graph TD
subgraph Sender["Sending Host"]
APP1["Web Browser<br/>Port 52431"]
APP2["Slack<br/>Port 52432"]
APP3["SSH<br/>Port 52433"]
APP1 --> MUX["Transport Layer<br/>MULTIPLEXING<br/>(attach port numbers)"]
APP2 --> MUX
APP3 --> MUX
MUX --> NET["Network Layer<br/>(send packets)"]
end
subgraph Receiver["Receiving Host"]
NET2["Network Layer<br/>(receive packets)"] --> DEMUX["Transport Layer<br/>DEMULTIPLEXING<br/>(read port numbers)"]
DEMUX --> SRV1["Web Server<br/>Port 443"]
DEMUX --> SRV2["Slack Server<br/>Port 443"]
DEMUX --> SRV3["SSH Server<br/>Port 22"]
end
style APP1 fill:#4f46e5,stroke:#4338ca,color:#fff
style APP2 fill:#7c3aed,stroke:#6d28d9,color:#fff
style APP3 fill:#8b5cf6,stroke:#7c3aed,color:#fff
style MUX fill:#a78bfa,stroke:#8b5cf6,color:#fff
style NET fill:#c4b5fd,stroke:#a78bfa,color:#000
style NET2 fill:#c4b5fd,stroke:#a78bfa,color:#000
style DEMUX fill:#a78bfa,stroke:#8b5cf6,color:#fff
style SRV1 fill:#4f46e5,stroke:#4338ca,color:#fff
style SRV2 fill:#7c3aed,stroke:#6d28d9,color:#fff
style SRV3 fill:#8b5cf6,stroke:#7c3aed,color:#fff
This diagram shows multiplexing and demultiplexing. On the sending host, three applications - a web browser on port 52431, Slack on port 52432, and SSH on port 52433 - each send data to the Transport Layer. The Transport Layer multiplexes them by attaching port numbers and passes the segments to the Network Layer. On the receiving host, the Network Layer delivers incoming packets to the Transport Layer, which demultiplexes them by reading the destination port number and delivering each segment to the correct application: web server on port 443, Slack server on port 443, or SSH server on port 22.
The Socket Abstraction
Applications do not interact with the Transport Layer directly through raw segments and port numbers. Instead, they use the socket API - a programming interface that abstracts the complexities of network communication into a set of familiar operations: create, bind, listen, connect, send, receive, close.
A socket is an endpoint for communication. It is identified by a combination of IP address and port number. When a server application calls bind(port 443) and listen(), it creates a socket that accepts incoming connections on port 443. When a client calls connect(server_ip, 443), it creates a socket that initiates a connection to the server.
The socket API was originally developed for BSD Unix in 1983 (the "Berkeley sockets" API) and has been adopted by virtually every operating system and programming language since. Whether you write network code in Python, Java, Go, Rust, or C, you use some form of the socket API. The actual system calls differ slightly, but the conceptual model is universal:
# Server socket creation (simplified)
import socket
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # Create TCP socket
server.bind(('0.0.0.0', 8080)) # Bind to port 8080 on all interfaces
server.listen(5) # Start listening, queue up to 5 connections
while True:
client_socket, address = server.accept() # Accept incoming connection
data = client_socket.recv(1024) # Read data from client
client_socket.send(b'Hello!') # Send data to client
client_socket.close() # Close connection
SOCK_STREAM creates a TCP socket (stream-oriented, reliable, ordered). SOCK_DGRAM creates a UDP socket (datagram-oriented, unreliable, unordered). This single parameter determines which transport protocol your application uses - and consequently whether you get reliability guarantees or raw speed.
Connection-Oriented vs Connectionless
TCP is connection-oriented: before any data can be exchanged, the client and server must establish a connection through a handshake (the three-way handshake covered in the next topic). The connection maintains state (sequence numbers, window sizes, congestion parameters) that both sides track for the duration of the communication. When communication is finished, the connection is explicitly closed through a teardown process. This connection state is what enables TCP to provide reliability, ordering, and flow control - but it also means TCP incurs latency overhead (at least one round trip for the handshake) and consumes memory on both endpoints to maintain the state.
UDP is connectionless: there is no handshake, no connection state, and no teardown. The sender simply sends datagrams to the destination address and port. There is no guarantee that the datagrams arrive, arrive in order, or arrive at all. The sender does not even know if the destination exists or is listening. This simplicity is what makes UDP faster than TCP - no handshake latency (saving at least one round-trip time), no per-connection state overhead (the OS does not need to maintain connection tables), and no retransmission delays (lost data is simply gone, not re-sent).
The choice between TCP and UDP is one of the most fundamental design decisions in network application development. Most applications use TCP because reliability is more important than speed - a web page with missing bytes is broken, a database query with lost data is corrupt. Applications where timeliness matters more than completeness - real-time video (where a dropped frame is better than a delayed frame that arrives after the viewer has moved on), DNS queries (where retransmission at the application level is cheaper than TCP connection overhead), and online gaming (where low latency matters more than perfect delivery of every position update) - use UDP.
TCP and UDP Header Overview
At the Transport Layer, data is organized into segments (TCP) or datagrams (UDP). Each segment or datagram includes a header that carries the information the Transport Layer needs to perform multiplexing, demultiplexing, and (for TCP) reliability and flow control.
The TCP Header
The TCP header is a minimum of 20 bytes and contains critical fields:
| Field | Size | Purpose |
|---|---|---|
| Source Port | 16 bits | Identifies the sending application |
| Destination Port | 16 bits | Identifies the receiving application |
| Sequence Number | 32 bits | Position of this segment's data in the byte stream |
| Acknowledgment Number | 32 bits | Next byte expected from the other side |
| Data Offset | 4 bits | Header length (in 32-bit words) |
| Flags (SYN, ACK, FIN, RST, PSH, URG) | 6 bits | Control flags for connection management |
| Window Size | 16 bits | Flow control - how much data the receiver can accept |
| Checksum | 16 bits | Error detection for the entire segment |
| Urgent Pointer | 16 bits | Used with the URG flag for priority data |
The TCP header's complexity reflects the protocol's ambition: reliable, ordered, flow-controlled, congestion-aware delivery. Every field serves a purpose that you will encounter in the topics that follow - sequence numbers for ordering, acknowledgment numbers for reliability, window size for flow control, and flags for connection management.
The UDP Header
The UDP header is only 8 bytes - dramatically simpler:
| Field | Size | Purpose |
|---|---|---|
| Source Port | 16 bits | Identifies the sending application |
| Destination Port | 16 bits | Identifies the receiving application |
| Length | 16 bits | Total datagram length |
| Checksum | 16 bits | Error detection (optional in IPv4, mandatory in IPv6) |
UDP's simplicity is its strength. No sequence numbers, no acknowledgments, no window sizes, no connection state. The application gets raw speed at the cost of having no delivery guarantees. Applications that use UDP must implement their own reliability mechanisms if they need them - or accept that some data may be lost.
TCP vs UDP - A First Look
This module focuses on TCP. The next module covers UDP. Here is a preview of the key differences:
| Feature | TCP | UDP |
|---|---|---|
| Connection | Connection-oriented (handshake required) | Connectionless (no handshake) |
| Reliability | Guaranteed delivery with acknowledgments | Best-effort, no delivery guarantee |
| Ordering | Data delivered in order | No ordering guarantee |
| Flow control | Yes (sliding window) | No |
| Congestion control | Yes (slow start, AIMD) | No |
| Overhead | 20-byte minimum header | 8-byte fixed header |
| Use cases | Web, APIs, databases, email, file transfer | Video streaming, gaming, DNS, VoIP |
graph LR
subgraph TransportChoice["Transport Protocol Decision"]
direction TB
Q["Does your application<br/>need reliable, ordered<br/>delivery?"]
Q -->|"Yes"| TCP["TCP<br/>Web, APIs, databases,<br/>file transfers, email"]
Q -->|"No - speed and<br/>low latency matter<br/>more"| UDP["UDP<br/>Video, gaming, DNS,<br/>VoIP, real-time data"]
end
style Q fill:#4f46e5,stroke:#4338ca,color:#fff
style TCP fill:#22c55e,stroke:#16a34a,color:#fff
style UDP fill:#7c3aed,stroke:#6d28d9,color:#fff
This diagram shows the transport protocol decision. The central question is whether the application needs reliable, ordered delivery. If yes, TCP is the choice, used for web, APIs, databases, file transfers, and email. If speed and low latency matter more than perfect delivery, UDP is the choice, used for video streaming, gaming, DNS, VoIP, and real-time data.
Interview: Transport Layer knowledge appears in every System Design interview:
"What protocol does your service use?" - Almost always TCP (via HTTP/HTTPS). Know why: reliability, ordering, congestion control. If you choose UDP, you must justify it - what makes this use case different enough to give up reliability?
"What port does your database run on?" - PostgreSQL: 5432. MySQL: 3306. Redis: 6379. MongoDB: 27017. Knowing these by heart demonstrates operational fluency.
"Can two applications on the same server use the same port?" - No, not with the same protocol and IP. A port can only be bound by one process at a time (unless using
SO_REUSEPORTfor load balancing). This is why deploying multiple services on the same host requires different ports or a reverse proxy."What is the difference between a socket and a port?" - A port is a number (0-65535). A socket is an endpoint identified by the combination of IP address and port. Multiple sockets can use the same port on the same machine - a server listening on port 443 creates a new socket for each accepted client connection, all sharing port 443 on the server side but differentiated by the client's IP and port.
What Comes Next
The next topic covers the TCP three-way handshake - the sequence of SYN, SYN-ACK, and ACK messages that establishes a TCP connection. Understanding the handshake explains why TCP connections have latency overhead, how sequence numbers are initialized, and what happens during the critical first milliseconds of every TCP connection you will ever make.
Exercise: Starter: Run
netstat -tuln(Linux) orlsof -i -P(Mac) on your machine to see all active listening sockets. Which ports are open? Can you identify which application is listening on each port? Try running a simple Python HTTP server (python3 -m http.server 8080) and verify it appears in the listing.Intermediate: Write a simple TCP echo server and client in Python. The server should listen on port 9999, accept a connection, read data, echo it back, and close the connection. The client should connect, send "Hello", print the response, and close. Run both and observe the connection. Then modify the client to connect from two different terminals simultaneously - what happens?
Advanced: Research the
SO_REUSEPORTsocket option. How does it allow multiple processes to bind to the same port? How do modern web servers like Nginx useSO_REUSEPORTto distribute incoming connections across worker processes? What is the difference betweenSO_REUSEADDRandSO_REUSEPORT, and when would you use each? How does this relate to the "thundering herd" problem in multi-process servers?