Career OS

How A Request Travels

You’ve written @GetMapping("/api/friends") and watched JSON come back, hundreds of times. But between your controller and the screen there’s a machine address, a process selector, a numbered conversation, and data chopped into pieces and reassembled — and when any of it fails, the error message assumes you know all four. This module is the map. Every other module in this track pins to it.

The Goal

By the end of this module you can:

  • Name the four layers a real request passes through, and say which one each common error comes from
  • Explain what an IP address actually identifies — and why “the IP of a website” is sloppy thinking
  • Point at the exact socket your running splitease-api owns, with netstat, and match it to the process
  • Answer the classic interview question: how does one server port serve thousands of connections at once?
  • Say why localhost:8080 works on your laptop and fails on your phone — and what 0.0.0.0 changes
  • Feel the difference between connection refused and a timeout, because you produced both on purpose

The Lesson

Four layers, no dogma

Every network course opens with the OSI model: seven layers, a mnemonic, an exam question. Here’s the honest version — the 7-layer OSI model is interview trivia. The 4-layer reality is what you debug. Nobody has ever fixed a production incident by remembering the Presentation layer.

What actually runs, top to bottom:

LayerIts one jobYou meet it as
Application — HTTPDecide what to sayJSON bodies, headers, status codes — your @GetMapping
Transport — TCPSay it reliably, to the right processports, connection refused, timeouts
Internet — IPRoute packets between machinesIP addresses, tracert hops
LinkMove the actual bitswifi, ethernet cable — you almost never debug here

Each layer only talks to the one directly above and below it. HTTP doesn’t know about routing. IP doesn’t know what a “request” is. That separation is why you can debug them independently — which is the entire method of this track.

When your code sends a request, the data travels down this stack on your machine, across the network at the bottom, and up the stack on the server:

flowchart TD
    A["Your code calls GET on api slash friends"] --> B["HTTP writes the request as structured text"]
    B --> C["TCP chops it into numbered segments addressed to a port"]
    C --> D["IP wraps each segment with source and destination machine addresses"]
    D --> E["Link layer turns packets into wifi signals"]
    E --> F["Routers pass packets hop by hop reading only the IP layer"]
    F --> G["Server unwraps in reverse and Tomcat hands Spring your request"]

Each layer wraps the layer above it like an envelope inside an envelope. Routers in the middle open only the outer IP envelope — they never see your HTTP, never see your JSON. That’s called encapsulation, and it’s why a router in Chennai can forward your payment request without understanding a byte of it.

What an IP address actually identifies

An IP address identifies a network interface on a machine. Not a website. Not a company. A machine’s plug into a network — and one machine can have several (your laptop has a wifi interface, a loopback interface, maybe ethernet, each with its own IP).

The “IP of a website” idea breaks in both directions:

  • Many sites per IP. A single shared host serves hundreds of small sites from one IP. The server tells them apart at the HTTP layer (the Host header — module 04).
  • Many IPs per site. Run nslookup google.com a few times — big services answer with different IPs from different places. One name, a fleet of machines.

So an IP gets your packets to a machine. It says nothing about which program on that machine should receive them. That’s the next problem.

Ports — picking the process

A machine runs dozens of network-capable processes at once. Right now on your laptop: splitease-api, PostgreSQL, your browser, Windows update. When a packet arrives, who gets it?

A port is a 16-bit number (0–65535) that selects the process. The OS keeps a table: “port 8080 → that Java process, port 5432 → PostgreSQL.” An IP without a port is an address without a flat number.

RangeNameReality
0–1023Well-knownReserved for standard services: 80 HTTP, 443 HTTPS, 22 SSH. Binding these needs admin rights.
1024–49151RegisteredWhere app servers live by convention: 8080 Tomcat, 5432 PostgreSQL, 3306 MySQL, 6379 Redis
49152–65535EphemeralHanded out automatically to the client side of connections — your browser is using a few right now

That last row matters: clients get ports too. When your browser calls splitease-api, the OS assigns the browser a random ephemeral port like 51734 for that one connection. Both ends of every connection have an IP and a port.

Sockets — the four-tuple, and the classic interview question

A socket is one end of a connection, and a live connection is identified by four numbers:

(source IP, source port, destination IP, destination port)

Here’s the interview classic: “Your server listens on one port — 8080. How can it serve thousands of clients at once? Don’t they collide?”

They don’t, because the OS distinguishes connections by the whole four-tuple, not the server port alone. Three clients hitting your API:

Source IPSource portDest IPDest port
192.168.1.75173410.0.0.58080
192.168.1.75181210.0.0.58080
203.0.113.404992010.0.0.58080

Same destination, three distinct tuples, three independent connections. Even two tabs of the same browser differ — each got its own ephemeral source port. The server port is a meeting point, not a single-occupancy room.

