Skip to content

Network Programming

Network programming means writing programs that talk to other programs over a network. If your app fetches data from a website, calls an API, connects to a database server, or sends messages to another computer - that is network programming.

It can sound scary at first, but the main idea is simple: one side sends data, the other side receives it, and both sides follow a set of agreed-upon rules called a protocol.

In this chapter, you will start from the basics and work your way up to practical patterns used in real projects.

Before writing any code, it helps to understand a few fundamental ideas. These will help you debug problems faster and understand how data actually travels from one place to another.

An IP address is a unique label that identifies a device on a network - like a home address for your computer.

  • IPv4: 192.168.1.1
  • IPv6: 2001:db8::1

If you send data to the wrong IP address, it goes to the wrong machine - just like sending a letter to the wrong house.

127.0.0.1 (also called localhost) is a special address that means “this same computer.”

A port identifies a specific app or service running on a machine. Think of it like a room number inside a building.

  • The IP address tells you which building (device) to go to
  • The port tells you which room (app) inside that building to knock on

Common examples:

  • 80 -> HTTP (regular websites)
  • 443 -> HTTPS (secure websites)
  • 22 -> SSH (remote terminal access)

Key idea:

IP Address = Device
Port = Application inside device

A socket is the connection point between two machines. It combines an IP address and a port number into one endpoint:

(IP Address + Port)

When two sockets connect to each other, data can flow in both directions between them.

A protocol is just a set of rules that decides how data is sent. The two most common ones are TCP and UDP.

  • Makes sure data is delivered reliably
  • Data arrives in the correct order
  • A connection is established before any data is sent
  • A little slower than UDP, but much safer

Use TCP for:

  • web apps
  • APIs
  • logins
  • payments
  • Very fast
  • No guarantee that data arrives
  • No strict ordering of packets
  • No connection setup needed

Use UDP for:

  • live voice or video calls
  • multiplayer games
  • situations where speed matters more than perfect delivery
flowchart LR A[Client] -->|Request| B[Server] B -->|Response| A

Most internet communication follows this simple pattern. The client asks for something, the server sends it back.

Examples:

  • A browser (client) asks a web server for a page.
  • A mobile app (client) asks an API server for user data.

Socket programming gives you low-level control over how your program communicates over a network. Python has a built-in socket module that lets you do this, and it’s a great way to really understand how networking works under the hood.

  1. Create a socket
  2. Bind it to an IP address and port (server side only)
  3. Start listening for incoming connections
  4. Accept a client that wants to connect
  5. Send and receive data as bytes
  6. Close the socket when done

This same pattern shows up in many real systems, even when a framework is hiding the details from you.

Both have their place. Pick based on what your project needs:

  • use TCP when correctness and reliability matter
  • use UDP when speed matters and occasional data loss is acceptable
sequenceDiagram participant Client participant Server Client->>Server: connect() Client->>Server: send() Server->>Client: response Client->>Server: close()
import socket
HOST = "127.0.0.1"
PORT = 8080
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind((HOST, PORT))
server.listen()
print(f"Server running on {HOST}:{PORT}")
while True:
client_socket, client_addr = server.accept()
print(f"Connected by {client_addr}")
data = client_socket.recv(1024)
message = data.decode("utf-8")
print("Client says:", message)
reply = "Hello from server"
client_socket.send(reply.encode("utf-8"))
client_socket.close()
  • AF_INET means we are using IPv4 addresses
  • SOCK_STREAM means we are using TCP
  • bind() attaches the server to a specific IP and port
  • listen() tells the server to start waiting for clients
  • accept() waits until a client connects and returns a new socket for that client
  • recv() receives incoming bytes
  • send() sends bytes back
import socket
HOST = "127.0.0.1"
PORT = 8080
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect((HOST, PORT))
client.send("Hello server".encode("utf-8"))
response = client.recv(1024)
print("Server says:", response.decode("utf-8"))
client.close()

Try it:

  1. Run the server file first.
  2. Run the client file in a separate terminal window.
  3. Watch the messages appear on both sides.
