Filter concepts by levelShowing all levels.

Python · Networking

Networking

The TCP/IP stack underneath every Python network call — addressing, sockets, DNS, TLS, and the infrastructure that sits between client and server

  • (IP, port)the pair that actually identifies a reachable service
  • TCP vs UDPordered and guaranteed, or fast and best-effort
  • Reverse proxy + LBhide and distribute traffic across backend instances
  • WebSockets vs SSEfull-duplex upgrade, or a one-way stream over plain HTTP

1.TCP/IP, IP addresses, and ports

TCP/IP is the layered set of protocols the internet runs on: IP delivers a packet to the right HOST using an IP address, and a port number then delivers it to the right PROCESS on that host. TCP is the layer on top that makes delivery reliable and ordered.

Key features

  • An IPv4 address is 32 bits, written as four dotted decimal octets (192.168.1.10); IPv6 is 128 bits, written in colon-separated hex groups
  • A port is a 16-bit number (0-65535) identifying a specific process on a host — 0-1023 are well-known ports (80 HTTP, 443 HTTPS, 22 SSH), assigned by IANA
  • A TCP/UDP connection endpoint is the PAIR (IP address, port) — the same host can run many independent services, one per port
  • The internet protocol stack layers roughly: Link (Ethernet/Wi-Fi) -> Internet (IP) -> Transport (TCP/UDP) -> Application (HTTP, DNS, ...), each layer only trusting the guarantees of the one below it
  • IP itself is unreliable and connectionless — it does not guarantee a packet arrives, arrives once, or arrives in order; TCP is the layer that adds those guarantees on top

The four layers a web request actually crosses

The four layers a web request actually crosses
LayerExample protocolWhat it addresses
ApplicationHTTP, DNS, SSHthe meaning of the data (a request, a query, a login)
TransportTCP, UDPa process on the host, via a port number
InternetIP (v4/v6)a host, via an IP address
LinkEthernet, Wi-Fia device on the local physical network
IP finds the host; the port finds the process on it

Application data

an HTTP request, a DNS query, ...

TCP/UDP + port

e.g. destination port 443 — picks the process

IP + IP address

e.g. destination 192.168.1.10 — picks the host

Link layer

Ethernet/Wi-Fi frame — picks the device on this wire

  1. Application data — an HTTP request, a DNS query, ...
  2. TCP/UDP + port — e.g. destination port 443 — picks the process
  3. IP + IP address — e.g. destination 192.168.1.10 — picks the host
  4. Link layer — Ethernet/Wi-Fi frame — picks the device on this wire

Command

python
import ipaddress

ip = ipaddress.ip_address('192.168.1.10')      # ValueError if not a valid IPv4/IPv6 address
net = ipaddress.ip_network('192.168.1.0/24')   # a whole subnet
port = 443                                     # just an int in [0, 65535] -- no dedicated type

An IP address routes to a HOST; a port routes to a PROCESS on that host; a service is really identified by the (IP, port) pair together, not by either alone.

2.Sockets

A socket is a programming handle for one end of a network connection -- socket.socket() creates it, .bind()/.listen()/.accept() set up a server side, and .connect() sets up a client side. Every HTTP request, database driver, and SSH session is built on sockets underneath.

Key features

  • socket.socket(socket.AF_INET, socket.SOCK_STREAM) creates a TCP socket over IPv4; SOCK_DGRAM creates a UDP socket instead
  • Server side: .bind((host, port)) claims an address, .listen() marks it ready to accept connections, .accept() blocks until a client connects and returns a NEW socket for that one connection
  • Client side: .connect((host, port)) performs the TCP three-way handshake before returning
  • .send()/.recv() (TCP) or .sendto()/.recvfrom() (UDP) move bytes -- sockets work in bytes, not str, so encode/decode text explicitly
  • .close() releases the underlying OS file descriptor -- an unclosed socket leaks resources exactly like an unclosed file

Core socket methods, server vs. client side

