---
title: 'The Client–Server Model'
source: 'https://academia.sh/en/courses/how-the-internet-works/client-server-model'
course: 'How the Internet Works'
language: en
updated: '2026-08-17T18:07:03+00:00'
license: 'CC BY-SA 4.0'
---

# The Client–Server Model

The distinction between the role that initiates a connection and the one that waits, the concept of a port, and a comparison with the peer-to-peer model.

The previous lesson established how packets are carried across the network and left open
the question of who initiates communication. The network carries both hosts the same
way; the difference between them is not the network's property but that of the programs
running on them.

This lesson defines that difference: why does the program on the machine holding the
`example.test` page wait continuously, while the program on your machine starts talking
whenever it wants? By the end of the lesson, this course's example server will be up and
running.

## The Criterion That Separates the Roles

A **server** is a program that waits on a port and responds to incoming requests. A
**client** is a program that initiates the connection and sends the request.

The only criterion for the distinction is **who initiates the connection**. A common
misunderstanding is treating the server as the "powerful machine" and the client as the
"weak machine." This is not true: the role belongs to the program's behavior, not the
hardware. The same machine can be a server for one program and a client for another;
servers are often clients of other servers.

The roles produce two asymmetries.

**Discoverability.** The client needs to be able to find the server; the server does not
need to know the client. This is why the server's address must be stable and known in
advance. The client's address can be temporary and can change from connection to
connection.

**Availability.** The server must be listening at every moment a request might arrive.
The client, in contrast, runs only at the moment it wants to speak. The server's
requirement to run continuously is the source of the hosting problem covered in later
lessons.

## The Port

An address specifies a machine. But more than one server program can run on a machine at
the same time. Telling which program an incoming packet belongs to requires a second
number: the **port**.

The address points to the building, the port to the apartment inside it. A connection is
defined by four values: source address, source port, destination address, destination
port. As long as that four-tuple is unique, countless connections can coexist between the
same two machines.

Servers listen on well-known numbers; like the client's address, the client's port is
usually a temporary number assigned by the operating system at that moment. Unencrypted
web traffic is listened for on port 80, encrypted traffic on port 443. These numbers are
assigned by a registration authority and stay invisible because they are not written in
the address bar: typing `http://example.test/` assumes 80, and `https://example.test/`
assumes 443.

If a different number is to be used, it is written explicitly in the address. This
course's examples use port 8080; choosing an unprivileged number allows the server to run
without administrator rights.

## Writing a Server

The most certain way to explain what a server does is to run one. The program below
listens on a port, accepts a single connection, prints the bytes it receives, and sends
back a fixed response.

```python
import socket

BODY = b"<!doctype html>\n<html lang=\"en\"><body><h1>Example Page</h1></body></html>\n"

RESPONSE = (
    b"HTTP/1.1 200 OK\r\n"
    b"Content-Type: text/html; charset=utf-8\r\n"
    b"Content-Length: " + str(len(BODY)).encode() + b"\r\n"
    b"Connection: close\r\n"
    b"\r\n"
) + BODY


def run_server(port: int = 8080) -> None:
    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as listener:
        listener.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        listener.bind(("127.0.0.1", port))
        listener.listen(1)
        client, address = listener.accept()
        with client:
            request = client.recv(4096)
            print("--- incoming request ---")
            print(request.decode("utf-8", "replace"), end="")
            print("--- end of request ---")
            client.sendall(RESPONSE)


run_server()
```

The program executes four steps in order, and these four steps are the same in every
server:

1. `bind` — an address and port are claimed.
2. `listen` — the program becomes ready to accept incoming connections.
3. `accept` — the program **waits** until a client connects. Execution stops on this
   line.
4. `recv` / `sendall` — the request is read, the response is written.

The third step is the essence of the role. The server waits without knowing when or who
will connect.

The **socket** in the `socket` calls is the connection's handle on the program's side;
the operating system reads and writes data through this handle.

## Running a Client