import socket
server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
server.bind(("localhost", 8080))
while True:
data, addr = server.recvfrom(1024)
print("Received:", data.decode())
server.sendto(b"Hello UDP Client", addr)
import socket
client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
client.sendto(b"Hello UDP Server", ("localhost", 8080))
data, _ = client.recvfrom(1024)
print(data.decode())

With UDP there is no connect() handshake like TCP has. You just send data packets (called datagrams) directly to the other side.

HTTP is the protocol that browsers, APIs, and most web services use to talk to each other. When you open a website, your browser sends an HTTP request and the server sends back an HTTP response.

sequenceDiagram Client->>Server: HTTP Request Server->>Client: HTTP Response
GET /index.html HTTP/1.1
Host: example.com
User-Agent: browser

This request is saying: “Please send me the file at /index.html from this website.”

HTTP/1.1 200 OK
Content-Type: text/html
<html>...</html>

200 OK means the request worked. The body below the headers contains the actual content - HTML, JSON, an image, or whatever was requested.

  • GET -> fetch/read data
  • POST -> send/create data
  • PUT -> update existing data
  • DELETE -> remove data

In REST APIs, these four methods map to the four basic operations: create, read, update, and delete.

  • 200 -> Success
  • 201 -> Created successfully
  • 400 -> Bad request from the client
  • 401 -> Unauthorized (not logged in)
  • 403 -> Forbidden (logged in but not allowed)
  • 404 -> Not Found
  • 500 -> Server Error
  • 301 -> Redirect (the page has moved)

The requests library makes HTTP calls simple and easy to read. It is one of the most used Python libraries in backend development and automation work.

Terminal window
pip install requests
import requests
response = requests.get("https://api.github.com", timeout=10)
print(response.status_code)
print(response.json())

Always set a timeout so your program does not freeze and wait forever if a server is slow or unreachable.

import requests
data = {"username": "sahil", "password": "1234"}
response = requests.post("https://httpbin.org/post", json=data)
print(response.json())

Use json=data when the API expects the body to be in JSON format.

headers = {
"Authorization": "Bearer token",
"Content-Type": "application/json"
}
response = requests.get("https://api.example.com", headers=headers)

Headers carry extra information about the request - things like your auth token, what kind of data you are sending, or what language you prefer.

import requests
response = requests.get("https://api.example.com")
if response.status_code == 200:
print(response.json())
else:
print("Error:", response.status_code)

Better way to handle errors in real projects:

import requests
try:
response = requests.get("https://api.example.com", timeout=10)
response.raise_for_status() # raises HTTPError for 4xx/5xx
data = response.json()
print(data)
except requests.exceptions.Timeout:
print("Request timed out")
except requests.exceptions.RequestException as exc:
print("Request failed:", exc)

This approach prevents your program from crashing and gives you much clearer error messages when something goes wrong.

Most APIs send and receive data in JSON format. JSON is just a way to represent structured data as text.

{
"name": "Sahil",
"age": 21
}
data = response.json()
print(data["name"])

When reading JSON from an API, keep these things in mind:

  • check that the keys you expect are actually there
  • handle missing fields safely
  • do not assume every response from an API looks exactly the same

Safe example using .get():

user = response.json()
name = user.get("name", "Unknown")
print(name)

If the "name" key is missing, .get() returns "Unknown" instead of crashing.

Real servers need to handle many clients at the same time. If you handle one client at a time and make all the others wait in line, your app will feel very slow.

Common ways to handle many clients at once:

  • threads - easy to set up and understand
  • processes - good for tasks that use a lot of CPU power
  • async I/O - great when you have many connections that spend most of their time waiting
import socket
import threading
def handle_client(client_socket):
data = client_socket.recv(1024)
client_socket.send(b"OK")
client_socket.close()
server = socket.socket()
server.bind(("localhost", 8080))
server.listen()
while True:
client, addr = server.accept()
thread = threading.Thread(target=handle_client, args=(client,))
thread.start()

