Robust error handling is crucial for production socket applications. This module covers common errors and how to handle them properly.

Common Socket Errors

Connection Errors

ConnectionRefusedError

Occurs when: Server not listening or rejecting connections

try:
    client.connect(('localhost', 8080))
except ConnectionRefusedError:
    print("Server not accepting connections")
    # Handle: retry, notify user, fallback

Solutions: - Check if server is running - Verify port number - Check firewall settings - Implement retry logic

ConnectionResetError

Occurs when: Remote side forcibly closed connection

try:
    data = client.recv(1024)
except ConnectionResetError:
    print("Connection reset by peer")
    # Handle: cleanup, reconnect if needed

Solutions: - Gracefully close local socket - Handle reconnection logic - Log error for debugging

ConnectionAbortedError

Occurs when: Connection aborted locally (software caused)

try:
    client.send(data)
except ConnectionAbortedError:
    print("Connection aborted")

Solutions: - Check for local network issues - Verify socket is still valid

TimeoutError

Occurs when: Operation exceeds timeout period

client.settimeout(5.0)
try:
    client.connect(('localhost', 8080))
except TimeoutError:
    print("Connection timed out")

Solutions: - Increase timeout value - Check network connectivity - Implement retry with backoff

Address Resolution Errors

socket.gaierror

Occurs when: Hostname cannot be resolved to IP address

try:
    client.connect(('invalid-hostname.com', 80))
except socket.gaierror as e:
    print(f"DNS resolution failed: {e}")

Solutions: - Verify hostname spelling - Check DNS configuration - Use IP address instead of hostname - Provide fallback addresses

Binding Errors

OSError: [Errno 48] Address already in use

Occurs when: Port already bound by another process

try:
    server.bind(('localhost', 8080))
except OSError as e:
    if e.errno == 48:  # Address already in use
        print("Port 8080 already in use")
        # Solutions:
        # 1. Use SO_REUSEADDR
        # 2. Choose different port
        # 3. Stop conflicting process

Solutions:

# Set SO_REUSEADDR before bind
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server.bind(('localhost', 8080))

OSError: [Errno 13] Permission denied

Occurs when: Binding to privileged port (< 1024) without root/admin

try:
    server.bind(('localhost', 80))  # Privileged port
except PermissionError:
    print("Need root/admin privileges for port < 1024")
    # Use port >= 1024 instead

Solutions: - Use port >= 1024 - Run with elevated privileges (not recommended) - Use reverse proxy (nginx, etc.)

Data Transfer Errors

BrokenPipeError

Occurs when: Trying to send data to closed connection

try:
    client.send(data)
except BrokenPipeError:
    print("Connection closed")
    client.close()

Solutions: - Check connection state before sending - Handle gracefully, close socket

socket.timeout

Occurs when: Operation exceeds timeout

client.settimeout(10.0)
try:
    data = client.recv(1024)
except socket.timeout:
    print("Receive timeout")
    # Handle: retry, close connection, etc.

Solutions: - Adjust timeout values - Implement retry logic - Handle as normal condition, not error

Comprehensive Error Handling Pattern

Server with Error Handling

import socket
import sys
import logging

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

def handle_client(client, address):
    """Handle client with comprehensive error handling."""
    logger.info(f"Handling client {address}")

    try:
        client.settimeout(30.0)  # 30 second timeout

        while True:
            try:
                data = client.recv(1024)

                if not data:
                    logger.info(f"Client {address} closed connection")
                    break

                # Process data
                response = process_data(data)

                # Send response
                try:
                    client.sendall(response)
                except (BrokenPipeError, ConnectionResetError) as e:
                    logger.warning(f"Error sending to {address}: {e}")
                    break

            except socket.timeout:
                logger.warning(f"Timeout waiting for data from {address}")
                # Send keepalive or close
                break

            except ConnectionResetError:
                logger.info(f"Connection reset by {address}")
                break

            except Exception as e:
                logger.error(f"Unexpected error with {address}: {e}")
                break

    except Exception as e:
        logger.error(f"Fatal error handling {address}: {e}")

    finally:
        try:
            client.close()
        except:
            pass
        logger.info(f"Connection to {address} closed")

def process_data(data):
    """Process received data."""
    try:
        return data.upper()
    except Exception as e:
        logger.error(f"Error processing data: {e}")
        return b"ERROR"

def robust_server(host='localhost', port=8080):
    """Server with comprehensive error handling."""

    server = None

    try:
        # Create socket
        server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

        # Bind
        try:
            server.bind((host, port))
        except OSError as e:
            if e.errno == 48:  # Address already in use
                logger.error(f"Port {port} already in use")
                sys.exit(1)
            elif e.errno == 13:  # Permission denied
                logger.error(f"Permission denied for port {port}")
                sys.exit(1)
            else:
                raise

        # Listen
        server.listen(5)
        logger.info(f"Server listening on {host}:{port}")

        while True:
            try:
                client, address = server.accept()
                handle_client(client, address)

            except KeyboardInterrupt:
                logger.info("Shutting down server...")
                break

            except Exception as e:
                logger.error(f"Error accepting connection: {e}")
                continue

    except Exception as e:
        logger.error(f"Fatal server error: {e}")
        sys.exit(1)

    finally:
        if server:
            server.close()
        logger.info("Server closed")

