Networking & HTTP

Networking Fundamentals

15 min Lesson 1 of 13

Networking Fundamentals

Every Java networking API — from raw Socket to the modern HttpClient — sits on top of the same foundational concepts: the TCP/IP protocol suite, ports, and the client/server model. Before touching a single line of networking code you must understand what happens at each layer, because those mental models determine how you design connections, handle errors, and tune performance in production systems.

The TCP/IP Protocol Suite

TCP/IP is not a single protocol; it is a four-layer stack. From lowest to highest:

  • Network Access (Link) Layer — physical transmission: Ethernet frames, Wi-Fi packets. Java never touches this directly.
  • Internet (IP) Layer — routes packets across networks using IP addresses. Each packet travels independently and may arrive out of order or not at all. IP is best-effort.
  • Transport Layer — delivers data to the correct process. Two protocols live here: TCP and UDP.
  • Application Layer — protocols your application implements on top of transport: HTTP/2, TLS, WebSocket, SMTP, and so on.
IP addresses identify machines; ports identify processes. When a packet arrives at a host, the OS reads the destination port number from the Transport header and hands the data to whichever process is listening on that port. Without ports, a single machine could run only one networked program at a time.

TCP: Reliable, Ordered, Connection-Oriented

TCP (Transmission Control Protocol) adds three guarantees on top of raw IP:

  1. Reliable delivery — lost packets are automatically retransmitted.
  2. Ordered delivery — bytes arrive in exactly the order they were sent.
  3. Flow & congestion control — the sender is throttled to match what the receiver and network can handle.

These guarantees come from the three-way handshake that establishes a connection before any application data flows:

// What happens under the hood when you call new Socket("host", port): // // Client Server // |--- SYN (seq=x) ------------>| "I want to connect" // |<-- SYN-ACK (seq=y,ack=x+1)-| "OK, I acknowledge; here is my seq" // |--- ACK (ack=y+1) ---------->| "Acknowledged — connection established" // | // | ... application data flows both ways ... // | // |--- FIN ----------------------| "I am done sending" // |<-- FIN-ACK -----------------| // |--- ACK ----------------------| (four-way close)

Every Socket you create in Java triggers this handshake. Every socket.close() triggers the four-way close. These round trips have latency cost — that is why HTTP/1.1 introduced keep-alive and HTTP/2 introduced multiplexing: to reuse connections rather than rebuilding them for each request.

Ports and the Well-Known Port Assignments

The 16-bit port number ranges (0–65535) are partitioned by convention:

  • 0–1023: Well-known ports — assigned by IANA. HTTP = 80, HTTPS = 443, SSH = 22, SMTP = 25. Binding to these requires root / administrator privilege on most OSes.
  • 1024–49151: Registered ports — used by well-known application protocols but do not require elevated privilege. MySQL = 3306, PostgreSQL = 5432, Redis = 6379.
  • 49152–65535: Dynamic / ephemeral ports — the OS assigns these automatically to the client side of a connection. When your Java client connects to a server, the OS picks an ephemeral port as the source port so the server can send replies back to that specific socket.
Choose a port outside 0–1023 for your own servers during development so you never need to elevate privileges. Standard practice is to make the port configurable via an environment variable or configuration file rather than hard-coding it.

The Client/Server Model

All TCP-based Java networking follows the same structural split:

  • Server — binds to a specific port on a specific interface, enters a listening state, and accepts incoming connection requests. It is passive.
  • Client — knows the server's IP address and port, initiates the connection actively, and communicates once the handshake is complete.

In Java, ServerSocket represents the listening endpoint; each call to serverSocket.accept() blocks until a client connects and then returns a regular Socket representing that specific connection. The server typically hands that socket to a thread (or virtual thread in Java 21+) so it can accept the next client immediately.

// Conceptual sketch — just to see the API shape // (Full implementation covered in Lesson 2) // SERVER SIDE ServerSocket server = new ServerSocket(8080); Socket client = server.accept(); // blocks until a client connects InputStream in = client.getInputStream(); OutputStream out = client.getOutputStream(); // ... read request, write response ... client.close(); // CLIENT SIDE Socket socket = new Socket("localhost", 8080); // triggers the TCP handshake InputStream in = socket.getInputStream(); OutputStream out = socket.getOutputStream(); // ... write request, read response ... socket.close();

IP Addresses: IPv4 and IPv6

Java's InetAddress class abstracts both address families:

import java.net.InetAddress; import java.net.UnknownHostException; public class AddressDemo { public static void main(String[] args) throws UnknownHostException { // Resolve a hostname to its IP addresses InetAddress[] addresses = InetAddress.getAllByName("api.example.com"); for (InetAddress addr : addresses) { System.out.println(addr.getHostAddress()); // "93.184.216.34" (IPv4) or IPv6 } // Loopback (always available, never leaves the machine) InetAddress loopback = InetAddress.getLoopbackAddress(); System.out.println(loopback); // 127.0.0.1 or ::1 // Wildcard — bind to ALL interfaces (common for servers) // Use null or "0.0.0.0" / "::" when constructing ServerSocket } }
DNS resolution is blocking and can fail. InetAddress.getByName() performs a DNS lookup on the calling thread. In high-throughput servers this stalls threads. The modern HttpClient handles this asynchronously internally; with raw sockets you must off-load DNS lookups or use an async DNS library.

Sockets are I/O Resources — Always Close Them

Both Socket and ServerSocket implement AutoCloseable. Always use try-with-resources. An unclosed socket holds an OS file descriptor and a TCP connection slot; leaking sockets under load will exhaust both.

// Correct pattern: try-with-resources guarantees close() on any exit path try (ServerSocket server = new ServerSocket(8080); Socket client = server.accept()) { // communicate with client ... } // both sockets closed here even if an exception is thrown

TCP Versus UDP: When Does the Reliability Tax Matter?

TCP's reliability guarantees are not free: retransmission adds latency, and head-of-line blocking means a lost packet stalls all subsequent data on that connection. UDP trades reliability for speed:

  • No connection setup — send the first packet immediately.
  • No retransmission — lost datagrams are gone.
  • No ordering — packets may arrive in any sequence.

Use UDP when timeliness beats correctness: live video/audio streaming, online game state updates, DNS queries, and QUIC (the transport under HTTP/3). For REST APIs, database connections, file transfer, and most enterprise workloads, TCP is the right default.

Summary

TCP/IP is a layered stack where IP routes packets and TCP makes delivery reliable and ordered at the cost of handshake latency. Ports bind a transport endpoint to a specific process. The client/server model splits networking into an active connector and a passive listener — mirrored exactly by Socket and ServerSocket in Java. These are not just theoretical concepts: every timeout you set, every connection pool you size, and every retry policy you design is a direct consequence of understanding how TCP works at this level.