Understanding the socket lifecycle is crucial for writing robust network applications. This module explains the complete lifecycle from creation to termination.

TCP Socket Lifecycle

Server Socket Lifecycle

1. socket()          → Create socket
   ↓
2. bind()            → Bind to address/port
   ↓
3. listen()          → Start listening
   ↓
4. accept()          → Wait for connection (BLOCKING)
   ↓
5. [New Client Socket Created]
   ↓
6. recv()/send()     → Communicate with client
   ↓
7. close()           → Close client socket
   ↓
8. [Back to step 4 for next client]
   ↓
9. close()           → Close server socket (when done)

Client Socket Lifecycle

1. socket()          → Create socket
   ↓
2. connect()         → Connect to server (BLOCKING)
   ↓
3. send()/recv()     → Communicate
   ↓
4. close()           → Close socket

Detailed Lifecycle Stages

Stage 1: Creation (socket())

Purpose: Allocate resources for socket

What happens: - Operating system allocates file descriptor - Socket structure created in kernel - Socket is in CLOSED state (TCP) or UNBOUND (UDP)

Code:

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# Socket created but not yet usable

State: Socket exists but not bound to any address


Stage 2: Binding (bind()) - Server Only

Purpose: Associate socket with local address/port

What happens: - Socket gets a local address (IP + port) - Port becomes "reserved" for this socket - Other processes cannot use this port - Socket enters LISTENING state (after listen())

Code:

server.bind(('0.0.0.0', 8080))
# Socket now bound to port 8080

Common Issues: - Port already in use → Address already in use - Insufficient privileges → Permission denied (ports < 1024) - Invalid address → Cannot assign requested address

State: Socket bound, ready to listen or send


Stage 3: Listening (listen()) - TCP Server Only

Purpose: Make socket accept incoming connections

What happens: - Socket enters LISTEN state - OS creates connection queue - Socket can now accept connections - Backlog parameter sets queue size

Code:

server.listen(5)  # Allow 5 pending connections
# Socket now listening

State: LISTEN - waiting for connections


Stage 4: Connecting (connect()) - TCP Client Only

Purpose: Establish connection to server

What happens (3-way handshake): 1. Client sends SYN (synchronize) packet 2. Server responds with SYN-ACK 3. Client sends ACK (acknowledge) 4. Connection established

Code:

client.connect(('192.168.1.100', 8080))
# Connection established (or exception raised)

States: - SYN_SENT → Client sent SYN - ESTABLISHED → Connection active

Common Issues: - Server not listening → Connection refused - Network unreachable → Network unreachable - Firewall blocking → Connection timed out


Stage 5: Accepting (accept()) - TCP Server Only

Purpose: Accept incoming connection

What happens: - Blocks until connection arrives - New socket created for client - Original socket continues listening - Returns client socket and address

Code:

client_sock, client_addr = server.accept()
# New socket for this specific client

Important: - Original server socket unchanged - Each client gets its own socket - Server can handle multiple clients concurrently

State: Client socket in ESTABLISHED state


Stage 6: Data Transfer (send()/recv() or sendto()/recvfrom())

Purpose: Exchange data

TCP:

# Server sends
server_socket.send(b"Hello")

# Client receives
data = client_socket.recv(1024)

# Client sends
client_socket.send(b"Response")

# Server receives
data = server_socket.recv(1024)

UDP:

# Send to specific address
socket.sendto(b"Hello", ('192.168.1.100', 8080))

# Receive from any address
data, addr = socket.recvfrom(1024)

State: ESTABLISHED (TCP) or UNBOUND (UDP, but bound if server)


Stage 7: Closing (close())

Purpose: Terminate connection and free resources

TCP Connection Closure (4-way handshake):

Active Close (client closes): 1. Client sends FIN 2. Server responds with ACK 3. Server sends FIN 4. Client responds with ACK 5. Both sides enter TIME_WAIT

Passive Close (server closes): - Same process, initiated by server

Code:

socket.close()
# Connection terminated gracefully

States During Close: - FIN_WAIT_1 → Sent FIN, waiting for ACK - FIN_WAIT_2 → Received ACK, waiting for FIN - CLOSE_WAIT → Received FIN, waiting for application close - LAST_ACK → Sent FIN, waiting for final ACK - TIME_WAIT → Waiting for any delayed packets (usually 2*MSL) - CLOSED → Socket fully closed

UDP Closing: - Simpler: just close the socket - No handshake needed (connectionless)


Complete TCP Server Example

import socket

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

# Stage 2: Bind
server.bind(('localhost', 8080))

# Stage 3: Listen
server.listen(5)
print("Server listening on port 8080")

try:
    while True:
        # Stage 4: Accept (blocks here)
        client, addr = server.accept()
        print(f"Connection from {addr}")

        try:
            # Stage 5: Communicate
            while True:
                data = client.recv(1024)
                if not data:
                    break

                response = data.upper()
                client.send(response)

        finally:
            # Stage 6: Close client
            client.close()
            print(f"Connection to {addr} closed")

finally:
    # Stage 7: Close server
    server.close()

Complete TCP Client Example

import socket

# Stage 1: Create
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

try:
    # Stage 2: Connect
    client.connect(('localhost', 8080))
    print("Connected to server")

    # Stage 3: Communicate
    client.send(b"Hello, Server!")
    response = client.recv(1024)
    print(f"Response: {response.decode()}")

finally:
    # Stage 4: Close
    client.close()

UDP Socket Lifecycle (Simpler)

Server

# 1. Create
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

# 2. Bind
sock.bind(('localhost', 8080))

# 3. Receive/Send (loop)
while True:
    data, addr = sock.recvfrom(1024)
    sock.sendto(data.upper(), addr)

# 4. Close
sock.close()

Client

# 1. Create
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

# 2. Send (no connection)
sock.sendto(b"Hello", ('localhost', 8080))

# 3. Receive
data, addr = sock.recvfrom(1024)

# 4. Close
sock.close()

State Transitions Diagram (TCP)

CLOSED
  ↓ [socket()]
UNBOUND
  ↓ [bind()]
BOUND
  ↓ [listen()]                    [connect()]
LISTEN ──────────────────────────> SYN_SENT
  ↓ [accept()]                      ↓
SYN_RCVD                           SYN_SENT
  ↓                                  ↓
ESTABLISHED ←────────────────── ESTABLISHED
  ↓                                  ↓
[data transfer]              [data transfer]
  ↓                                  ↓
[close()]                    [close()]
  ↓                                  ↓
FIN_WAIT_1                     FIN_WAIT_1
  ↓                                  ↓
FIN_WAIT_2                     FIN_WAIT_2
  ↓                                  ↓
TIME_WAIT ←────────────────── TIME_WAIT
  ↓                                  ↓
CLOSED                        CLOSED

Common Lifecycle Issues

Port Reuse

Problem: "Address already in use" after closing socket

Solution: Set SO_REUSEADDR option

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

Orphaned Connections

Problem: Client socket closed but server doesn't know

Solution: Check for empty recv()

data = client.recv(1024)
if not data:
    # Connection closed by client
    break

Blocking Operations

Problem: accept(), recv(), connect() block indefinitely

Solutions: - Set timeouts: socket.settimeout(5.0) - Use non-blocking mode - Use select/poll/epoll (advanced)


Key Takeaway: Understanding the socket lifecycle helps you write correct, efficient, and robust network applications. Each stage has specific requirements and common pitfalls to avoid.