if __name__ == '__main__':
    robust_server()

Client with Error Handling

import socket
import time
import logging

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

def robust_client(host='localhost', port=8080, max_retries=3):
    """Client with comprehensive error handling and retry logic."""

    for attempt in range(max_retries):
        client = None

        try:
            # Create socket
            client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            client.settimeout(10.0)

            # Connect
            logger.info(f"Attempting connection to {host}:{port} (attempt {attempt + 1})")
            try:
                client.connect((host, port))
                logger.info("Connected successfully")
            except ConnectionRefusedError:
                logger.error("Connection refused - is server running?")
                if attempt < max_retries - 1:
                    time.sleep(2 ** attempt)  # Exponential backoff
                    continue
                return None

            except socket.gaierror as e:
                logger.error(f"DNS resolution failed: {e}")
                return None

            except socket.timeout:
                logger.error("Connection timeout")
                if attempt < max_retries - 1:
                    time.sleep(2 ** attempt)
                    continue
                return None

            # Communicate
            try:
                # Send
                message = b"Hello, Server!"
                client.sendall(message)
                logger.info("Message sent")

                # Receive
                response = client.recv(1024)
                if response:
                    logger.info(f"Response: {response.decode()}")
                else:
                    logger.warning("Empty response received")

                return client  # Success

            except (BrokenPipeError, ConnectionResetError) as e:
                logger.warning(f"Connection lost during communication: {e}")
                if attempt < max_retries - 1:
                    time.sleep(2 ** attempt)
                    continue
                return None

            except socket.timeout:
                logger.warning("Communication timeout")
                return None

        except Exception as e:
            logger.error(f"Unexpected error: {e}")
            if attempt < max_retries - 1:
                time.sleep(2 ** attempt)
                continue
            return None

        finally:
            if client:
                client.close()

    logger.error("Max retries reached")
    return None

if __name__ == '__main__':
    robust_client()

Error Handling Best Practices

1. Always Use try-except

# Bad
client.send(data)

# Good
try:
    client.sendall(data)
except (BrokenPipeError, ConnectionResetError):
    handle_connection_error()

2. Check Connection State

def is_connected(sock):
    """Check if socket is still connected."""
    try:
        sock.getpeername()
        return True
    except socket.error:
        return False

# Usage
if is_connected(client):
    client.send(data)

3. Use Context Managers

# Python 3.3+ automatically closes sockets
with socket.socket(...) as sock:
    sock.connect(...)
    # use socket
# Automatically closed

4. Set Appropriate Timeouts

# Short timeout for connections
client.settimeout(5.0)
client.connect(...)

# Longer timeout for data transfer
client.settimeout(30.0)
data = client.recv(1024)

5. Handle Partial Sends/Receives

def send_all(sock, data):
    """Send all data, handling partial sends."""
    total_sent = 0
    while total_sent < len(data):
        try:
            sent = sock.send(data[total_sent:])
            if sent == 0:
                raise RuntimeError("Socket connection broken")
            total_sent += sent
        except socket.error as e:
            raise

def recv_all(sock, size):
    """Receive exactly 'size' bytes."""
    data = b''
    while len(data) < size:
        chunk = sock.recv(size - len(data))
        if not chunk:
            raise ConnectionError("Connection closed")
        data += chunk
    return data

6. Log Errors Appropriately

import logging

logger = logging.getLogger(__name__)

try:
    client.connect(...)
except ConnectionRefusedError:
    logger.warning("Connection refused")  # Expected in some cases
except Exception as e:
    logger.error(f"Unexpected error: {e}", exc_info=True)  # Full traceback

7. Implement Retry Logic

import time

def connect_with_retry(host, port, max_retries=3):
    for attempt in range(max_retries):
        try:
            client = socket.socket(...)
            client.connect((host, port))
            return client
        except ConnectionRefusedError:
            if attempt < max_retries - 1:
                time.sleep(2 ** attempt)  # Exponential backoff
                continue
            raise

Error Recovery Strategies

1. Graceful Degradation

try:
    # Try primary server
    client.connect(('primary.example.com', 8080))
except:
    # Fallback to secondary
    client.connect(('secondary.example.com', 8080))

2. Connection Pooling

class ConnectionPool:
    def __init__(self, host, port, pool_size=5):
        self.host = host
        self.port = port
        self.pool = []
        self.pool_size = pool_size

    def get_connection(self):
        # Try to reuse existing connection
        while self.pool:
            conn = self.pool.pop()
            if self.is_valid(conn):
                return conn

        # Create new connection
        return self.create_connection()

    def return_connection(self, conn):
        if len(self.pool) < self.pool_size and self.is_valid(conn):
            self.pool.append(conn)
        else:
            conn.close()

3. Health Checks

def health_check(client):
    """Check if connection is healthy."""
    try:
        client.settimeout(1.0)
        # Send ping
        client.sendall(b"PING")
        # Receive pong
        response = client.recv(4)
        return response == b"PONG"
    except:
        return False

Key Takeaway: Comprehensive error handling makes your socket applications robust and production-ready. Always handle common errors, implement retry logic, and provide meaningful error messages.