Core socket methods, server vs. client side
MethodSidePurpose
socket.socket(family, type)bothcreate the socket object (not yet connected)
.bind((host, port))serverclaim a local address to listen on
.listen(backlog)servermark the socket ready to accept incoming connections
.accept()serverblock until a client connects; returns (new_socket, addr)
.connect((host, port))clientperform the TCP handshake to a listening server
.send(bytes) / .recv(bufsize)bothwrite/read bytes once connected
.close()bothrelease the OS socket resource
The TCP handshake a socket performs before any data moves
Client
Server
  1. 1bind() + listen()claims the address, starts waiting
  2. 2connect() -> SYN
  3. 3SYN-ACK
  4. 4ACK -- handshake done
  5. 5accept() returnsa new socket for this one connection
  6. 6send()/recv() data
  1. Server → Server: bind() + listen() (claims the address, starts waiting)
  2. Client → Server: connect() -> SYN
  3. Server → Client: SYN-ACK
  4. Client → Server: ACK -- handshake done
  5. Server → Server: accept() returns (a new socket for this one connection)
  6. Client → Server: send()/recv() data

Command

python
import socket

# server
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(('0.0.0.0', 8080))
s.listen(5)
conn, addr = s.accept()      # blocks until a client connects

# client
c = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
c.connect(('example.com', 8080))

A socket is bytes-in/bytes-out once connected: server calls bind()+listen()+accept(), client calls connect() -- and .recv() can return a partial message, so loop until your protocol says you have the whole thing.

3.TCP vs UDP

TCP sets up a connection first, then guarantees ordered, complete delivery -- at the cost of setup time and retransmission delays. UDP sends datagrams with no setup and no delivery guarantee, but with lower latency and less overhead, so it fits real-time data where a late packet is worse than a lost one.

Key features

  • TCP (SOCK_STREAM) requires a handshake (connect()/accept()) before any data moves; UDP (SOCK_DGRAM) sends a datagram immediately with sendto(), no connection setup
  • TCP guarantees delivery, ordering, and no duplication via acknowledgment and retransmission; UDP guarantees none of these -- a datagram can be lost, duplicated, or arrive out of order
  • TCP has per-packet overhead (headers, ACKs, retransmission logic) that adds latency; UDP has a much smaller header and no retransmission delay
  • Typical TCP uses: HTTP, database connections, file transfer -- anywhere a dropped byte must not be silently ignored
  • Typical UDP uses: DNS queries, video/voice calls, online games -- anywhere a late retransmit is worse than just losing that one packet and moving on

TCP vs UDP, side by side

TCP vs UDP, side by side
PropertyTCPUDP
ConnectionRequired (three-way handshake)None -- send immediately
Delivery guaranteeGuaranteed, retransmits on lossBest-effort, no retransmission
OrderingGuaranteed in-orderNot guaranteed
Overhead20+ byte header, ACKs, retransmit logic8 byte header, no ACKs
socket typeSOCK_STREAMSOCK_DGRAM
Typical useHTTP, databases, file transferDNS, video/voice, gaming

Command

python
import socket

tcp = socket.socket(socket.AF_INET, socket.SOCK_STREAM)   # reliable, ordered, connection-based
udp = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)    # best-effort, connectionless

TCP: handshake first, then guaranteed ordered delivery. UDP: no handshake, no guarantees, lower latency -- choose UDP only when a late packet is worse than a lost one.

4.DNS

DNS (Domain Name System) translates a human-readable hostname like www.python.org into the IP address a computer actually needs to open a connection. Python resolves it via the OS resolver with socket.gethostbyname() or the more complete socket.getaddrinfo().

Key features

  • socket.gethostbyname(host) returns one IPv4 address as a string, resolved through the OS DNS resolver
  • socket.getaddrinfo(host, port) is the more complete API -- it can return multiple addresses (IPv4 and IPv6) and the info needed to open a socket to each
  • DNS lookups are cached at multiple layers (OS, browser, resolver) with a Time To Live (TTL) set by the domain owner -- a changed IP is not visible everywhere instantly
  • A failed lookup raises socket.gaierror ("getaddrinfo error"), not a generic connection error -- distinguishing "name did not resolve" from "server did not respond"
  • connect()ing to a hostname (not a bare IP) always performs a DNS lookup first, even though it looks like one step in code

DNS resolution calls in the socket module

