Python Socket Programming: Server and Client Example Guide
Practical guide to python socket programming: TCP and UDP examples, threading for multiple clients, blocking vs non-blocking sockets, common errors and fixes, and security tips.
Drake Nguyen
Founder · System Architect
Introduction
Python socket programming lets you build networked applications by creating endpoints (sockets) that send and receive data over TCP or UDP. This guide covers core concepts — creating a simple python socket server and client, handling multiple clients with threading, comparing tcp socket python vs udp socket python, and common debugging tips so you can build reliable python networking tools.
What is socket programming in Python?
At its core, python socket programming maps the networking primitives (bind, listen, accept, connect, send, recv) to Python APIs in the socket module. A socket acts as an endpoint for communication. In a client-server architecture the server binds to an address and port, listens for connections, and accepts clients; the client connects and exchanges data. Sockets can operate over TCP (stream, reliable) or UDP (datagram, fast but unreliable).
Simple TCP: Python socket server and client example
This example demonstrates a basic python socket server and a python socket client exchanging text. The server uses SO_REUSEADDR to avoid the "address already in use" error when restarting during development.
TCP server (socket_server.py)
import socket
import threading
HOST = '127.0.0.1' # localhost
PORT = 5001 # choose a port above 1024
def handle_client(conn, addr):
print(f'Connected by {addr}')
try:
while True:
data = conn.recv(1024)
if not data:
break
text = data.decode('utf-8')
print(f'Received from {addr}: {text}')
conn.sendall(f'ECHO: {text}'.encode('utf-8'))
finally:
conn.close()
print(f'Connection closed: {addr}')
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as server_socket:
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server_socket.bind((HOST, PORT))
server_socket.listen()
print(f'Server listening on {HOST}:{PORT}')
while True:
conn, addr = server_socket.accept()
thread = threading.Thread(target=handle_client, args=(conn, addr), daemon=True)
thread.start()
TCP client (socket_client.py)
import socket
HOST = '127.0.0.1'
PORT = 5001
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
try:
s.connect((HOST, PORT))
except ConnectionRefusedError:
print('Connection refused: is the server running?')
else:
while True:
msg = input('-> ')
if not msg:
continue
s.sendall(msg.encode('utf-8'))
data = s.recv(1024)
print('Received:', data.decode('utf-8'))
if msg.lower().strip() == 'bye':
break
Handling multiple clients with threading
A common pattern in socket programming python is to spawn a thread per client. The server example above starts a daemon thread for each accepted connection. For high-scale servers, consider thread pools, asyncio, or event-driven frameworks rather than creating one thread per connection.
UDP example: python udp socket example send receive
UDP sockets are connectionless and use sendto/recvfrom. Use UDP when you need low-latency datagrams and can tolerate packet loss (video streaming, gaming).
# udp_server.py
import socket
HOST = '127.0.0.1'
PORT = 5002
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s:
s.bind((HOST, PORT))
print(f'UDP server listening on {HOST}:{PORT}')
while True:
data, addr = s.recvfrom(4096)
print('From', addr, data.decode())
s.sendto(b'ack', addr)
# udp_client.py
import socket
HOST = '127.0.0.1'
PORT = 5002
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s:
s.sendto(b'hello udp', (HOST, PORT))
data, _ = s.recvfrom(4096)
print('Response:', data.decode())
Blocking vs non-blocking sockets
By default sockets are blocking: calls like recv() and accept() wait until data or a connection is available. Non-blocking sockets return immediately and typically require a readiness mechanism such as select, poll, or using asyncio.
Example using select to avoid blocking on recv for multiple sockets:
import select
# assume sockets is a list of connected sockets
readable, _, _ = select.select(sockets, [], [], timeout_seconds)
for s in readable:
data = s.recv(1024)
if not data:
# remote closed
s.close()
else:
process(data)
TCP vs UDP — quick comparison
- TCP (SOCK_STREAM): connection-oriented, reliable, ordered delivery, good for file transfer, web services.
- UDP (SOCK_DGRAM): connectionless, lower overhead, no delivery guarantee, suitable for realtime streaming and simple query protocols.
Common errors and debugging tips
-
Address already in use: occurs when a previous process bound the same port. Fixes: set SO_REUSEADDR before bind or pick a different port. Example:
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) -
Connection refused: client attempted to connect but server isn’t listening. Ensure the server process is running and bound to the expected host/port. Use a short create_connection timeout to probe ports:
import socket try: socket.create_connection(('127.0.0.1', 5001), timeout=1) print('Port open') except (socket.timeout, ConnectionRefusedError): print('No server listening') -
Timeouts and partial reads: TCP is a stream — a single send may be received in several recv() calls. Design a protocol (length prefix, sentinel, or newline) to frame messages and consider socket timeouts to avoid indefinite blocking.
-
Handling exceptions: Always catch socket.error / OSError in production code and close sockets in finally blocks or use context managers to avoid resource leaks.
Security and deployment notes
- Never expose a development server directly to the public Internet. Use TLS for encryption (ssl.wrap_socket or the ssl module).
- Limit which interfaces you bind to: bind to '127.0.0.1' for local testing, or '0.0.0.0' to accept external connections.
- Choose ports above 1024 for unprivileged processes unless you have a reason to use a well-known port.
Frequently asked questions
How Netalith I create a Python socket server?
Import socket, create a socket.socket(socket.AF_INET, socket.SOCK_STREAM), set socket options if needed, bind((host, port)), listen(), and accept() connections. For production, wrap accept handling in threads, a thread pool, or an async loop.
How Netalith I create a Python socket client?
Create a socket, call connect((host, port)) for TCP, then use send/sendall and recv to exchange data. For UDP use sendto and recvfrom without connect.
How can I support multiple clients?
Common approaches: spawn a thread per client (suitable for moderate loads), use a thread pool or concurrent.futures, or adopt asyncio for large numbers of concurrent connections with lower memory overhead.
What port should I use?
Use ports above 1024 for development (e.g., 5000–6000). Ports below 1024 are privileged and usually reserved for system services.
Further learning
- Experiment with the socket module docs and try the examples above.
- Explore asyncio for non-blocking networking and higher-concurrency servers.
- Look into TLS (ssl module) when transmitting sensitive data.
With these samples and tips you can start building simple python client server programs, learn to handle multiple clients, and troubleshoot common socket errors while following safe deployment practices.