This is a simple approach and easy to understand. The downside is that it can use a lot of memory and system resources if thousands of clients connect at the same time.

flowchart TD A[Client 1] --> S[Server] B[Client 2] --> S C[Client 3] --> S S -->|Threaded Handling| D[Parallel Execution]

Async is a good fit when your program spends most of its time waiting for network responses rather than doing heavy computation.

Instead of creating one thread per connection (which wastes resources), async lets a single event loop manage many connections at the same time by switching between them whenever one is waiting.

import asyncio
async def main():
print("Start")
await asyncio.sleep(2)
print("End")
asyncio.run(main())

For real async networking, most developers use libraries built on top of asyncio like aiohttp, httpx (in async mode), or async web frameworks.

flowchart LR A[Start Task] --> B[Await I/O] B --> C[Switch Task] C --> D[Resume Task]

Before your request can reach a server, something called DNS (Domain Name System) has to translate the website name into an IP address - because computers use IP addresses, not names.

Here is what happens step by step when you visit a website:

  • You request https://example.com
  • DNS looks up the IP address for example.com
  • A TCP/TLS connection is created to that IP
  • Your HTTP request is sent
  • The server sends back a response

Understanding this flow helps a lot when debugging issues like “it works on my computer but not on a different network.”

Network calls fail in the real world. Servers go down, internet connections drop, and responses can be slow. You should always write your networking code with the assumption that something might go wrong.

Good habits:

  • always set a timeout so your program does not hang
  • retry the request a few times before giving up
  • log what went wrong so you can diagnose issues later

Simple retry example:

import time
import requests
url = "https://api.example.com/health"
for attempt in range(3):
try:
r = requests.get(url, timeout=5)
r.raise_for_status()
print("Service is healthy")
break
except requests.exceptions.RequestException:
if attempt == 2:
print("Service check failed after retries")
else:
time.sleep(1)
  • HTTPS is the secure version of HTTP
  • It uses SSL/TLS encryption to protect data in transit

Never send passwords, tokens, or any sensitive data over plain HTTP in a real app. Always use HTTPS.

  • Man-in-the-middle attack - someone intercepts data between client and server
  • Data interception - sensitive data is read by an unintended party
  • Injection attacks - malicious input is used to manipulate the server
  • Weak authentication - using easy-to-guess or reused passwords/tokens
  • Leaking secrets in logs - accidentally printing API keys or passwords to log files
  • Always use HTTPS
  • Validate all inputs before using them
  • Use authentication tokens instead of passwords where possible
  • Never hardcode secrets (API keys, passwords) in your source code
  • Rate-limit sensitive endpoints to slow down attackers
  • Be careful about what you log - never log sensitive data

Network programming is used in:

  • Web servers (Django, Flask)
  • APIs
  • Chat applications
  • Multiplayer games
  • Microservices (small services that talk to each other)

It is also used in:

  • IoT devices (smart home gadgets, sensors)
  • Payment gateways
  • Cloud monitoring systems
  • forgetting to use .encode() and .decode() when sending/receiving socket data
  • not setting a timeout on HTTP calls
  • assuming network requests will always succeed
  • only testing on your own machine and not thinking about real network conditions
  • mixing business logic and networking code together in one big function
  1. Build a simple TCP client and server that talk on your own machine
  2. Build a UDP echo app
  3. Use requests to call a public API
  4. Add error handling and retries to your API calls
  5. Build a threaded server that handles multiple clients at the same time
  6. Learn async networking for handling many connections efficiently
  7. Add authentication, logging, and monitoring
  1. Create an echo server - whatever the client sends, the server sends straight back.
  2. Build a small HTTP API checker that prints the status code and how long the request took.
  3. Add a timeout and retry logic to your API checker.
  4. Build a tiny single-room chat app using sockets.

These small projects are the fastest way to make networking concepts click.

flowchart TD A[Client] --> B[DNS] B --> C[Server IP] A --> D[HTTP Request] D --> C C --> E[Process Request] E --> F[Response] F --> A