DNS resolution calls in the socket module
CallReturnsWhen to use
socket.gethostbyname(host)a single IPv4 stringquick lookup, IPv4 only, one result
socket.getaddrinfo(host, port)list of (family, type, proto, canonname, sockaddr) tuplesproduction code -- handles IPv4/IPv6, multiple results
socket.gethostbyname_ex(host)(hostname, aliaslist, ipaddrlist)need every IPv4 address, not just one
socket.gaierrorexceptionraised when a name fails to resolve at all

Command

python
import socket

ip = socket.gethostbyname('www.python.org')          # one IPv4 address
infos = socket.getaddrinfo('www.python.org', 443)     # full list, IPv4 + IPv6

DNS turns a hostname into an IP address before any connection can open -- gethostbyname() for a quick single result, getaddrinfo() for production code that needs IPv4/IPv6 and multiple results.

5.HTTP, HTTPS, and TLS in the protocol stack

HTTP is an application-layer protocol that runs over a TCP connection. HTTPS is that same HTTP, sent over a connection wrapped in TLS -- so the TCP handshake happens first, then a TLS handshake negotiates encryption, and only then does the HTTP request/response exchange begin.

Key features

  • HTTP is application-layer; it needs an already-open, reliable byte stream underneath -- in practice, a TCP connection
  • HTTPS = HTTP + TLS: the TCP handshake completes first, then a separate TLS handshake negotiates encryption before any HTTP bytes are sent
  • TLS operates below HTTP and above TCP -- it does not know or care about HTTP methods, headers, or status codes; it only encrypts whatever bytes the layer above hands it
  • HTTP method/status/header semantics, HTTPS deployment, and application-level TLS/same-origin concerns are covered in full in Web and HTTP Fundamentals -- this concept is the transport-layer view only
  • The default ports reflect the layering: HTTP defaults to port 80 (no TLS step), HTTPS defaults to port 443 (TLS handshake first)

Where each protocol sits in the stack

Where each protocol sits in the stack
LayerProtocolDefault port
ApplicationHTTP80
Application + SecurityHTTPS (HTTP over TLS)443
Transport-securityTLSn/a -- wraps the transport connection
TransportTCPn/a -- carries both

Command

python
import ssl, socket

ctx = ssl.create_default_context()                          # sensible defaults: verify cert, check hostname
with socket.create_connection((host, 443)) as sock:          # TCP handshake first
    with ctx.wrap_socket(sock, server_hostname=host) as tls: # TLS handshake wraps the open TCP socket
        ...                                                  # only now is it safe to send HTTP bytes

HTTP needs a reliable byte stream (TCP) underneath; HTTPS adds a TLS handshake between the TCP handshake and the first HTTP byte. For HTTP methods, status codes, headers, and the application-security view of HTTPS/TLS, see Web and HTTP Fundamentals.

6.Reverse proxy and load balancer

A reverse proxy sits in front of one or more backend servers and forwards client requests to them, hiding the backend from the client. A load balancer is a reverse proxy whose main job is splitting traffic across multiple backend instances so no single one is overwhelmed.

Key features

  • A FORWARD proxy sits in front of CLIENTS, hiding the client from the server; a REVERSE proxy sits in front of SERVERS, hiding the server from the client -- opposite sides of the connection
  • nginx and HAProxy are common reverse proxies; a reverse proxy commonly also handles TLS termination, request buffering, and static file serving so the application server does not have to
  • A load balancer distributes requests across multiple backend instances using a strategy: round robin (rotate through instances), least connections (send to the least busy), or IP hash (same client always reaches the same instance)
  • A load balancer needs health checks to stop routing to a backend instance that has crashed or is failing -- otherwise it keeps sending traffic to a dead target
  • Gunicorn/uWSGI serving a Python app are typically run BEHIND a reverse proxy (nginx), not exposed to the internet directly -- the proxy handles TLS, slow clients, and static files so the Python process only handles application logic

Reverse proxy vs. load balancer vs. forward proxy

Reverse proxy vs. load balancer vs. forward proxy
ComponentSits in front ofPrimary job
Forward proxyclientshide/control the client, e.g. corporate egress filtering
Reverse proxyserver(s)hide the backend, handle TLS termination, route by path/host
Load balancermultiple server instancesdistribute requests so no one instance is overwhelmed
nginx / HAProxyserver(s)real software that can act as either or both at once