While the server runs, a client is run from another terminal. `curl` is used here as the
client. The `--resolve` option manually declares which address the name `example.test`
corresponds to; name resolution is the subject of the next topic, and this option stands
in for it for now. The `-A` option fixes the text the client identifies itself with.

```sh
curl -sv --resolve example.test:8080:127.0.0.1 -A "example-client" http://example.test:8080/
```

The exchange as seen on the client side (`>` shows outgoing, `<` incoming lines):

```
* Added example.test:8080:127.0.0.1 to DNS cache
* Hostname example.test was found in DNS cache
*   Trying 127.0.0.1:8080...
* Connected to example.test (127.0.0.1) port 8080
> GET / HTTP/1.1
> Host: example.test:8080
> User-Agent: example-client
> Accept: */*
> 
* Request completely sent off
< HTTP/1.1 200 OK
< Content-Type: text/html; charset=utf-8
< Content-Length: 74
< Connection: close
< 
{ [74 bytes data]
* Closing connection
<!doctype html>
<html lang="en"><body><h1>Example Page</h1></body></html>
```

Lines starting with `*` are `curl`'s own commentary, not part of the exchange.

On the server side, the bytes the client sent appeared:

```
--- incoming request ---
GET / HTTP/1.1
Host: example.test:8080
User-Agent: example-client
Accept: */*

--- end of request ---
```

The two outputs are two ends of the same exchange. The point to notice is that what the
server receives is not a structured object but plain text. Both the request and the
response consist of bytes; the only thing that sets the rules is that both sides follow
the same format. That format is HTTP, and it is the subject of the third topic.

The address `127.0.0.1` refers to the machine itself; packets sent to this address never
leave the machine. This makes it possible to examine the whole client–server exchange
without needing a real network.

## The Peer-to-Peer Model

Client–server is not the only option. In the **peer-to-peer** model, every host acts as
both client and server: it both receives resources and offers them to others. There is
no privileged machine sitting at the center.

The two models are compared along the same criteria.

| Criterion | Client–server | Peer-to-peer |
|---|---|---|
| Location of the resource | On a known host | Spread across participating hosts |
| Discovery method | The server's address is known | The list of peers is discovered |
| As demand grows | Server load grows | Capacity grows too |
| Single point of failure | The server | None |
| Control and consistency | Single location, easy | Distributed, hard |
| Availability | Depends on the server | Continues if enough peers remain |

The peer-to-peer model's clearest advantage is scaling: every new host that wants the
resource is, at the same time, new capacity able to offer that resource. As demand grows,
so does supply.

It has two difficulties in return. The first is discoverability: without a stable
address, determining who holds a resource is a separate problem. The second is control:
guaranteeing a resource's accuracy, currency, and authorization from a single point is
hard in a distributed structure.

The web is built on the client–server model. The reason is that these two difficulties
directly conflict with the web's basic requirements: a domain name is expected to show
specific content, and who has authority over that content must be clear. A stable name
and stable responsibility require a stable server.

This distinction is not a sharp boundary. Many machines can share the load behind a
single server; a direct connection can be established between two browsers. The roles
are determined by a specific exchange, not by the machines.

## Summary

- The criterion separating client from server is not hardware but who initiates the
  connection.
- The server listens continuously at a stable address; the client's address can be
  temporary.
- The port distinguishes multiple server programs on the same machine; a connection is
  defined by the four-tuple of source and destination addresses and ports.
- A server program follows the `bind`, `listen`, `accept`, and read/write steps; waiting
  at the `accept` call is the essence of the role.
- The request and response are not structured objects but byte sequences that both
  sides interpret the same way.
- The peer-to-peer model gains capacity as demand grows, but the client–server model was
  chosen for the web, which needs a stable address and central control.

## Next Step

It was established that the server has to listen at a stable address; but this server is
not on the same local network as your machine. The packets in between pass through many
independently operated networks that share no common owner. The next lesson covers how
this structure, belonging to no single organization, holds together, and who determines
the path to the `example.test` server.
