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

# Ports and Sockets

Addressing application endpoints, port number ranges, the four-tuple that makes a connection unique, and the distinction between a listening socket and a connection socket.

Throughout the previous topic, the term "port" was used without being defined: in the
translation table's key, in the leasing protocol's numbers 67 and 68, in the destination
server's 443.

The place where the term is defined is the transport layer. The network layer delivers a
packet to the correct **machine** and stops there; yet a single machine has dozens of
programs using the network at the same time. This lesson's question is how it is decided
which program an incoming segment is handed to.

## Multiplexing

A single machine has several network flows at once: a browser talks to several servers, a
mail client runs in the background, a backup tool sends data. All of them share the same IP
address.

The transport layer's first job is to separate these flows. Two fields are placed in the
header for the separation: **source port** and **destination port**. Each is 16 bits; the
value range is between $0$ and $65\,535$.

Handing an incoming segment to the correct flow is called **demultiplexing**; loading
outgoing segments onto a single network-layer flow is called **multiplexing**. This is the
last link in the chain of multiplexing keys introduced in the Encapsulation and Headers
lesson: the Ethernet type field leads to IP, the IP protocol field leads to TCP, the TCP
port number leads to the application.

## Number Ranges

Numbers are divided into three ranges:

| Range | Name | Use |
|---|---|---|
| 0–1023 | Well-known | Standardized services; assignment requires authorization |
| 1024–49151 | Registered | Numbers registered to specific applications |
| 49152–65535 | Dynamic / ephemeral | The range clients use as their source number |

Numbers in the first range cannot be bound without elevated privilege on most operating
systems. This restriction is a security measure: it prevents an unprivileged program from
seizing a standard service's number and impersonating it.

Some well-known numbers:

| Number | Protocol | Service |
|---|---|---|
| 22 | TCP | Secure shell |
| 53 | UDP and TCP | Name resolution |
| 67, 68 | UDP | Address leasing (server, client) |
| 80 | TCP | Resource transfer |
| 443 | TCP and UDP | Secure resource transfer |

It should be noted that numbers are independent of protocol: TCP 443 and UDP 443 are
**separate endpoints**. On the same machine, one can belong to one application and the
other to a different one. There are two separate namespaces.

Numbers are a convention, not a requirement. A service can also run on a different number;
in that case the client needs to know the number explicitly.

## The Four-Tuple That Makes a Connection Unique

A port number alone is not enough to identify a flow. Thousands of clients connect to a
server's port 443 at the same time, and all of them use the same destination number.

What makes a TCP connection unique is the **four-tuple**:

$$
(\text{source address},\ \text{source port},\ \text{destination address},\ \text{destination port})
$$

When the protocol is also counted, it becomes a five-tuple; the translation table's key in
the NAT and PAT lesson was this five-tuple.

**Socket**, at one end, is the address-and-port pair; at the operating-system level it is
the object representing that endpoint. Two sockets form a connection.

The following program makes two connections to the same listening endpoint and prints the
four-tuples from both sides.

```python
import socket
import threading
import time


def server(ready: threading.Event, state: dict) -> None:
    listener = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    listener.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    listener.bind(("127.0.0.1", 9101))
    listener.listen(5)
    state["listener"] = listener.getsockname()
    ready.set()
    for _ in range(2):
        connection, peer = listener.accept()
        state.setdefault("connections", []).append((connection.getsockname(), peer))
        connection.close()
    listener.close()


ready = threading.Event()
state: dict = {}
threading.Thread(target=server, args=(ready, state), daemon=True).start()
ready.wait()
print("listening endpoint:", state["listener"])

for i in range(2):
    client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    client.connect(("127.0.0.1", 9101))
    print(f"client {i + 1}: local={client.getsockname()} remote={client.getpeername()}")
    client.close()

time.sleep(0.2)
for local, peer in state["connections"]:
    print(f"server side: local={local} peer={peer}")
```

Output:

