The Network Surface - Hosts, Ports, and Local Servers
Context
A network is machines sending each other messages. One machine has an address, and because a single machine runs many programs that all want to talk, each conversation is tagged with a number so the messages reach the right one. One side asks, the other answers, and that exchange is the whole shape of it.
Your own machine counts as one of those machines, which is why a notebook server, a local model endpoint, and a database on your laptop are all reached the same way an external service is. Downloading a dataset, calling a hosted model, or serving your own trained model behind an endpoint are all this same ask-and-answer round trip with different participants.
The errors are distinctive and constant. Something is already using the number your server wanted, or nothing is listening at the address you asked, and both are unreadable until you know what an address and a number actually refer to. Everything so far has stayed on one machine, this lesson is the first time a process is listening for something from outside it.
Goal
- Describe a network as machines exchanging messages, and send a message to a machine to confirm it answers.
- Distinguish a remote host from localhost (your own machine) and address each correctly.
- Explain how a port lets many network programs share one machine, and see which process is listening on which port.
- Identify the client and the server in an exchange, and run both on your own machine.
- Trace a single request-response round trip, one ask out, one answer back, on your own machine.
Prerequisites
Both are optional and the lesson assumes neither. They are here if you would rather see the picture before you build it.
- YouTube - "How the Web Works: Clients, Servers, Browsers, and Files Explained" (VirtualAddiction). A visual mental model of clients, servers, requests, and responses.
- MDN Web Docs - "Overview of HTTP" (read only the "Client: the user-agent" and "The Web server" sections). A clear, openly licensed description of the request-response exchange.
Table of Contents
- Network
- Host and localhost
- Port
- Client-Server Model
- Request-Response
Theory
Network
What. A network, in the plain model we need, is a set of machines that can exchange messages. One machine sends a message, another receives it and can send one back. The messages are bytes (the same unit from earlier lessons) traveling between machines instead of staying inside one.
Why. No single machine has everything, the data you want, the model that answers, the page you load all live somewhere else. A network is what lets your machine ask another for something and get an answer. Strip away the cables, Wi-Fi, and protocols and the essence is simple: a message goes out, a message comes back. Holding that plain picture keeps the rest of the lesson from feeling like magic, everything that follows is just how those messages are addressed and organized.
When. You are using a network any time a program reaches beyond your own machine: loading a web page, downloading a dataset, calling an online service. You also use one, quietly, when two programs on the same machine talk to each other over network rules, which is exactly how you will test things in this lesson.
# 'ping' sends small messages to a machine and waits for replies.
# It's the simplest proof that a network round trip works.
ping -c 3 localhost # '-c 3' = send 3 messages, then stopping -c 3 localhost sends three little "are you there?" messages and prints a reply line for each. Each reply is the round trip made visible: your message went out and an answer came back. That out-and-back is the entire network model in its smallest form.
# The replies report round-trip time, how long each message took.
# Tiny numbers (well under 1 ms) here because it never left your machine.
ping -c 3 127.0.0.1 # '127.0.0.1' means 'this machine'127.0.0.1 is a numeric address meaning "this machine" (next subtitle), so these messages never leave your computer and come back almost instantly. The point is not the speed but the shape: send, receive, repeat. A network is built entirely out of these exchanges.
DON'T:
- DON'T assume "network" means "the internet." A network can be two programs on one machine talking over network rules, the internet is just an enormous network of networks.
- DON'T picture messages as anything exotic. They are bytes in motion between machines, the same bytes you have been moving through streams and files all along.
Host and localhost
What. A host is a machine on the network, anything that can send or receive messages. To reach one you name it, by an address. localhost is the special name for your own machine, its numeric address is 127.0.0.1, and messages sent there loop back to you without ever leaving the computer.
Why. A message needs a destination, and the host is that destination, "which machine do I send this to?" localhost matters enormously for learning and development: it lets you be both the sender and the receiver, so you can run a server and talk to it on the same computer with nothing else involved. Every server you build will first be tested against localhost before anyone else ever connects. It is the safe practice ground where you control both ends.
When. You address a real host (by name or address) when you want another machine, a website, an API, a database server. You address localhost whenever the program you are talking to is running on the same machine you are typing on: testing your own server, running a local tool that listens for connections, or following this lesson.
# 'hostname' prints THIS machine's name on the network.
hostname # e.g. machine
# 'localhost' and '127.0.0.1' both mean 'this machine', a loopback to you.
ping -c 2 localhost # by name
ping -c 2 127.0.0.1 # by numeric addresshostname tells you what this machine calls itself. The two pings show that localhost and 127.0.0.1 are two spellings of the same idea, "me", and both loop straight back. Anything you send to localhost is received by your own machine.
# A remote host is named the same way, just with a different address.
# (Reaches a public host, requires internet. 'localhost' needs none.)
ping -c 2 example.com # messages go to ANOTHER machine and backPointing ping at example.com sends the messages to a different machine somewhere on the internet, which replies. The mechanism is identical to the localhost case, send and receive, only the destination host changed. That sameness is the lesson: local and remote differ only in which host you name.
DON'T:
- DON'T confuse
localhostwith the internet.localhostnever leaves your machine, it cannot reach another computer and another computer cannot reach yourlocalhost. - DON'T assume every host is always reachable. A machine that is off, firewalled, or not running the program you want will not answer, a silent or failed
pingmeans "no reply," not "you typed it wrong."
Port
What. A port is a numbered slot (from 0 to 65535) that a process listens on to receive network messages. A machine has a few addresses but thousands of ports, so many network programs can run on it at once, each claiming its own port. The address picks the machine, the port picks the server on it.
Why. A host runs many network programs simultaneously, a web server, a database, your own test server. If a message only named the machine, the machine would not know which program it was for. The port is that finer address: "send this to the server listening on port 8000." This ties straight back to processes from Section 1, a server is just a process that has claimed a port and is waiting there for messages. One port holds one server at a time, and the port is how the OS routes an incoming message to the right running program.
When. You specify a port whenever you connect to a particular service on a host: web traffic conventionally uses port 80, secure web 443, and development servers often use 8000 or 8080. You choose a port when you start your own server and need to tell it where to listen.
# Start a server process that LISTENS on port 8000. The '&' backgrounds
# it so your prompt returns, otherwise it
# would hold the terminal in the foreground.
python3 -m http.server 8000 & # serve on port 8000, backgroundedpython3 -m http.server 8000 launches a tiny web server as a process that claims port 8000 and waits there for messages. The trailing & runs it in the background, and without it the server would occupy your terminal in the foreground. That backgrounded process now owns port 8000.
# 'ss -tlnp' lists processes LISTENING on ports: 't'=TCP, 'l'=listening,
# 'n'=numeric, 'p'=show the process ('PID'). Find your server on 8000.
# macOS has no 'ss': run lsof -iTCP:8000 -sTCP:LISTEN -P instead.
ss -tlnp | grep 8000 # keep only the line for port 8000
# LISTEN 0 ... 0.0.0.0:8000 ... users:(("python3",pid=7012,...))ss -tlnp shows every process currently listening on a port, and piping into grep 8000 isolates yours. The output ties the whole stack together: a port (8000), a process name (python3), and its PID (7012 here, a different number on every run), exactly the identifiers Section 1 established. The 0.0.0.0 in front of the port means the server is listening on every four-number address this machine has, 127.0.0.1 among them. On macOS the same server binds ::, every address in its IPv6 form, and lsof -iTCP:8000 -sTCP:LISTEN -P shows the listener where ss would, since macOS ships no ss. A port is held by one server at a time, a running process you can see, address, and stop.
# Done with it? Stop the server like any process: by 'PID', or 'Ctrl-C' if
# it were in the foreground. This frees port 8000 again.
kill 7012 # replace with the 'PID' that 'ss' showedBecause the server is just a process, you stop it by handing kill its PID (or Ctrl-C a foreground one). Stopping it releases port 8000 so something else may use it. Ports are claimed while a server holds them and freed when it ends.
DON'T:
- DON'T try to start two servers on the same port. The second fails with "Address already in use", a port is held by one server at a time, so free it (
killthe first) or pick another port. - DON'T imagine a port as a physical thing. It is a number the OS uses to route a message to the right server, nothing plugs into it.
Client-Server Model
What. The client-server model splits a network conversation into two roles. The server is a process that waits, listening on a port, and answers requests. The client is the process that initiates, it reaches out to a server and asks for something. The server never speaks first, the client always starts.
Why. Networking needs a clear "who does what," and this division supplies it. The server is the always-on responder (a website, a database, a model serving predictions), the client is the occasional asker (your browser, a script, curl). Framing it this way explains the asymmetry you will see everywhere: one side is a long-lived process bound to a port waiting patiently, the other connects only when it wants something and disconnects after. Your python3 -m http.server is a server, curl is a client.
When. You run a server when you want to offer something over the network continuously, serve files, answer API calls, return model predictions. You act as a client when you want to consume something, fetch a page, download data, send input to a service and read what comes back. The same machine can run both, which is exactly how you test.
# THE SERVER: a process that waits on port 8000 and answers requests.
python3 -m http.server 8000 & sleep 1 # start it, backgrounded, then 'sleep 1' waits a second while it claims its port
# THE CLIENT: 'curl' reaches out to that server and asks for something.
curl http://localhost:8000 # ask the server on 'localhost:8000'The first line starts the server, it sits on port 8000 doing nothing until contacted. The second line runs curl as a client: it connects to localhost on port 8000 and asks for the default page. The server, seeing the request arrive, answers. Notice both run on your machine: localhost lets one process serve and another consume over the network, no second computer needed.
echo "epoch=1 loss=0.42" > somefile.txt # a file for the server to hand back
# The URL encodes WHERE: 'scheme://host:port/path'
# http -> the rules to speak
# localhost -> which host (this machine)
# 8000 -> which port (which server)
curl http://localhost:8000/somefile.txt # ask for a specific pathA URL bundles everything the client needs to find the right responder: the host names the machine, the port names the server on it, and the path names what on that server. This single line is the client-server model in miniature, one side encoded the address of the other and reached out.
DON'T:
- DON'T expect the server to contact you. By definition it waits, if nothing connects to it, it simply sits idle. The client must initiate.
- DON'T assume client and server are different machines. Both roles run happily on one computer via
localhost, the roles are about who asks vs. who answers, not about location.
Request-Response
What. A request-response is the basic unit of client-server communication: the client sends one request, and the server sends back one response. The request says what the client wants, the response carries the answer (and a status saying how it went). One ask, one answer.
Why. This pairing is the rhythm of almost all networked programs. It is predictable and self-contained: every response is tied to a request that caused it, so you can always reason about cause and effect. Understanding it lets you read what actually crossed the wire, the request line your client sent and the response line the server returned, instead of treating the exchange as a black box. Much later you will start a small program on your own machine that waits for questions and sends back answers, and everything here is the vocabulary for that. You are learning the shape by hand now so it is already familiar when you come to build it.
When. Every time a client talks to a server you are doing request-response: loading a page (request the HTML, receive it), submitting a form (send the data, receive a result), calling an API (send inputs, receive outputs). You think about it explicitly when something goes wrong and you need to see whether the request was malformed or the response reported an error.
# '-v' (verbose) shows the RAW request and response lines.
# Lines starting with '>' are what the CLIENT sent (the request).
# Lines starting with '<' are what the SERVER returned (the response).
curl -v http://localhost:8000
# > GET / HTTP/1.1 <- the request: "give me /"
# > Host: localhost:8000
# < HTTP/1.0 200 OK <- the response status: success
# < Content-type: text/html; charset=utf-8curl -v peels back the curtain. The > lines are the request the client sent, here GET /, meaning "give me the root resource." The < lines are the response the server sent back, beginning with a status (200 OK = success). You are seeing the one-ask-one-answer exchange in its actual text form. The lines starting with * are curl's own notes on connecting, and a Connection refused among them is harmless: curl first tries ::1, this machine's address in the newer IPv6 numbering, which 0.0.0.0 does not cover, then connects on 127.0.0.1.
# Ask for something that doesn't exist: the RESPONSE still comes,
# but its status reports the problem. One request, one response,
# even when the answer is "not found".
curl -v http://localhost:8000/missing.txt
# < HTTP/1.0 404 File not found <- still a response, status says '404'Requesting a missing file still produces a response, the server answers 404, "not found." This reinforces the model: a request always yields exactly one response, and the response's status (200 success, 404 missing, and others) tells you how it went. The exchange completes either way.
# Done asking? Stop this server the way you stopped the first one: read its
# 'PID' from 'ss', then 'kill' it. This frees port 8000 again.
ss -tlnp | grep 8000 # the 'PID' is on this line
kill 7012 # replace with the 'PID' that 'ss' showedDON'T:
- DON'T expect a response with no request. The server answers what it is asked, it does not push unrequested data in this model. No request, no response.
- DON'T read a
404(or other non-200 status) as "the network failed." The round trip succeeded, you got a response, the status is the server's report about your specific request, not a connection error.
Additional - the whole lesson is one URL
Every idea here lives inside a single line like curl http://localhost:8000/data.csv. The network carries the bytes, localhost is the host (which machine), 8000 is the port (which server), curl is the client and the thing on 8000 is the server, and the GET-and-reply is the request-response. When you much later start your own small program that waits on a port and answers questions, it is the identical anatomy, a host, a port, a waiting server, and one request-response at a time. You will have seen every piece by hand first.
Lab Exercises
Lab Exercise 1: Prove the Round Trip
Print your machine's network name, then send a few messages to your own machine using both the name localhost and the numeric address 127.0.0.1, confirming replies come back. In comments, state what a reply proves and why the round-trip times are so small.
Lab Exercise 2: Stand Up a Server, Ask It a Question
Start a web server listening on a port of your choice (backgrounded so your prompt returns), then use a client to request its default page over localhost. In comments, label which command is the server and which is the client, and which role initiates the exchange.
Solution 1: Prove the Round Trip
# This machine's network name.
hostname # e.g. machine
# Send messages to yourself two ways, both loop back.
ping -c 3 localhost # by name
ping -c 3 127.0.0.1 # by numeric addresshostname reports what this machine calls itself. Both pings send three messages to your own machine and print a reply for each, and a reply is proof the round trip works: the message reached a listener and an answer returned. The times are a fraction of a millisecond because the messages never left the computer, localhost/127.0.0.1 loop straight back.
DON'T:
- DON'T read a reply as "the internet works." A
localhostreply only proves your machine can talk to itself, no external network is involved. - DON'T treat
localhostand127.0.0.1as different destinations. They are two names for the same "this machine" target.
Solution 2: Stand Up a Server, Ask It a Question
# SERVER: a process that waits on port 8080 and answers. '&' backgrounds it.
python3 -m http.server 8080 & sleep 1
# CLIENT: 'curl' initiates, it reaches out and asks for the default page.
curl http://localhost:8080 # request the root page from the server
# Done? Stop the server, freeing port 8080 again.
ss -tlnp | grep 8080 # the 'PID' is on this line
kill 7012 # replace with the 'PID' that 'ss' showedThe first command is the server: it claims port 8080 and waits, sent to the background so your prompt is free. The second is the client: curl initiates the exchange by connecting to localhost:8080 and requesting the root page, which the server answers. The client always starts, the server only responds.
DON'T:
- DON'T forget the
&and then wonder why your prompt is stuck, a foreground server holds the terminal until you stop it. - DON'T mistake the server's own lines for
curl's output. The backgrounded server still writes to your terminal, aServing HTTP on ...banner as it starts and request lines such as"GET / HTTP/1.1" 200 -as it answers, printed just before the pagecurlfetched.
Theory Assessment
Conceptual reinforcement, not a coding section. All four questions with their hints first, then all four answers.
Q1: What a network minimally is, and the address that never leaves
State the minimal description of a network and what the messages crossing it actually are. Then explain what localhost, numerically 127.0.0.1, refers to, and why it is so useful for testing a server you are building yourself.
Hint: The messages are the same unit you have already moved through files and streams. For the second half, ask how far a message sent there actually travels.
Q2: Why an address is not enough, and who owns a port
Explain what problem the port solves that a host address alone cannot. Then, using ss -tlnp, explain how a port relates to the process and PID ideas from Section 1.
Hint: One machine runs many network programs at once, so ask what the address alone fails to distinguish. For the second half, read what else appears on the same line as the port.
Q3: Who speaks first, and the shape of one exchange
State which role starts a network exchange and which one waits, with an everyday example of each. Then trace what happens when a client runs curl http://localhost:8000, in order, and say what a 200 status means.
Hint: One of the two roles is patient by design and idles until spoken to. For the trace, count the messages, because there are exactly two.
Q4: Two errors that are not the errors you think
A client requests a missing path and gets 404. Explain why that outcome proves the network worked rather than that it failed. Then: you start a server on port 8000 and it works, start a second server on port 8000 and it fails with "Address already in use". Explain why, and give two ways to fix it. Say plainly what that message is not telling you about your code.
Hint: For the first, ask what had to happen for a status code of any kind to reach you. For the second, ask what a port can hold at one time, and notice the message never mentions your program.
Answer 1:
A network is minimally two or more machines that can exchange messages: one sends, another receives, and either can reply. The messages are bytes traveling between machines, the same unit you have already moved through streams and files. Cables, Wi-Fi and protocols are only how those byte-messages get delivered.
localhost, numerically 127.0.0.1, refers to your own machine, and messages sent there loop back without ever leaving the computer. That is what makes it invaluable for testing: you run the server and the client on one machine, exercising a server you are building before any second computer is involved. You control both ends and depend on nothing external.
DON'T equate "network" with "the internet". DON'T assume localhost can reach other computers, another host's localhost is its machine, not yours.
Answer 2:
A host address gets a message to the right machine, but a machine runs many network programs at once, a web server, a database, your test server, and the address alone cannot say which one a message is for. The port is the finer address that selects the server: deliver this to whatever is listening on port 8000. Address picks the machine, port picks the program on it.
ss -tlnp | grep 8000 # 0.0.0.0:8000 ... ("python3",pid=7012,...)That one line carries the port, the program name and the PID together, which is the point: a port does not float free. A server is a process that has claimed one, so the port, the process and its PID are bound, and that is exactly why kill PID frees the port.
DON'T think one machine can run only one network program. DON'T treat a port as independent of a server, it is held by one running server at a time and released when that server ends.
Answer 3:
The client initiates, reaching out and asking. The server waits, listening on a port, and only answers. The server never speaks first. An everyday client is a browser or curl fetching a page, and an everyday server is a website or database sitting running, responding to whoever connects.
For the trace: the client sends one request, GET /, meaning "give me the root resource", to the server listening on localhost:8000. The server receives it, finds the resource, and sends back one response, a status line plus the content. A 200 means success, the request was understood and the resource is being returned. One ask, one answer, paired.
DON'T expect the server to start the conversation, without a client it simply idles. DON'T imagine unrequested extra responses, each request yields exactly one.
Answer 4:
Both of these read as failures and neither one is a failure of the thing you would blame.
A 404 means the client sent a request and the server sent a response back. The round trip completed, so the network, the address, the port and the server process all worked. 404 is the server's verdict on that specific path, which is a completely separate question from whether the connection succeeded. A status code of any kind is proof of connectivity, because something had to be listening, parse your request and compose a reply in order to produce it.
"Address already in use" fails because a port is held by exactly one server at a time, and the first server already owns 8000. The OS refuses to let a second process claim the same slot. Two fixes: free the port by stopping the first server with kill, or give the second server a different port such as 8001.
ss -tlnp | grep 8000 # find who holds 8000, the PID is on that line
PID=7012 # put the PID you just read here
kill "$PID" # free it... OR start the new server on ':8001'What that message is not telling you is anything about your code. It is one of the most misread errors there is, because it arrives the instant you launch and looks like a startup crash. Nothing you wrote is wrong. Something else, very often a server from an earlier session you never shut down, is holding the port, and ss -tlnp names it in one command.
DON'T conflate a non-200 status with a failed connection. DON'T start editing your program because of "Address already in use", find the holder first.
Assignment
Assignment: Anatomy of a Request
Run a server, then make a verbose client request and annotate (in comments) which lines are the request and which are the response, including the status. Make a second request to a non-existent path and capture the changed status. Finally, run two servers at once on two different ports and show, with a port listing, that both are listening simultaneously. In comments, explain why two servers cannot share one port but can use two.
Hints:
curl -v URLshows>request lines and<response lines, a missing path returns a non-200 status.- Start one server on 8000 and another on 8001 (each
&), thenss -tlnp | grep -E '8000|8001'to see both listening at once.
Solution:
# Run the first server (backgrounded) so a client can talk to it.
python3 -m http.server 8000 & sleep 1
# Verbose request: '>' lines are the REQUEST (client), '<' lines the RESPONSE.
curl -v http://localhost:8000
# > GET / HTTP/1.1 <- request line
# < HTTP/1.0 200 OK <- response status: success
# A path that doesn't exist: one response still comes, status changes to '404'.
curl -v http://localhost:8000/missing.txt
# < HTTP/1.0 404 File not found <- response status: not found
# Run a SECOND server on a DIFFERENT port (each needs its own).
python3 -m http.server 8001 & sleep 1
# Show BOTH ports listening at once. '-E' enables an OR pattern in 'grep'.
ss -tlnp | grep -E '8000|8001'
# 0.0.0.0:8000 ... ("python3",pid=7012,...)
# 0.0.0.0:8001 ... ("python3",pid=7090,...)
# Stop both servers, freeing 8000 and 8001 again.
kill 7012 7090 # replace with the two PIDs that 'ss' showedThe first python3 -m http.server 8000 & stands up a server to interrogate. curl -v exposes the raw exchange: > lines are the request the client sent, < lines the response the server returned, beginning with a status, 200 OK for the root page, then 404 File not found for the missing path, which is still one complete response. The second server claims port 8001, and ss -tlnp | grep -E '8000|8001' shows both listening at once: two python3 processes, two PIDs, two ports. Two servers cannot share one port because a port is held by exactly one server at a time, but giving each its own port lets both run side by side without colliding.
DON'T try to start the second server on 8000 expecting it to "share." It fails with "Address already in use," because one port belongs to one server, distinct servers need distinct ports (here 8000 and 8001).
Additional Resources
Optional, and nothing later depends on either. Worth a look only if this lesson left you curious.
- YouTube - "Network Ports Explained: A Beginner's Guide" (NetWork From Home). Reinforces the host-plus-port addressing model with visuals, a natural complement to this lesson.
- MDN Web Docs - "HTTP response status codes". Where the
200/404statuses you saw expand into the full, openly documented set, the next layer beyond this lesson.
Summary
Everything in this lesson collapses into a single mental image: two processes exchanging messages, one asking and one answering, addressed by a machine and a slot on that machine. You started from the plainest possible idea of a network, bytes going out and coming back, and added just enough structure to make it precise. A host names the machine, localhost is the machine under your own hands, the place where you get to be both sides at once. A port names one server on that machine at a time, which is why a single computer can run a web server, a database, and your own experiment without any of them colliding. And the client-server split, with its one-request-one-response rhythm, names the roles and the beat of the conversation. None of it is magic once you have watched the request leave and the response arrive in plain text.
The reason this matters reaches well past the terminal. Much later you will start a small program on your own machine that waits for questions and sends back answers, and when you do, every word here will already be yours: it will listen on a port, it will be a server, the thing that contacts it will be a client, and each exchange will be one request and one response. This lesson is the vocabulary for that future program, learned in advance and by hand. Having stood up a server, found it by its port and PID, asked it a question, and read its reply, you will recognize the shape of every networked interaction you meet, and you will know that a "not found" answer is not a mystery and a busy port is not a wall, they are just the system telling you, precisely, what happened.
Cheat Sheet
Part A - Reference Table
| Concept | Means | See it with | Notes |
|---|---|---|---|
| Network | machines exchanging messages | ping HOST |
messages are bytes, out and back |
| Host | a machine on the network | hostname, ping HOST |
named by address/name |
| localhost / 127.0.0.1 | this machine (loopback) | ping localhost |
never leaves your computer |
| Port | numbered slot a process listens on | ss -tlnp, on macOS lsof -iTCP:8000 -sTCP:LISTEN -P |
0-65535, one server per port |
| Server | process that waits and answers | python3 -m http.server PORT |
long-running, bound to a port |
| Client | process that asks | curl http://HOST:PORT |
initiates the exchange |
| Request | the client's one ask | curl -v (> lines) |
e.g. GET / |
| Response | the server's one answer | curl -v (< lines) |
starts with a status (200, 404) |
Part B - Compact Code Block
ping -c 3 localhost # prove a round trip (messages out + back)
hostname # this machine's network name
python3 -m http.server 8000 & sleep 1 # SERVER: listens on port 8000
ss -tlnp | grep 8000 # see the port -> process + 'PID'curl http://localhost:8000 # CLIENT: one request -> one response
curl -v http://localhost:8000 # show raw request ('>') and response ('<')
kill 7012 # use the 'PID' that 'ss' showed, freeing the port