This is also why netstat output (you’ll run it in Build This) shows one LISTENING socket plus a separate ESTABLISHED line per active client — the listener just accepts newcomers; each accepted connection gets its own socket.

Packets — why data gets chopped up

Networks don’t send your 2 MB JSON response as one blob. Every link has an MTU (Maximum Transmission Unit) — the largest packet it will carry, typically ~1500 bytes on ethernet and wifi. So everything you send is sliced into packets, each routed independently (they can take different paths and arrive out of order), and reassembled at the far end. Whose job is reassembly, ordering, and re-sending the slices that got lost? TCP’s — that machine is the whole of module 02. For now: one request, many packets, any of which can vanish. Every timeout you’ll ever debug starts there.

Private vs public IPs — why your laptop is invisible to the internet

Run ipconfig and you’ll see something like 192.168.1.7. That’s a private address. Three ranges (10.x.x.x, 172.16–31.x.x, 192.168.x.x) are reserved for use inside local networks and are not routable on the internet — millions of homes have a 192.168.1.7 simultaneously, which is exactly the point.

Your router does NAT (Network Address Translation): your whole household shares the router’s one public IP. Outbound works — the router rewrites your private address to its public one and remembers the mapping so replies find their way back. Inbound cold doesn’t: someone on the internet sending packets to “192.168.1.7” sends them nowhere, and packets to your public IP hit a router that has no idea which device you meant.

This is why you can’t just demo splitease-api to a friend by sending them your ipconfig address — and why deploying means renting a machine that has a public IP. That’s week 9 of the sprint; this paragraph is the reason that week exists.

localhost, 127.0.0.1, and the 0.0.0.0 gotcha

localhost resolves to 127.0.0.1 — the loopback interface, a fake network plug wired back into your own machine. Packets to it never touch the wifi card. Fast, private, perfect for development.

But here’s the gotcha you will hit. You start splitease-api, it works at http://localhost:8080, so you grab your phone — same wifi — and type localhost:8080. Dead. Two separate reasons, and you need both:

  1. localhost on the phone means the phone. It’s not a name for “the dev machine” — every device’s localhost is itself. From the phone you’d need the laptop’s wifi IP: http://192.168.1.7:8080.
  2. The bind address decides who’s even allowed in. A server binds its listening socket to an interface. Bound to 127.0.0.1, it accepts connections only from the same machine — the wifi interface never even rings. Bound to 0.0.0.0 (“all interfaces”), it accepts from loopback and wifi. Tomcat binds 0.0.0.0 by default — you’ll verify that with your own eyes in Build This — but PostgreSQL ships bound to localhost only, on purpose, and many tools (React dev servers, databases, docker setups) make you choose. And even with 0.0.0.0, Windows Firewall gets a veto on inbound — the popup you clicked past when you first ran the API was exactly this question.

127.0.0.1 = this machine only. 0.0.0.0 = every interface this machine has. The difference is invisible until the day it costs you an hour.

Where your Spring code sits in all this

Every @GetMapping you wrote in the REST controllers module lives at the very top of this stack. By the time Spring calls your method, Tomcat has already: accepted a TCP connection on the 8080 socket, read packets off it, reassembled them, and parsed the HTTP text into the HttpServletRequest your annotations decorate. Your controller is the last 5% of the journey. When something breaks below that line, no amount of staring at Java will show it — which is why this track exists.

Check The Concept

How This Shows Up At Work

  • The “works on my machine” deploy. A service runs fine locally, then is unreachable the moment it’s containerized — because it bound 127.0.0.1 and the container’s network arrives on a different interface. Engineers lose entire afternoons to this; you now know it’s a one-line bind-address fix.
  • Port 8080 was already in use on startup. Spring refuses to start. The fix isn’t restarting the laptop — it’s netstat -ano | findstr 8080, read the PID, find the zombie process holding the port. You’ll do exactly this in Build This.
  • The interview filter. “Server has one port — how does it handle 10k connections?” is asked precisely because tutorial-taught candidates can’t answer it. The four-tuple answer, with the ephemeral-port detail, signals you understand the layer your framework sits on.
  • The 2am triage fork. An API stops responding. The first diagnostic question is which layer: does the machine resolve (DNS)? Does the port accept (TCP)? Does HTTP answer (app)? Engineers without the layer map restart things at random; engineers with it run three commands and know where the body is buried.
  • Fintech reality. A payment gateway gives you an IP allowlist form. Knowing the difference between your office’s private IP and its public NAT address is the difference between filling that form right and a week of “but it works from my browser” tickets.

Build This

You’re going to inspect your own machine’s wire, watch splitease-api claim its socket, reach a real server across the internet, then break things on purpose and read the wreckage. All in PowerShell, nothing to install.

  1. Find your own addresses. Run:
