Python's socket module provides low-level access to network interfaces. While most projects use higher-level libraries like requests, understanding sockets helps debug networking issues and build custom protocols.

TCP Server Basics

import socket
 
# Create TCP socket
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
 
# Allow address reuse (avoid "address already in use")
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
 
# Bind to address and port
server.bind(('localhost', 9999))
 
# Listen for connections (backlog of 5)
server.listen(5)
print("Server listening on port 9999...")
 
while True:
    # Accept connection
    client, address = server.accept()
    print(f"Connection from {address}")
    
    # Receive data
    data = client.recv(1024)
    print(f"Received: {data.decode()}")
    
    # Send response
    client.send(b"Hello from server!")
    
    # Close connection
    client.close()

TCP Client

import socket
 
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect(('localhost', 9999))
 
# Send data
client.send(b"Hello from client!")
 
# Receive response
response = client.recv(1024)
print(f"Server said: {response.decode()}")
 
client.close()

Context Managers

import socket
 
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
    sock.connect(('localhost', 9999))
    sock.send(b"Hello!")
    response = sock.recv(1024)

Socket closes automatically when leaving the with block.

UDP (Connectionless)

# UDP Server
import socket
 
server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
server.bind(('localhost', 9999))
 
while True:
    data, addr = server.recvfrom(1024)
    print(f"From {addr}: {data.decode()}")
    server.sendto(b"Got it!", addr)
# UDP Client
import socket
 
client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
client.sendto(b"Hello UDP!", ('localhost', 9999))
response, _ = client.recvfrom(1024)
print(response.decode())

Handling Multiple Clients

Using select for non-blocking I/O:

import socket
import select
 
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server.bind(('localhost', 9999))
server.listen(5)
server.setblocking(False)
 
sockets = [server]
 
while True:
    readable, _, _ = select.select(sockets, [], [], 1)
    
    for sock in readable:
        if sock is server:
            # New connection
            client, addr = server.accept()
            client.setblocking(False)
            sockets.append(client)
            print(f"New connection from {addr}")
        else:
            # Data from existing client
            try:
                data = sock.recv(1024)
                if data:
                    print(f"Received: {data.decode()}")
                    sock.send(b"Echo: " + data)
                else:
                    # Empty data means disconnect
                    sockets.remove(sock)
                    sock.close()
            except ConnectionResetError:
                sockets.remove(sock)
                sock.close()

Timeouts

import socket
 
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(5.0)  # 5 second timeout
 
try:
    sock.connect(('example.com', 80))
    sock.send(b"GET / HTTP/1.1\r\nHost: example.com\r\n\r\n")
    response = sock.recv(4096)
except socket.timeout:
    print("Connection timed out")
finally:
    sock.close()

Getting Host Information

import socket
 
# Hostname to IP
ip = socket.gethostbyname('example.com')
print(f"IP: {ip}")
 
# Full DNS lookup
info = socket.getaddrinfo('example.com', 80)
for item in info:
    print(item)
 
# Get local hostname
hostname = socket.gethostname()
local_ip = socket.gethostbyname(hostname)
print(f"Local: {hostname} ({local_ip})")

Simple HTTP Request

import socket
 
def http_get(host, path='/'):
    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
        sock.connect((host, 80))
        
        request = f"GET {path} HTTP/1.1\r\n"
        request += f"Host: {host}\r\n"
        request += "Connection: close\r\n\r\n"
        
        sock.send(request.encode())
        
        response = b""
        while True:
            chunk = sock.recv(4096)
            if not chunk:
                break
            response += chunk
        
        return response.decode()
 
html = http_get('example.com')
print(html[:500])

Binary Data

import socket
import struct
 
# Pack data into binary format
data = struct.pack('!HH', 1234, 5678)  # Two unsigned shorts
 
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
    sock.connect(('localhost', 9999))
    sock.send(data)
    
    response = sock.recv(4)
    val1, val2 = struct.unpack('!HH', response)

IPv6

import socket
 
# IPv6 socket
sock = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
sock.connect(('::1', 9999))  # localhost IPv6

Common Socket Options

import socket
 
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
 
# Reuse address
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
 
# Keep connection alive
sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
 
# Set send/receive buffer sizes
sock.setsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF, 65536)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, 65536)
 
# Disable Nagle's algorithm (send immediately)
sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)

Error Handling

import socket
 
try:
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.connect(('nonexistent.example', 80))
except socket.gaierror:
    print("DNS lookup failed")
except socket.timeout:
    print("Connection timed out")
except ConnectionRefusedError:
    print("Connection refused")
except socket.error as e:
    print(f"Socket error: {e}")
finally:
    sock.close()

Summary

The socket module is Python's interface to network programming fundamentals. Use TCP (SOCK_STREAM) for reliable connections, UDP (SOCK_DGRAM) for speed without guarantees. While you'll typically use higher-level libraries, understanding sockets helps when debugging or building custom protocols.

React to this post: