Network Protocols: TCP, UDP, IP, DNS, HTTP/HTTPS & OSI/TCP-IP Models
This guide is a comprehensive introduction to network protocols — covering TCP, UDP, IP, DNS, HTTP/HTTPS, the OSI model, and the TCP/IP stack with practical examples.
In a Nutshell
Network protocols define how computers communicate. TCP/IP is the foundational protocol stack, the OSI model describes seven layers, HTTP/HTTPS enable web communication, and DNS translates domain names into IP addresses.
Quick Reference
Network protocols are standardized rules and conventions that govern data communication between computers on a network.
Key protocols:
TCP (Transmission Control Protocol)
- Type: Connection-oriented, reliable
- Features: Three-way handshake, flow control, error correction
- Use cases: Web, email, file transfer
- Ports: 80 (HTTP), 443 (HTTPS), 25 (SMTP)
UDP (User Datagram Protocol)
- Type: Connectionless, unreliable
- Features: Simple, fast, no guarantees
- Use cases: Streaming, gaming, DNS
- Ports: 53 (DNS), 123 (NTP), 67 (DHCP)
IP (Internet Protocol)
- Type: Packet switching, routing
- Versions: IPv4 (32-bit), IPv6 (128-bit)
- Features: Addressing, fragmentation
- Use cases: Foundation of the internet
DNS (Domain Name System)
- Type: Name resolution
- Function: Domain → IP address
- Record types: A, AAAA, MX, NS, CNAME
- Port: 53 (UDP/TCP)
HTTP/HTTPS (Hypertext Transfer Protocol)
- Type: Application protocol for the web
- Methods: GET, POST, PUT, DELETE
- Status codes: 200 OK, 404 Not Found, 500 Server Error
- Ports: 80 (HTTP), 443 (HTTPS)
Exam Essentials
These points form the backbone of IT certification exams (IHK AP1/AP2). Exams don’t just ask you to define terms — you need to explain differences, match use cases, and connect concepts.
- Network protocols: Rules for computer-based communication. Why it matters: Without standardized protocols, different systems couldn’t talk to each other. Protocols define format, sequence, error handling, and confirmation.
- TCP: Connection-oriented, reliable, three-way handshake (SYN → SYN-ACK → ACK). Why it matters: TCP guarantees complete, accurate data delivery. In exams, you need to explain the handshake and know when to use TCP (web, email, FTP).
- UDP: Connectionless, fast, unreliable. Why it matters: UDP sends without establishing a connection and without waiting for acknowledgment. In exams, contrast it with TCP and name applications where speed beats reliability (DNS, VoIP, gaming, streaming).
- IP: Packet switching, routing, IPv4 (32-bit) vs IPv6 (128-bit). Why it matters: IP is the foundation of the internet. You should explain the difference between IPv4 (limited addresses, NAT required) and IPv6 (virtually unlimited addresses) and understand how routers forward packets.
- DNS: Name resolution, domain → IP address. Why it matters: DNS translates human-readable names into machine addresses. In exams, explain the hierarchical structure (root → TLD → authoritative) and record types (A, AAAA, MX, CNAME).
- HTTP/HTTPS: Web communication, request/response. Why it matters: Explain the methods (GET, POST, PUT, DELETE) and status codes (200, 404, 500), and distinguish between HTTP (port 80, unencrypted) and HTTPS (port 443, TLS).
- OSI Model: Seven-layer model. Why it matters: In exams, name the layers and assign them correctly: Layer 2 = Data Link (switch), Layer 3 = Network (router, IP), Layer 4 = Transport (TCP, UDP), Layer 7 = Application (HTTP, DNS).
- TCP/IP Stack: Four-layer model. Why it matters: TCP/IP is the practical model used on the internet. You should be able to map the four layers to the seven OSI layers (e.g., TCP/IP Application = OSI 5–7).
- Exam relevance: Foundation for network administration and development. Why it matters: AP1 and AP2 exams regularly test networks, protocols, and security. Repeated study leads to real understanding.
Core Components
These core components form the technical foundation of network communication. Master these building blocks, and you can not only describe protocols but also troubleshoot network problems and build your own networked applications.
- OSI Model: Theoretical seven-layer model. Why it matters: The OSI model gives you a shared language for describing network communication. When a colleague says “The problem is on Layer 3,” you immediately know routing and IP are involved. It helps you systematically narrow down faults.
- TCP/IP Stack: Practical four-layer model. Why it matters: TCP/IP is what actually runs the internet. It’s leaner than OSI and directly implementable. Understand TCP/IP, and you understand how the internet really works.
- Transport Protocols: TCP and UDP for data delivery. Why it matters: Your choice of transport protocol determines how reliably your application communicates. A banking app needs TCP; a live stream needs UDP. As a developer, you make this decision actively.
- Network Protocols: IP for routing and addressing. Why it matters: IP addresses are like postal addresses in your network. Without correct addressing, data doesn’t arrive. IPv4 versus IPv6 migration is a live topic in many organizations.
- Application Protocols: HTTP, DNS, SMTP for services. Why it matters: These protocols sit at the boundary between user and network. Every webpage uses HTTP, every domain name needs DNS. As a developer, you use them every day.
- Port Numbers: Identification of services. Why it matters: An IP address alone isn’t enough — multiple services often run on one server. Ports (like 80 for HTTP, 443 for HTTPS) route traffic to the right program. Know well-known ports for the exam.
- Socket Programming: Network communication. Why it matters: Sockets are the programming interface to networks. Understand sockets, and you can build your own servers and clients — that’s what separates a real developer from a user.
- Network Security: Firewalls, VPN, TLS. Why it matters: Every open network is a target. TLS encrypts data, firewalls block unwanted traffic. In exams and on the job, security isn’t optional — it’s essential.
Hands-On Examples
1. TCP Socket Programming with Java
What does this code do? This Java program shows a complete TCP server with thread pool management that handles multiple clients concurrently. It also includes a TCP client and performance tests for latency and throughput.
Why understand this? TCP is the most commonly used transport protocol on the internet. Learn how a TCP server works, and you understand how web servers, mail servers, and databases function at a fundamental level. The three-way handshake, connection management, and thread handling are core exam topics and everyday concepts on the job.
What will you learn?
- How a ServerSocket accepts connections and hands them off to separate threads
- How to exchange data bidirectionally using BufferedReader and PrintWriter
- Why a thread pool matters — so you don’t create thousands of threads
- How to measure latency and throughput programmatically
- How to cleanly close client connections and free resources
If you don’t know Java or haven’t learned it yet, read through this example anyway. Focus on the structure instead: when are global variables set, when does the actual client/connection happen, when do try statements appear and why exactly there, and so on. You’ll find a Python example further down.
import java.io.*;
import java.net.*;
import java.util.concurrent.*;
public class TCPServerDemo {
private static final int PORT = 8080;
private static final int MAX_CLIENTS = 10;
public static void main(String[] args) {
System.out.println("=== TCP Server Demo ===");
// Thread pool for client connections
ExecutorService threadPool = Executors.newFixedThreadPool(MAX_CLIENTS);
try (ServerSocket serverSocket = new ServerSocket(PORT)) {
System.out.println("TCP Server started on port " + PORT);
while (true) {
// Wait for client connections
Socket clientSocket = serverSocket.accept();
System.out.println("New client connected: " + clientSocket.getInetAddress());
// Handle client in separate thread
threadPool.execute(new ClientHandler(clientSocket));
}
} catch (IOException e) {
System.err.println("Server error: " + e.getMessage());
} finally {
threadPool.shutdown();
}
}
// Client handler
static class ClientHandler implements Runnable {
private final Socket clientSocket;
public ClientHandler(Socket socket) {
this.clientSocket = socket;
}
@Override
public void run() {
try (
BufferedReader in = new BufferedReader(
new InputStreamReader(clientSocket.getInputStream()));
PrintWriter out = new PrintWriter(
clientSocket.getOutputStream(), true)
) {
String clientAddress = clientSocket.getInetAddress().toString();
System.out.println("Handler for " + clientAddress + " started");
// Send greeting
out.println("Welcome to TCP Server!");
out.println("Type 'exit' to disconnect");
// Receive messages from client and respond
String inputLine;
while ((inputLine = in.readLine()) != null) {
System.out.println("Message from " + clientAddress + ": " + inputLine);
if ("exit".equalsIgnoreCase(inputLine.trim())) {
out.println("Goodbye!");
break;
}
// Echo with timestamp
String response = "Echo: " + inputLine + " [" +
java.time.LocalDateTime.now() + "]";
out.println(response);
}
} catch (IOException e) {
System.err.println("Error in client handler: " + e.getMessage());
} finally {
try {
clientSocket.close();
System.out.println("Client connection closed");
} catch (IOException e) {
System.err.println("Error closing connection: " + e.getMessage());
}
}
}
}
// TCP Client
static class TCPClient {
private final String hostname;
private final int port;
public TCPClient(String hostname, int port) {
this.hostname = hostname;
this.port = port;
}
public void start() {
try (
Socket socket = new Socket(hostname, port);
BufferedReader in = new BufferedReader(
new InputStreamReader(socket.getInputStream()));
PrintWriter out = new PrintWriter(
socket.getOutputStream(), true);
BufferedReader stdIn = new BufferedReader(
new InputStreamReader(System.in))
) {
System.out.println("Connected to server");
// Receive server response
String serverResponse = in.readLine();
System.out.println("Server: " + serverResponse);
// Interactive communication
String userInput;
while ((userInput = stdIn.readLine()) != null) {
out.println(userInput);
serverResponse = in.readLine();
System.out.println("Server: " + serverResponse);
if ("exit".equalsIgnoreCase(userInput.trim())) {
break;
}
}
} catch (UnknownHostException e) {
System.err.println("Unknown host: " + hostname);
} catch (IOException e) {
System.err.println("I/O error: " + e.getMessage());
}
}
}
// TCP Performance Test
static class TCPPerformanceTest {
public static void testTCPLatency(String hostname, int port, int iterations) {
try (Socket socket = new Socket(hostname, port);
PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
BufferedReader in = new BufferedReader(
new InputStreamReader(socket.getInputStream()))) {
System.out.println("=== TCP Latency Test ===");
long totalTime = 0;
for (int i = 0; i < iterations; i++) {
long startTime = System.nanoTime();
out.println("ping");
String response = in.readLine();
long endTime = System.nanoTime();
long latency = (endTime - startTime) / 1_000_000; // ms
totalTime += latency;
if (i % 10 == 0) {
System.out.printf("Ping %d: %dms%n", i + 1, latency);
}
}
double avgLatency = (double) totalTime / iterations;
System.out.printf("Average latency: %.2fms%n", avgLatency);
} catch (IOException e) {
System.err.println("Error in latency test: " + e.getMessage());
}
}
public static void testTCPThroughput(String hostname, int port, int dataSize) {
try (Socket socket = new Socket(hostname, port);
PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
BufferedReader in = new BufferedReader(
new InputStreamReader(socket.getInputStream()))) {
System.out.println("=== TCP Throughput Test ===");
// Create test data
StringBuilder testData = new StringBuilder();
for (int i = 0; i < dataSize; i++) {
testData.append("A");
}
long startTime = System.nanoTime();
out.println("throughput:" + testData.toString());
String response = in.readLine();
long endTime = System.nanoTime();
long duration = (endTime - startTime) / 1_000_000; // ms
double throughput = (double) dataSize / (duration / 1000.0) / 1024.0; // KB/s
System.out.printf("Throughput: %.2f KB/s (%d bytes in %dms)%n",
throughput, dataSize, duration);
} catch (IOException e) {
System.err.println("Error in throughput test: " + e.getMessage());
}
}
}
// Main for client tests
public static void main(String[] args) {
// Start server
if (args.length == 0) {
TCPServerDemo.main(args);
} else {
// Client tests
TCPClient client = new TCPClient("localhost", PORT);
// Interactive client
new Thread(() -> client.start()).start();
// Performance tests
try {
Thread.sleep(2000); // Wait for server to be ready
TCPPerformanceTest.testTCPLatency("localhost", PORT, 100);
TCPPerformanceTest.testTCPThroughput("localhost", PORT, 10240);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
}
2. UDP Socket Programming with Python
What does this code do? This Python program demonstrates a UDP server with broadcast capability, an interactive UDP client, performance tests for latency, throughput, and packet loss, plus a UDP multicast demo.
Why is this worth understanding? UDP is the opposite of TCP: fast but unreliable. Understanding UDP tells you when speed matters more than reliability. Broadcasting and multicast are fundamental concepts for network discovery, streaming, and IoT communication. On an exam, you need to be able to contrast TCP and UDP.
What will you learn?
- How to create UDP sockets using
socket.SOCK_DGRAM - Why UDP requires no connection setup and how
recvfromreturns sender addresses - How broadcasting reaches all clients and multicast reaches a specific group
- Why UDP experiences packet loss and how to measure it
- How to send and receive simultaneously using threads
Pay special attention to the structure and layout of this example. Analyze the imports carefully and ask yourself why each one is imported and what purpose it serves.
import socket
import threading
import time
import json
from datetime import datetime
# UDP Server
class UDPServer:
def __init__(self, host='localhost', port=9999):
self.host = host
self.port = port
self.server_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
self.clients = set()
self.running = False
def start(self):
"""Start the UDP server"""
self.server_socket.bind((self.host, self.port))
self.running = True
print(f"UDP Server running on {self.host}:{self.port}")
while self.running:
try:
# Receive data
data, client_address = self.server_socket.recvfrom(2048)
# Add client to list
self.clients.add(client_address)
print(f"Message from {client_address}: {data.decode()}")
# Send message to all clients (broadcast)
self.broadcast(data, client_address)
except Exception as e:
print(f"Error receiving: {e}")
def broadcast(self, message, sender_address):
"""Send message to all clients"""
timestamp = datetime.now().strftime("%H:%M:%S")
broadcast_message = f"[{timestamp}] {sender_address[0]}: {message.decode()}"
for client in self.clients:
if client != sender_address:
try:
self.server_socket.sendto(broadcast_message.encode(), client)
except Exception as e:
print(f"Error sending to {client}: {e}")
def stop(self):
"""Stop the server"""
self.running = False
self.server_socket.close()
print("UDP Server stopped")
# UDP Client
class UDPClient:
def __init__(self, host='localhost', port=9999):
self.host = host
self.port = port
self.client_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
self.running = False
def start(self, username):
"""Start the UDP client"""
self.running = True
print(f"UDP Client connected to {self.host}:{self.port}")
# Start receive thread
receive_thread = threading.Thread(target=self.receive_messages)
receive_thread.daemon = True
receive_thread.start()
try:
while self.running:
# Input message
message = input(f"{username}: ")
if message.lower() == 'exit':
break
# Send message
full_message = f"{username}: {message}"
self.client_socket.sendto(full_message.encode(), (self.host, self.port))
except KeyboardInterrupt:
print("\nClient terminated")
finally:
self.stop()
def receive_messages(self):
"""Receive messages from server"""
while self.running:
try:
data, server = self.client_socket.recvfrom(2048)
print(f"\r{data.decode()}")
print("Message: ", end="", flush=True)
except Exception as e:
if self.running:
print(f"Error receiving: {e}")
break
def stop(self):
"""Stop the client"""
self.running = False
self.client_socket.close()
print("UDP Client stopped")
# UDP Performance Test
class UDPPerformanceTest:
def __init__(self, host='localhost', port=9999):
self.host = host
self.port = port
self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
def test_latency(self, iterations=100):
"""UDP latency test"""
print(f"=== UDP Latency Test ===")
latencies = []
for i in range(iterations):
start_time = time.time()
# Send ping message
self.socket.sendto(b"ping", (self.host, self.port))
# Receive response
try:
data, _ = self.socket.recvfrom(1024)
end_time = time.time()
latency = (end_time - start_time) * 1000 # ms
latencies.append(latency)
if i % 10 == 0:
print(f"Ping {i+1}: {latency:.2f}ms")
except socket.timeout:
print(f"Timeout on ping {i+1}")
if latencies:
avg_latency = sum(latencies) / len(latencies)
min_latency = min(latencies)
max_latency = max(latencies)
print(f"\nLatency statistics:")
print(f" Average: {avg_latency:.2f}ms")
print(f" Minimum: {min_latency:.2f}ms")
print(f" Maximum: {max_latency:.2f}ms")
def test_throughput(self, data_size=1024, duration=5):
"""UDP throughput test"""
print(f"\n=== UDP Throughput Test ===")
# Create test data
test_data = b'A' * data_size
start_time = time.time()
packets_sent = 0
while time.time() - start_time < duration:
try:
self.socket.sendto(test_data, (self.host, self.port))
packets_sent += 1
# Brief pause to avoid overwhelming the server
time.sleep(0.001)
except Exception as e:
print(f"Error sending: {e}")
break
end_time = time.time()
total_time = end_time - start_time
throughput = (packets_sent * data_size) / total_time / 1024 # KB/s
packets_per_second = packets_sent / total_time
print(f"Throughput statistics:")
print(f" Packets sent: {packets_sent}")
print(f" Total duration: {total_time:.2f}s")
print(f" Packets/second: {packets_per_second:.2f}")
print(f" Throughput: {throughput:.2f} KB/s")
def test_packet_loss(self, packets=1000):
"""UDP packet loss test"""
print(f"\n=== UDP Packet Loss Test ===")
packets_lost = 0
for i in range(packets):
try:
# Send sequential packet number
packet = f"packet_{i}".encode()
self.socket.sendto(packet, (self.host, self.port))
# Brief pause
time.sleep(0.001)
except Exception as e:
packets_lost += 1
loss_rate = (packets_lost / packets) * 100
print(f"Packet loss statistics:")
print(f" Packets sent: {packets}")
print(f" Packets lost: {packets_lost}")
print(f" Loss rate: {loss_rate:.2f}%")
def close(self):
"""Close socket"""
self.socket.close()
# UDP Multicast Demo
class UDPMulticastDemo:
def __init__(self, multicast_group='224.0.0.1', port=5000):
self.multicast_group = multicast_group
self.port = port
def create_multicast_sender(self):
"""Create multicast sender"""
sender = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# Set TTL (time-to-live: how many hops the packet can make)
ttl = struct.pack('b', 1)
sender.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, ttl)
return sender
def create_multicast_receiver(self):
"""Create multicast receiver"""
receiver = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# Bind socket to multicast address
receiver.bind(('', self.port))
# Join the multicast group
group = socket.inet_aton(self.multicast_group)
receiver.setsockopt(socket.SOL_IP, socket.IP_ADD_MEMBERSHIP, group + socket.inet_aton('0.0.0.0'))
return receiver
def send_multicast(self, message):
"""Send multicast message"""
sender = self.create_multicast_sender()
try:
sender.sendto(message.encode(), (self.multicast_group, self.port))
print(f"Multicast sent: {message}")
finally:
sender.close()
def receive_multicast(self):
"""Receive multicast messages"""
receiver = self.create_multicast_receiver()
try:
print(f"Multicast receiver listening on {self.multicast_group}:{self.port}")
while True:
try:
data, address = receiver.recvfrom(1024)
print(f"Multicast from {address}: {data.decode()}")
except KeyboardInterrupt:
break
finally:
# Leave the group
group = socket.inet_aton(self.multicast_group)
receiver.setsockopt(socket.SOL_IP, socket.IP_DROP_MEMBERSHIP, group + socket.inet_aton('0.0.0.0'))
receiver.close()
# Main for UDP demos
def main():
import sys
if len(sys.argv) > 1:
mode = sys.argv[1]
if mode == "server":
# Start UDP server
server = UDPServer()
try:
server.start()
except KeyboardInterrupt:
server.stop()
elif mode == "client":
# Start UDP client
username = input("Your name: ")
client = UDPClient()
client.start(username)
elif mode == "performance":
# Performance tests
test = UDPPerformanceTest()
test.test_latency(100)
test.test_throughput(1024, 5)
test.test_packet_loss(1000)
test.close()
elif mode == "multicast_send":
# Multicast sender
multicast = UDPMulticastDemo()
while True:
message = input("Multicast message (or 'exit'): ")
if message.lower() == 'exit':
break
multicast.send_multicast(message)
elif mode == "multicast_receive":
# Multicast receiver
multicast = UDPMulticastDemo()
multicast.receive_multicast()
else:
print("Usage: python udp_demo.py [server|client|performance|multicast_send|multicast_receive]")
else:
print("Usage: python udp_demo.py [server|client|performance|multicast_send|multicast_receive]")
if __name__ == "__main__":
main()
3. DNS Lookups Across Different Programming Languages
What does this code do? This Java program demonstrates various DNS lookup techniques: standard name resolution (domain → IP), reverse DNS (IP → domain), retrieving all IP addresses for a hostname, DNS performance testing, and DNS caching comparison (first lookup vs. subsequent lookup).
Why is this worth understanding? DNS is the internet’s phonebook. Without it, you’d need to remember IP addresses. In exams and on the job, you’ll need to diagnose DNS problems—whether it’s a misconfigured A record, a stale cache entry, or an unreachable nameserver. Master DNS and you’ll understand why some websites are reachable and others aren’t.
What you’ll learn:
- How
InetAddress.getByName()translates domain names to IP addresses - How reverse DNS works using the same class
- Why DNS caching matters and how it affects performance
- How to benchmark DNS lookups programmatically
- The difference between IPv4 (
Inet4Address) and IPv6 (Inet6Address)
import java.net.*;
import java.util.*;
public class DNSLookupDemo {
// Simple DNS lookup
public static String dnsLookup(String hostname) {
try {
InetAddress address = InetAddress.getByName(hostname);
return address.getHostAddress();
} catch (UnknownHostException e) {
return "Unknown host: " + hostname;
}
}
// Reverse DNS lookup (IP → Hostname)
public static String reverseDNSLookup(String ipAddress) {
try {
InetAddress address = InetAddress.getByName(ipAddress);
return address.getHostName();
} catch (UnknownHostException e) {
return "Unknown IP: " + ipAddress;
}
}
// Get all IP addresses for a hostname
public static List<String> getAllIPAddresses(String hostname) {
List<String> addresses = new ArrayList<>();
try {
InetAddress[] allAddresses = InetAddress.getAllByName(hostname);
for (InetAddress addr : allAddresses) {
addresses.add(addr.getHostAddress());
}
} catch (UnknownHostException e) {
addresses.add("Unknown host: " + hostname);
}
return addresses;
}
// Query DNS record types (simplified)
public static void dnsLookupWithTypes(String hostname) {
System.out.println("=== DNS Lookup for " + hostname + " ===");
// A record (IPv4)
String ipv4 = dnsLookup(hostname);
System.out.println("A record (IPv4): " + ipv4);
// AAAA record (IPv6)
try {
InetAddress ipv6Address = InetAddress.getByName(hostname);
if (ipv6Address instanceof Inet6Address) {
System.out.println("AAAA record (IPv6): " + ipv6Address.getHostAddress());
}
} catch (UnknownHostException e) {
System.out.println("AAAA record (IPv6): Not found");
}
// Reverse lookup
if (!ipv4.equals("Unknown host: " + hostname)) {
String reverse = reverseDNSLookup(ipv4);
System.out.println("PTR record (Reverse): " + reverse);
}
// MX records (Mail Exchange)
dnsLookupMX(hostname);
// NS records (Name Server)
dnsLookupNS(hostname);
}
// Query MX records
public static void dnsLookupMX(String hostname) {
try {
// In practice, use DNS libraries like dnsjava for complete queries
// Here we demonstrate the concept
System.out.println("MX records: Use DNS libraries for full queries");
// Simple logic example
if (hostname.endsWith(".com")) {
System.out.println(" MX: mail." + hostname + " (example)");
}
} catch (Exception e) {
System.out.println("MX records: Query error - " + e.getMessage());
}
}
// Query NS records
public static void dnsLookupNS(String hostname) {
try {
System.out.println("NS records: Use DNS libraries for full queries");
// Simple logic example
String[] commonNS = {"ns1." + hostname, "ns2." + hostname};
for (String ns : commonNS) {
System.out.println(" NS: " + ns + " (example)");
}
} catch (Exception e) {
System.out.println("NS records: Query error - " + e.getMessage());
}
}
// DNS performance test
public static void dnsPerformanceTest(String hostname, int iterations) {
System.out.println("=== DNS Performance Test ===");
long totalTime = 0;
int successfulLookups = 0;
for (int i = 0; i < iterations; i++) {
long startTime = System.nanoTime();
String result = dnsLookup(hostname);
long endTime = System.nanoTime();
long duration = (endTime - startTime) / 1_000_000; // ms
if (!result.startsWith("Unknown host")) {
totalTime += duration;
successfulLookups++;
}
if (i % 10 == 0) {
System.out.printf("Lookup %d: %dms%n", i + 1, duration);
}
}
if (successfulLookups > 0) {
double avgTime = (double) totalTime / successfulLookups;
System.out.printf("Average DNS lookup time: %.2fms%n", avgTime);
System.out.printf("Successful lookups: %d/%d%n", successfulLookups, iterations);
}
}
// DNS caching demo
public static void dnsCachingDemo(String hostname) {
System.out.println("=== DNS Caching Demo ===");
// First lookup (cache miss)
long start = System.nanoTime();
String result1 = dnsLookup(hostname);
long firstLookup = (System.nanoTime() - start) / 1_000_000;
// Second lookup (cache hit)
start = System.nanoTime();
String result2 = dnsLookup(hostname);
long secondLookup = (System.nanoTime() - start) / 1_000_000;
System.out.println("First lookup: " + firstLookup + "ms");
System.out.println("Second lookup: " + secondLookup + "ms");
if (secondLookup < firstLookup) {
System.out.println("DNS caching active (second lookup faster)");
} else {
System.out.println("No DNS caching detected");
}
// Compare results
System.out.println("Result 1: " + result1);
System.out.println("Result 2: " + result2);
System.out.println("Results match: " + result1.equals(result2));
}
// IPv6 support test
public static void ipv6SupportTest(String hostname) {
System.out.println("=== IPv6 Support Test ===");
try {
// Resolve IPv6 address
InetAddress[] addresses = InetAddress.getAllByName(hostname);
boolean hasIPv4 = false;
boolean hasIPv6 = false;
for (InetAddress addr : addresses) {
if (addr instanceof Inet4Address) {
hasIPv4 = true;
System.out.println("IPv4: " + addr.getHostAddress());
} else if (addr instanceof Inet6Address) {
hasIPv6 = true;
System.out.println("IPv6: " + addr.getHostAddress());
}
}
System.out.println("IPv4 support: " + (hasIPv4 ? "Yes" : "No"));
System.out.println("IPv6 support: " + (hasIPv6 ? "Yes" : "No"));
} catch (UnknownHostException e) {
System.out.println("IPv6 support test failed: " + e.getMessage());
}
}
public static void main(String[] args) {
String hostname = "google.com";
if (args.length > 0) {
hostname = args[0];
}
// Various DNS lookups
dnsLookupWithTypes(hostname);
// Performance test
dnsPerformanceTest(hostname, 50);
// Caching demo
dnsCachingDemo(hostname);
// IPv6 support test
ipv6SupportTest(hostname);
}
}
4. HTTP/HTTPS Client with Advanced Features
What does this code do? This Python program demonstrates a complete HTTP/HTTPS client with advanced capabilities: retry logic, various HTTP methods (GET, POST, HEAD, OPTIONS), SSL/TLS certificate validation, cookie handling, redirect tracking, file downloads with progress reporting, performance benchmarking, and a REST API client for common endpoint operations.
Why is this worth understanding? HTTP/HTTPS is the fundamental protocol driving the web. Every API call, every page load, and every data sync depends on it. Understanding how requests, responses, status codes, headers, and sessions work lets you debug APIs effectively, spot security issues, and build performant web applications. HTTP methods and status codes are standard exam material.
What you’ll learn:
- How to build a reusable connection with
requests.Session() - Why retry strategies matter and how to implement them with
urllib3.util.retry - How to read SSL certificate information and TLS versions
- How cookies are automatically passed between requests
- How to follow or block redirects
- How to efficiently download large files using
stream=True - How to build a generic REST API client with CRUD operations
import requests
import json
import time
import ssl
import urllib3
from urllib3.util.retry import Retry
from datetime import datetime
# HTTP Client with advanced features
class HTTPClient:
def __init__(self):
self.session = requests.Session()
self.setup_session()
def setup_session(self):
"""Configure the session"""
# Retry strategy
retry_strategy = Retry(
total=3,
backoff_factor=1,
status_forcelist=[429, 500, 502, 503, 504],
allowed_methods=["HEAD", "GET", "OPTIONS"]
)
adapter = urllib3.HTTPAdapter(max_retries=retry_strategy)
self.session.mount("https://", adapter)
self.session.mount("http://", adapter)
# Headers
self.session.headers.update({
'User-Agent': 'HTTPClient-Demo/1.0',
'Accept': 'application/json',
'Accept-Language': 'de-DE,de;q=0.9,en;q=0.8',
'Cache-Control': 'no-cache'
})
# Timeout
self.session.timeout = 30
print("HTTP Client configured")
def get_request(self, url, params=None):
"""GET request with error handling"""
try:
print(f"GET Request: {url}")
start_time = time.time()
response = self.session.get(url, params=params)
end_time = time.time()
self.print_response_info(response, end_time - start_time)
return response
except requests.exceptions.RequestException as e:
print(f"GET request failed: {e}")
return None
def post_request(self, url, data=None, json_data=None):
"""POST request with data"""
try:
print(f"POST Request: {url}")
if json_data:
print(f"JSON data: {json.dumps(json_data, indent=2)}")
start_time = time.time()
if json_data:
response = self.session.post(url, json=json_data)
else:
response = self.session.post(url, data=data)
end_time = time.time()
self.print_response_info(response, end_time - start_time)
return response
except requests.exceptions.RequestException as e:
print(f"POST request failed: {e}")
return None
def print_response_info(self, response, duration):
"""Print response information"""
print(f"Status Code: {response.status_code}")
print(f"Response Time: {duration:.2f}s")
print(f"Content-Type: {response.headers.get('Content-Type', 'N/A')}")
print(f"Content-Length: {response.headers.get('Content-Length', 'N/A')}")
# Print headers
if response.headers:
print("Response Headers:")
for key, value in response.headers.items():
print(f" {key}: {value}")
def test_http_methods(self, url):
"""Test different HTTP methods"""
print(f"=== HTTP Methods Test for {url} ===")
# GET
print("\n--- GET ---")
response = self.get_request(url)
# HEAD
print("\n--- HEAD ---")
try:
response = self.session.head(url)
print(f"HEAD Status: {response.status_code}")
print(f"HEAD Headers: {dict(response.headers)}")
except Exception as e:
print(f"HEAD request failed: {e}")
# OPTIONS
print("\n--- OPTIONS ---")
try:
response = self.session.options(url)
print(f"OPTIONS Status: {response.status_code}")
allowed_methods = response.headers.get('Allow', 'N/A')
print(f"Allowed methods: {allowed_methods}")
except Exception as e:
print(f"OPTIONS request failed: {e}")
def test_https_features(self, url):
"""Test HTTPS features"""
print(f"=== HTTPS Features Test for {url} ===")
try:
# SSL certificate information
response = self.session.get(url)
# Print SSL information
cert_info = response.raw._connection.peer.cert
if cert_info:
print("SSL Certificate Information:")
for cert in cert_info:
print(f" Subject: {cert.subject}")
print(f" Issuer: {cert.issuer}")
print(f" Valid from: {cert.not_valid_before}")
print(f" Valid until: {cert.not_valid_after}")
print(f" Serial: {cert.serial_number}")
# TLS version
tls_version = response.raw._connection.tls_version
print(f"TLS Version: {tls_version}")
# Cipher suite
cipher_suite = response.raw._connection.cipher
print(f"Cipher Suite: {cipher_suite}")
except Exception as e:
print(f"HTTPS features test failed: {e}")
def test_authentication(self, url, username, password):
"""Test HTTP authentication"""
print(f"=== Authentication Test for {url} ===")
# Basic authentication
try:
from requests.auth import HTTPBasicAuth
auth = HTTPBasicAuth(username, password)
response = self.session.get(url, auth=auth)
print(f"Basic Auth Status: {response.status_code}")
if response.status_code == 200:
print("Basic authentication successful")
else:
print("Basic authentication failed")
except Exception as e:
print(f"Authentication test failed: {e}")
def test_cookies(self, url):
"""Test cookie handling"""
print(f"=== Cookie Test for {url} ===")
try:
# First request without cookies
response1 = self.session.get(url)
print(f"First request status: {response1.status_code}")
print(f"Cookies after first request: {len(self.session.cookies)}")
# Second request with cookies
response2 = self.session.get(url)
print(f"Second request status: {response2.status_code}")
print(f"Cookies after second request: {len(self.session.cookies)}")
# Cookie information
if self.session.cookies:
print("Cookie Details:")
for cookie in self.session.cookies:
print(f" {cookie.name}: {cookie.value}")
print(f" Domain: {cookie.domain}")
print(f" Path: {cookie.path}")
print(f" Secure: {cookie.secure}")
print(f" HttpOnly: {cookie.has_attr('HttpOnly')}")
except Exception as e:
print(f"Cookie test failed: {e}")
def test_redirects(self, url):
"""Test redirect handling"""
print(f"=== Redirect Test for {url} ===")
try:
# Don't follow redirects
response = self.session.get(url, allow_redirects=False)
print(f"Status without redirects: {response.status_code}")
if 300 <= response.status_code < 400:
location = response.headers.get('Location', 'N/A')
print(f"Redirect to: {location}")
# Follow redirects
response = self.session.get(url, allow_redirects=True)
print(f"Status with redirects: {response.status_code}")
print(f"Final URL: {response.url}")
# Redirect history
if hasattr(response.history, '__iter__'):
print("Redirect history:")
for i, resp in enumerate(response.history):
print(f" {i+1}. {resp.status_code} -> {resp.url}")
except Exception as e:
print(f"Redirect test failed: {e}")
def test_download(self, url, save_path=None):
"""Test file download"""
print(f"=== Download Test for {url} ===")
try:
response = self.session.get(url, stream=True)
content_length = int(response.headers.get('content-length', 0))
print(f"File size: {content_length} bytes")
if save_path:
with open(save_path, 'wb') as f:
downloaded = 0
chunk_size = 8192
for chunk in response.iter_content(chunk_size=chunk_size):
f.write(chunk)
downloaded += chunk_size
# Show progress
progress = (downloaded / content_length) * 100 if content_length > 0 else 0
print(f"\rDownload: {progress:.1f}%", end="", flush=True)
print(f"\nFile saved: {save_path}")
except Exception as e:
print(f"Download failed: {e}")
def performance_test(self, url, iterations=10):
"""Performance test"""
print(f"=== Performance Test for {url} ===")
times = []
for i in range(iterations):
try:
start_time = time.time()
response = self.session.get(url)
end_time = time.time()
duration = end_time - start_time
times.append(duration)
if i % 5 == 0:
print(f"Request {i+1}: {duration:.3f}s")
except Exception as e:
print(f"Request {i+1} failed: {e}")
if times:
avg_time = sum(times) / len(times)
min_time = min(times)
max_time = max(times)
print(f"\nPerformance Statistics:")
print(f" Requests: {len(times)}")
print(f" Average: {avg_time:.3f}s")
print(f" Minimum: {min_time:.3f}s")
print(f" Maximum: {max_time:.3f}s")
print(f" Requests/second: {1/avg_time:.2f}")
# API client for REST APIs
class APIClient(HTTPClient):
def __init__(self, base_url):
super().__init__()
self.base_url = base_url.rstrip('/')
# API-specific headers
self.session.headers.update({
'Content-Type': 'application/json',
'Accept': 'application/json'
})
def get_resource(self, resource, params=None):
"""GET resource"""
url = f"{self.base_url}/{resource.lstrip('/')}"
return self.get_request(url, params)
def create_resource(self, resource, data):
"""POST resource"""
url = f"{self.base_url}/{resource.lstrip('/')}"
return self.post_request(url, json_data=data)
def update_resource(self, resource, data):
"""PUT resource"""
url = f"{self.base_url}/{resource.lstrip('/')}"
try:
response = self.session.put(url, json=data)
self.print_response_info(response, 0)
return response
except Exception as e:
print(f"PUT request failed: {e}")
return None
def delete_resource(self, resource):
"""DELETE resource"""
url = f"{self.base_url}/{resource.lstrip('/')}"
try:
response = self.session.delete(url)
self.print_response_info(response, 0)
return response
except Exception as e:
print(f"DELETE request failed: {e}")
return None
# Main for HTTP demos
def main():
import sys
# HTTP client demo
client = HTTPClient()
# HTTP methods test
client.test_http_methods("https://httpbin.org")
# HTTPS features test
client.test_https_features("https://google.com")
# Redirect test
client.test_redirects("http://httpbin.org/redirect/1")
# Cookie test
client.test_cookies("https://httpbin.org/cookies/set/test/value")
# Performance test
client.performance_test("https://httpbin.org/get", 20)
# API client demo
api_client = APIClient("https://jsonplaceholder.typicode.com")
# GET users
users_response = api_client.get_resource("/users")
if users_response:
print(f"Users: {users_response.json()}")
# Create post
new_post = {
"title": "Test Post",
"body": "Test Body",
"userId": 1
}
create_response = api_client.create_resource("/posts", new_post)
# Download test
client.test_download("https://httpbin.org/bytes/1024", "test_download.bin")
if __name__ == "__main__":
main()
OSI Model vs TCP/IP Stack
OSI Model (7 Layers)
| Layer | Name | Function | Examples |
|---|---|---|---|
| 7 | Application | Application protocols | HTTP, FTP, SMTP |
| 6 | Presentation | Data transformation | SSL/TLS, JPEG |
| 5 | Session | Session management | NetBIOS, RPC |
| 4 | Transport | End-to-end communication | TCP, UDP |
| 3 | Network | Routing and addressing | IP, ICMP |
| 2 | Data Link | Frame transmission | Ethernet, WiFi |
| 1 | Physical | Bit transmission | Cables, wireless |
TCP/IP Stack (4 Layers)
| Layer | Name | OSI Equivalent | Protocols |
|---|---|---|---|
| 4 | Application | 5-7 | HTTP, FTP, DNS |
| 3 | Transport | 4 | TCP, UDP |
| 2 | Internet | 3 | IP, ICMP |
| 1 | Network Access | 1-2 | Ethernet, WiFi |
Important Port Numbers
Well-Known Ports (0-1023)
| Port | Protocol | Service |
|---|---|---|
| 20 | FTP | File Transfer (Data) |
| 21 | FTP | File Transfer (Control) |
| 22 | SSH | Secure Shell |
| 23 | Telnet | Remote Terminal |
| 25 | SMTP | |
| 53 | DNS | Domain Name System |
| 80 | HTTP | Web (unencrypted) |
| 110 | POP3 | E-Mail (Post Office) |
| 143 | IMAP | E-Mail (Internet Message) |
| 443 | HTTPS | Web (encrypted) |
| 993 | IMAPS | IMAP over SSL |
| 995 | POP3S | POP3 over SSL |
Registered Ports (1024-49151)
| Port | Protocol | Service |
|---|---|---|
| 3306 | MySQL | Database |
| 5432 | PostgreSQL | Database |
| 8080 | HTTP-Alt | Web (alternative) |
| 8443 | HTTPS-Alt | Web (alternative) |
| 27017 | MongoDB | NoSQL database |
TCP vs UDP Comparison
| Property | TCP | UDP |
|---|---|---|
| Connection | Connection-oriented | Connectionless |
| Reliability | Guaranteed | Not guaranteed |
| Order | Guaranteed | Not guaranteed |
| Flow Control | Yes | No |
| Error Correction | Yes | No |
| Overhead | High | Low |
| Speed | Slower | Faster |
| Use Cases | Web, Email | Streaming, Gaming |
HTTP Status Codes Overview
1xx Informational
- 100 Continue: Request accepted, continue sending
- 101 Switching Protocols: Protocol upgrade in progress
2xx Success
- 200 OK: Request successful
- 201 Created: Resource created
- 204 No Content: Request successful, no content to return
3xx Redirection
- 301 Moved Permanently: Resource permanently moved
- 302 Found: Resource temporarily moved
- 304 Not Modified: Cached resource still valid
4xx Client Error
- 400 Bad Request: Invalid request syntax
- 401 Unauthorized: Authentication required
- 403 Forbidden: Access denied
- 404 Not Found: Resource not found
- 429 Too Many Requests: Rate limit exceeded
5xx Server Error
- 500 Internal Server Error: Server-side error
- 502 Bad Gateway: Invalid response from upstream
- 503 Service Unavailable: Server temporarily unavailable
DNS Record Types
| Type | Description | Example |
|---|---|---|
| A | IPv4 address | example.com → 93.184.216.34 |
| AAAA | IPv6 address | example.com → 2606:2800:220:1 |
| MX | Mail Exchange | example.com → mail.example.com |
| NS | Name Server | example.com → ns1.example.com |
| CNAME | Canonical Name | www.example.com → example.com |
| TXT | Text record | example.com → “v=spf1 include:_spf.google.com ~all” |
| PTR | Pointer (Reverse) | 93.184.216.34 → example.com |
Socket Programming Basics
TCP Socket Lifecycle
// Server
1. Create ServerSocket
2. Wait for connection (accept)
3. Create InputStream/OutputStream
4. Exchange data
5. Close connection
// Client
1. Create Socket
2. Connect to server
3. Create InputStream/OutputStream
4. Exchange data
5. Close connection
UDP Socket Lifecycle
// Server
1. Create DatagramSocket
2. Wait for packets (receive)
3. Process packet
4. Send response (send)
// Client
1. Create DatagramSocket
2. Send packet (send)
3. Wait for response (receive)
4. Close socket
Advantages and Disadvantages
Benefits of Network Protocols
- Standardization: Systems can interoperate reliably
- Scalability: Support for networks of any size
- Reliability: Error handling and recovery mechanisms
- Flexibility: Different protocols for different purposes
Drawbacks
- Complexity: Multiple layers and protocols to manage
- Overhead: Additional data required for protocol metadata
- Security: Attack surface for malicious actors
- Performance: Network latencies impact throughput
Common Exam Questions
-
What’s the difference between TCP and UDP? TCP is connection-oriented and reliable, while UDP is connectionless and faster but offers no delivery guarantees.
-
Explain the OSI model! The OSI model describes seven layers for network communication, ranging from Physical at the bottom to Application at the top.
-
What is DNS and how does it work? DNS translates domain names to IP addresses and vice versa using a hierarchical system of nameservers.
-
When do you use HTTP versus HTTPS? HTTP for unencrypted communication, HTTPS for encrypted communication secured with SSL/TLS.
Key Resources
- https://www.ietf.org/rfc/rfc791.html (TCP)
- https://www.ietf.org/rfc/rfc768.html (UDP)
- https://www.ietf.org/rfc/rfc1035.html (DNS)
- https://www.ietf.org/rfc/rfc2616.html (HTTP/1.1)