Command

text
# nginx reverse proxy + load balancer, conceptual config
upstream app_servers {
    server 10.0.0.1:8000;
    server 10.0.0.2:8000;
}
server {
    listen 443 ssl;
    location / {
        proxy_pass http://app_servers;   # nginx picks an instance and forwards the request
    }
}

A reverse proxy hides servers from clients; a load balancer is a reverse proxy that specifically spreads requests across multiple backend instances, using health checks to skip dead ones.

7.Connection pooling and timeouts

Connection pooling keeps a set of already-open TCP connections ready to reuse, instead of opening (and TLS-handshaking) a new one per request. A timeout is a limit on how long code waits for a connection or response before giving up and raising an error instead of hanging forever.

Key features

  • Opening a TCP connection costs a round trip (the handshake); adding TLS costs another. A pool amortizes that cost across many requests to the same host by keeping connections open and reusable
  • This applies beyond HTTP -- database drivers (psycopg2's pool, SQLAlchemy's engine pool) hold open connections for the same reason, since reconnecting per query is far slower than reusing one
  • A connection-level timeout limits how long to wait for the TCP handshake to complete; a separate read/response timeout limits how long to wait for data once connected -- conflating the two hides which stage actually failed
  • No timeout at all means code can hang indefinitely on a server that accepts a connection but never responds -- a resource leak and a hung request under load, not a crash that shows up in a stack trace
  • socket.settimeout(seconds) applies to a raw socket's blocking operations (connect, recv); it raises socket.timeout (an alias of TimeoutError) once the limit passes

Timeout types worth setting separately

Timeout types worth setting separately
Timeout kindWhat it boundsCommon default risk
Connect timeouttime to complete the TCP (+TLS) handshakeunreachable host hangs instead of failing fast
Read timeouttime waiting for response data once connecteda slow/stalled server hangs the caller indefinitely
Pool checkout timeouttime waiting for a free connection from an exhausted poola saturated pool silently queues callers with no bound

Command

python
import socket

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.settimeout(5)             # 5 seconds for connect() AND subsequent recv() calls
s.connect((host, port))     # raises socket.timeout if the handshake takes too long

Reuse connections (a pool) instead of paying handshake cost per call; set an explicit timeout on every network call, or a dead/slow peer hangs the caller with no error at all.

8.Proxies and NAT

A proxy is an application-level intermediary a client deliberately routes traffic through, often to filter, cache, or hide its identity. NAT (Network Address Translation) is a router-level technique that lets many devices on a private network share one public IP address, rewriting addresses in each packet as it crosses the boundary.

Key features

  • A forward proxy sits in front of clients: the client is configured to send traffic through it, commonly for content filtering, caching, or hiding the client's real IP from the destination server
  • NAT lets multiple devices with private IP addresses (RFC 1918: 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16) share a single public IP -- the router rewrites the source address/port on outbound packets and reverses it on the matching reply
  • NAT is why most home devices do not have their own public, internet-routable IP address, and why an unsolicited inbound connection to one specific device behind NAT generally cannot reach it without extra configuration (port forwarding)
  • A proxy operates on complete requests (it can inspect/modify an HTTP request, cache a response); NAT operates on raw packets (it rewrites IP/port headers) and knows nothing about HTTP or any application protocol
  • IPv6's much larger address space removes NAT's original scarcity motivation, but NAT persists widely for IPv4 and for the network-boundary/isolation properties it happens to also provide

Proxy vs. NAT

Proxy vs. NAT
PropertyProxyNAT
Operates atapplication layer (HTTP-aware)network layer (IP packet headers)
Client awarenessclient is typically configured to use itusually fully transparent to the client
Typical purposefiltering, caching, anonymizing, access controlsharing one public IP among many private-IP devices
Can inspect request contentyes -- URLs, headers, bodiesno -- only IP/port headers

Command

python
import httpx

# routing a Python HTTP client's traffic through a forward proxy
with httpx.Client(proxy='http://proxy.internal:8080') as client:
    response = client.get('https://example.com')

A proxy is a deliberate, request-aware intermediary a client is configured to use; NAT is transparent packet-address rewriting at a network boundary so many private devices can share one public IP.

9.WebSockets and Server-Sent Events (SSE)