ipconfig

Find the Wireless LAN adapter Wi-Fi block:

Wireless LAN adapter Wi-Fi:

   IPv4 Address. . . . . . . . . . . : 192.168.1.7
   Subnet Mask . . . . . . . . . . . : 255.255.255.0
   Default Gateway . . . . . . . . . : 192.168.1.1

Your numbers will differ. Read it: the IPv4 address starts with 192.168. — that’s a private address, invisible to the internet. The Default Gateway is your router — the NAT box every outbound packet passes through. Write both down; you’ll see them again in step 4.

  1. Watch your API claim its socket. Start splitease-api (./mvnw spring-boot:run in its folder), wait for Tomcat started on port 8080, then in a second terminal:
netstat -ano | findstr 8080
  TCP    0.0.0.0:8080           0.0.0.0:0              LISTENING       18244
  TCP    [::]:8080              [::]:0                 LISTENING       18244

Read it column by column: protocol TCP; local address 0.0.0.0:8080 — bound to all interfaces (there’s the lesson’s bind address, live — Tomcat chose 0.0.0.0, so your phone could reach this via your wifi IP, firewall permitting); foreign address 0.0.0.0:0 meaning “no one yet, accepting anyone”; state LISTENING; and the PID. The second line is the same socket for IPv6.

  1. Match the PID to the process. Using the PID from your output (mine was 18244):
Get-Process -Id 18244
Handles  NPM(K)    PM(K)      WS(K)     CPU(s)     Id  SI ProcessName
-------  ------    -----      -----     ------     --  -- -----------
    683      45   812340     401224     12.55   18244   1 java

java — your API. The OS port table, confirmed with your own eyes: port 8080 → that process. This exact two-step is how you’ll hunt down every “port already in use” error for the rest of your career.

  1. Cross the internet for real. Now leave your machine:
Test-NetConnection example.com -Port 443
ComputerName     : example.com
RemoteAddress    : 23.192.228.80
RemotePort       : 443
InterfaceAlias   : Wi-Fi
SourceAddress    : 192.168.1.7
TcpTestSucceeded : True

One command, three layers: RemoteAddress proves DNS resolved the name to a machine address. SourceAddress is your private IP from step 1 — your end of the four-tuple (the ephemeral source port is there too, just not shown). TcpTestSucceeded : True means a full TCP handshake completed with a real process listening on port 443, across however many networks sit between you and it.

  1. Count those networks. How many machines did step 4 actually involve?
tracert example.com
Tracing route to example.com [23.192.228.80]
over a maximum of 30 hops:

  1     3 ms     2 ms     2 ms  192.168.1.1
  2    13 ms    11 ms    12 ms  10.50.0.1
  3    19 ms    17 ms    18 ms  broadband-isp-gateway
  ...
  9   112 ms   110 ms   114 ms  23.192.228.80

Hop 1 is your router from step 1. Hop 2 is already inside your ISP — note it’s a 10.x private address; ISPs use NAT layers too. Then watch the milliseconds grow as packets cross cities. Every hop is a router reading only the IP envelope, exactly as the encapsulation diagram promised. Count your hops — that’s how many machines your “simple API call” transits.

Now break it. The point is to feel the two failure shapes this whole track keeps returning to.

  1. Break 1 — connection refused. Aim at your own machine, a port where nothing listens:
Test-NetConnection 127.0.0.1 -Port 9999
WARNING: TCP connect to (127.0.0.1 : 9999) failed
ComputerName     : 127.0.0.1
RemoteAddress    : 127.0.0.1
PingSucceeded    : True
TcpTestSucceeded : False

Notice the speed — it failed instantly. Your OS received the connection attempt, checked its port table, found no process on 9999, and actively replied “refused.” PingSucceeded : True confirms it: the machine is alive and answering; the port is simply closed. Refused is an answer.

  1. Break 2 — timeout. Aim at a real, alive server, on a port its firewall silently drops:
Test-NetConnection example.com -Port 81

Start counting. Nothing… nothing… then after ~20 seconds:

WARNING: TCP connect to (23.192.228.80 : 81) failed
PingSucceeded    : True
TcpTestSucceeded : False

Same False, completely different story. The machine is alive (PingSucceeded : True) but your packets to port 81 vanished — a firewall dropped them without replying, so your machine retried into silence until it gave up. Refused = fast, definitive, somebody answered. Timeout = slow, silent, packets vanished. When production breaks at 2am, this distinction is your first fork in the road — module 02 builds the full decision tree on top of it.

  1. Stop splitease-api (Ctrl+C), run netstat -ano | findstr 8080 again — empty. The socket died with the process. Ports aren’t files; they exist only while a process holds them.