```
listening endpoint: ('127.0.0.1', 9101)
client 1: local=('127.0.0.1', 53044) remote=('127.0.0.1', 9101)
client 2: local=('127.0.0.1', 53045) remote=('127.0.0.1', 9101)
server side: local=('127.0.0.1', 9101) peer=('127.0.0.1', 53044)
server side: local=('127.0.0.1', 9101) peer=('127.0.0.1', 53045)
```

Ephemeral port numbers (`53044`, `53045`) **change on every run**; the operating system
picks them from the ephemeral range. What stays constant is that the two connections
receive different numbers. The four-tuples:

| Connection | Source | Destination |
|---|---|---|
| 1 | `127.0.0.1:53044` | `127.0.0.1:9101` |
| 2 | `127.0.0.1:53045` | `127.0.0.1:9101` |

Three components are shared; only the source port differs — that is what provides the
distinction.

## Listening Socket and Connection Socket

One detail in the output stands out: on the server side, the number `9101` appears in both
the listening socket and the two connection sockets. This is not a conflict, because the
two socket types are looked up by separate keys.

| Socket type | Key | Role |
|---|---|---|
| Listening socket | (local address, local port) | Accepts new connection requests |
| Connection socket | The full four-tuple | Carries data for an established connection |

For an incoming segment, the operating system first looks for a full four-tuple match; if
it does not find one and the segment is a new connection request, it routes the segment to
the listening socket. This two-step lookup is what lets a single listening socket serve an
unlimited number of connections.

The same distinction explains why a server's concurrent capacity is not limited by the
number of ports. The server's number is fixed at a single value; what varies is the client
side's address and number.

On the client side, however, the limit is real: the number of concurrent connections a
single client machine can establish to a single destination is limited by the count of
numbers in the ephemeral range. This is the same capacity limit computed in the NAT and PAT
lesson.

## Endpoints in the Example Network

The flow the course follows is written as a four-tuple as follows:

| Location | Source | Destination |
|---|---|---|
| At the client | `192.168.10.196:49152` | `198.51.100.20:443` |
| After translation | `203.0.113.10:61001` | `198.51.100.20:443` |
| As seen at the server | `203.0.113.10:61001` | `198.51.100.20:443` |

The server never sees the client's real address and number. The translation has changed two
components of the four-tuple, and this change is invisible to both ends.

A flow that stays within the internal network, however, is not subject to translation. A
request from the client in the administration section to the server room's
`192.168.10.226` address travels with the following four-tuple, unchanged along the way:

```
192.168.10.196:49160  ->  192.168.10.226:443
```

## Binding Failures

An application's attempt to bind to a number fails for two reasons.

**Number in use.** Another listening socket is already bound to the same address-and-number
pair. This error is common when a server program restarts; the reason is that the previous
connections have not finished their closing process. The detail of this behavior will be
covered in the TCP lesson. The `SO_REUSEADDR` option in the program above exists precisely
to get past this situation.

**Insufficient privilege.** An unprivileged process cannot bind to a number below 1024.

The address given when binding also matters. A socket bound to `127.0.0.1` is reachable
only from the same machine; a socket bound to `0.0.0.0`, which represents all interfaces,
is also reachable from the network. A service running during local development being
unreachable from the network is most often caused by this distinction.

## Summary

- The transport layer separates flows on the same machine using 16-bit port numbers.
- Numbers are divided into three ranges: well-known (0–1023), registered (1024–49151),
  ephemeral (49152–65535).
- TCP and UDP numbers are separate namespaces; the same number means two separate endpoints
  across the two protocols.
- What makes a connection unique is the four-tuple: source address, source number,
  destination address, destination number.
- A listening socket is looked up only by the local pair, a connection socket by the full
  four-tuple; this is why a single listening socket can serve unlimited connections.
- The address given when binding a socket determines the scope of access: `127.0.0.1` is
  local only, `0.0.0.0` is all interfaces.

## Next Step

The four-tuple names a flow; it says nothing about whether the flow will be reliable. The
network layer can lose a packet, reorder it, or deliver the same packet twice. The next
lesson covers the protocol that solves these three problems at once — its handshake,
sequence numbers, retransmission, and flow control.