WebSockets upgrade a single HTTP connection into a persistent, two-way channel -- server and client can both send messages at any time. SSE (Server-Sent Events) is simpler and one-way only: the server streams a sequence of text events to the client over a single long-lived HTTP response.

Key features

  • A WebSocket connection starts as a normal HTTP request with an Upgrade: websocket header; the server responds 101 Switching Protocols, and from then on the same TCP connection carries the WebSocket framing protocol instead of HTTP
  • Once upgraded, a WebSocket is full-duplex: server and client can each send messages independently, at any time, without waiting for a request/response cycle
  • SSE is plain HTTP -- no upgrade or new protocol at all. The client opens a request expecting Content-Type: text/event-stream, and the server keeps the response open, writing one event at a time as data: ...\n\n lines
  • SSE only flows server-to-client; the client cannot send data back over the same stream -- it would issue a separate, normal HTTP request for that
  • Browsers auto-reconnect a dropped SSE connection using EventSource, resuming with a Last-Event-ID header; a dropped WebSocket requires the application to detect the close and manually reconnect

WebSockets vs. SSE

WebSockets vs. SSE
PropertyWebSocketsSSE
Directionfull-duplex (both ways)one-way, server -> client only
ProtocolHTTP upgrade to a distinct framed protocolplain HTTP, kept open (text/event-stream)
Message formatbinary or text frames, app-definedUTF-8 text lines (data:, event:, id:)
Browser reconnectmanual, application handles itautomatic, built into EventSource
Typical usechat, multiplayer, live collaborative editinglive feeds, notifications, progress updates
Full-duplex vs. one-way streaming, both built on HTTP

WebSocket

  • HTTP Upgrade -> 101 Switching Protocols
  • server and client can both send, anytime
  • app must detect a dropped connection and reconnect

SSE (EventSource)

  • plain HTTP response, Content-Type: text/event-stream
  • server -> client only, one direction
  • browser auto-reconnects, resumes via Last-Event-ID
  • WebSocket
    • HTTP Upgrade -> 101 Switching Protocols
    • server and client can both send, anytime
    • app must detect a dropped connection and reconnect
  • SSE (EventSource)
    • plain HTTP response, Content-Type: text/event-stream
    • server -> client only, one direction
    • browser auto-reconnects, resumes via Last-Event-ID

Command

python
# WebSocket server handler (e.g. with the third-party 'websockets' package)
async def handler(ws):
    async for message in ws:      # receive, full-duplex
        await ws.send(f'echo: {message}')

# SSE: a plain streaming HTTP response (e.g. with a WSGI/ASGI framework)
def sse_view():
    def event_stream():
        yield 'data: {"progress": 10}\n\n'
        yield 'data: {"progress": 42}\n\n'
    return Response(event_stream(), content_type='text/event-stream')

WebSockets: one HTTP Upgrade handshake, then full-duplex messaging either side can start; SSE: plain HTTP response kept open, server-to-client only, with automatic browser reconnect built in.

Remember

  • A service is identified by (IP address, port) together — an IP alone says nothing about which process is reachable.
  • TCP: handshake first, then guaranteed ordered delivery. UDP: no handshake, no guarantees, lower latency.
  • Every network call needs an explicit timeout — a peer that accepts a connection but never responds hangs forever otherwise.
  • WebSockets are full-duplex over an upgraded connection; SSE is one-way, plain HTTP, with automatic browser reconnect.

Pro tip

  • Reuse connections (a pool) instead of paying a fresh TCP+TLS handshake per call — the same principle applies below HTTP too.
  • A reverse proxy needs health checks, or it keeps routing traffic to a backend instance that already crashed.
  • NAT is transparent packet-address rewriting; a proxy is a deliberate, request-aware intermediary the client is configured to use.
  • For HTTP methods, status codes, headers, and the application-security view of HTTPS/TLS, see Web and HTTP Fundamentals.

Next up

  • Operating Systems and Systems Programming

Sources

  • socket — docs.python.org/3/library/socket.html
  • ssl — docs.python.org/3/library/ssl.html
  • TCP/UDP, DNS, and TLS run for real on Python 3.14.3 · checked 2026-08-22

Spotted a mistake on this page? Tell us.