Where to Practice

ResourceWhat to do thereHow long
Cloudflare Learning CenterRead “What is the Internet?” and “What is an IP address?” — solid second pass on today’s layer map20 min
MDN HTTP docsSkim “An overview of HTTP” — just the first half, noticing where TCP and IP appear under it15 min
Your own machineRun netstat -ano with no filter; pick three LISTENING lines and identify each process with Get-Process -Id15 min
httpbin.orgTest-NetConnection httpbin.org -Port 443, then tracert httpbin.org — compare hop count and latency vs example.com10 min

Check Yourself

  1. Name the four working layers, top to bottom, and one error you’d meet at each.
  2. What does an IP address identify — and what are the two ways “one IP = one website” fails?
  3. A packet arrives at a machine running fifty processes. What decides which process receives it?
  4. Both ends of a TCP connection have a port. Where does the client’s port come from?
  5. State the four-tuple, and use it to answer: how does one listening port serve thousands of clients?
  6. Why can’t a friend across the city reach 192.168.1.7:8080, even if your API is running and bound to 0.0.0.0?
  7. Your API works at localhost:8080 on the laptop but not from your phone on the same wifi using the laptop’s IP. Name the two distinct things that could be wrong.
  8. Connection refused arrived in 50ms; a timeout took 21 seconds. Explain what each end actually did in both cases.
Answers
  1. Application/HTTP (a 404, a bad header), Transport/TCP (connection refused, timeout), Internet/IP (no route to host, a dead hop in tracert), Link (wifi down — the one you reboot, not debug).
  2. A network interface on a machine. Fails both ways: many sites share one IP (shared hosting, told apart by the HTTP Host header), and one site spans many IPs (large services answer with different addresses per lookup).
  3. The destination port. The OS keeps a port-to-process table and hands the data to whichever process bound that port.
  4. The OS assigns it automatically from the ephemeral range (49152–65535) when the connection opens — one fresh source port per connection.
  5. (source IP, source port, destination IP, destination port). Every client connection brings a unique source IP + ephemeral port combination, so every tuple is distinct — the OS keeps each connection’s data separate even though the destination port is identical.
  6. Private ranges like 192.168.x.x aren’t routable on the internet — routers refuse to carry them because millions of networks reuse the same addresses. The friend’s packets can never find your laptop; only your router’s public IP exists out there, and it won’t forward inbound traffic unless explicitly told to.
  7. Either they typed localhost on the phone (which means the phone itself — they need the laptop’s wifi IP), or the connection is blocked on arrival: server bound to 127.0.0.1 instead of 0.0.0.0, or Windows Firewall rejecting inbound on 8080.
  8. Refused: packets reached a live machine, the OS found no listener on that port, and actively sent back a rejection — fast and definitive. Timeout: packets were silently dropped (firewall, dead host, wrong network), no reply ever came, and your machine retried into the void until its timer expired.

Explain it out loud: Record a voice note walking one request from your browser to splitease-api and back — name the layer at every step, where the four-tuple is formed, what each router in between can and can’t see. If you stall at any handoff, that section gets a re-read.

Still Unclear?

Copy-paste any of these into Claude — they deepen the model, they don’t do the work for you:

I ran netstat -ano on Windows and I'm pasting three lines of output: [paste].
For each line, tell me what the local address, foreign address, and state mean —
then quiz me on a fourth made-up line and check my reading.
Explain the difference between binding a server to 127.0.0.1 vs 0.0.0.0 vs a
specific interface IP, using my situation: Spring Boot on a Windows laptop,
phone on the same wifi. Don't give me commands — make me predict what each
bind address allows, then confirm or correct.
Quiz me on the TCP four-tuple: give me five scenarios (two browser tabs, a
retried request, two devices behind one NAT, a server-to-database call) and
ask me what the tuple looks like in each. Correct my wrong answers with the
exact rule I violated.

Why AI Can’t Do This For You

AI can recite the four-layer model flawlessly — it’s in every training set. What it can’t do is sit at your machine at the moment of failure and notice that the error came back instantly (refused) rather than after twenty silent seconds (timeout), or that netstat shows the socket bound to 127.0.0.1 when the deploy config swears 0.0.0.0. Those observations are made at a keyboard, by someone who knows what each line of output means. The layer map only pays rent when it’s in your head during the incident.

And there’s a subtler reason: every error message in this stack is written assuming this module’s vocabulary. “Connection refused,” “bind failed: address in use,” “no route to host” — these are precise statements about sockets, ports, and routing. Engineers who lack the vocabulary paste them into a chatbot and relay answers they can’t verify. Engineers who have it read the message and know. That’s the difference this track is built to produce.

Module done? Add it to today’s tracker

Saves your progress on this device.