
Python’s socket module is a standard library module for creating network applications. In this tutorial, you will learn the basics of Python socket programming, including how to create a simple client-server architecture, handle multiple clients using threading, and understand the differences between TCP and UDP sockets. You will learn how to establish connections, send and receive data, and build network applications that handle connections and errors correctly. The examples in this tutorial require Python 3.10 or later.
socket module is a thin, cross-platform wrapper over the OS socket APIs (Berkeley sockets / Winsock), so many Python socket methods correspond closely to underlying OS operations.bind(), listen(), and accept() on the server and connect() on the client. UDP sockets use recvfrom() and sendto() instead and require no connection setup.SO_REUSEADDR before bind() to prevent Address already in use errors during development.sendall() rather than send() to avoid partial sends on large payloads.asyncio or selectors for higher concurrency.recv() and send() in try/except blocks covering ConnectionResetError, BrokenPipeError, and socket.timeout, and always close sockets in a finally block.bytes | None union type hint syntax introduced in Python 3.10.)A socket is a file descriptor (or handle) that represents one endpoint of a network connection. Python’s socket module is a thin wrapper over the operating system’s socket APIs (Berkeley sockets / Winsock), exposing primitives such as bind(), listen(), accept(), connect(), send(), and recv().
Two OS behaviors cause the most common Python socket bugs: recv() returns however many bytes are available up to your requested limit, not a guaranteed amount, and ports stay reserved in TIME_WAIT for up to 60 seconds after the server closes. Partial-read bugs and ‘Address already in use’ errors trace back to one of these two.
When your code calls socket.socket(AF_INET, SOCK_STREAM), the OS allocates a socket file descriptor bound to the IPv4 TCP stack. bind() assigns a local address and port. listen() marks the socket as passive. accept() blocks until a TCP three-way handshake completes, then returns a new connected socket. sendall() copies bytes into the kernel send buffer. recv(n) returns up to n bytes from the receive buffer; if fewer bytes have arrived, you get fewer back. This is not an error; it is why message framing exists.
The two most common socket types are SOCK_STREAM for TCP and SOCK_DGRAM for UDP. Unix domain sockets use AF_UNIX with SOCK_STREAM for inter-process communication on the same host, without going through the network stack.
| Use Case | Reliability | Order Guaranteed | Connection Required | Python Socket Constants |
|---|---|---|---|---|
| TCP: web, file transfer, any protocol requiring guaranteed delivery | Reliable | Yes | Yes | socket.SOCK_STREAM |
| UDP: DNS, video streaming, gaming, telemetry | Unreliable | No | No | socket.SOCK_DGRAM |
| Unix domain: local IPC between processes on the same host | Reliable | Yes | Yes | socket.AF_UNIX with socket.SOCK_STREAM |
A TCP server binds to an address, waits for a client to connect, then exchanges messages in a loop. The two scripts below implement a working pair you can run on the same machine.
Save this file as socket_server.py. The server binds to 127.0.0.1 (the loopback interface), which limits connections to the local machine. To accept connections on all network interfaces instead, bind to '' or '0.0.0.0'.
import socket
def server_program():
host = '127.0.0.1' # loopback address for local testing
port = 5000 # initiate port no above 1024
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # IPv4 TCP socket
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) # allow port reuse immediately after server shutdown
server_socket.bind((host, port)) # bind host address and port together
server_socket.listen(5) # queue up to 5 connection requests
conn, address = server_socket.accept() # accept new connection
print("Connection from: " + str(address))
while True:
raw = conn.recv(1024) # read up to 1024 bytes; larger messages require multiple recv() calls
if not raw:
break
try:
data = raw.decode('utf-8')
except UnicodeDecodeError:
print(f"Received non-UTF-8 data from {address}, skipping")
continue
print("from connected user: " + str(data))
data = input(' -> ')
conn.sendall(data.encode()) # send data to the client
conn.close() # close the connection
server_socket.close() # close the listening socket
if __name__ == '__main__':
server_program()
Two causes of UnicodeDecodeError on raw.decode('utf-8'): genuinely non-UTF-8 data, or a valid multibyte character split across two recv() calls. The except block handles both by dropping the bytes silently. When message boundaries or non-ASCII input matter, use send_msg and recv_msg from the framing section below; those helpers accumulate a complete message before decoding.
Two socket objects are in play here. server_socket is the listening socket. It stays open for the lifetime of the server and does nothing except accept new connections. conn is the connected socket returned by accept(). It represents one client and is the object you read from and write to. Each client gets its own conn; server_socket is never passed to send() or recv() directly.
The input() call inside the loop makes this server interactive for demonstration purposes. In a real server, replace input() with your application logic; leaving it in blocks the server from receiving the next message until the operator types a reply.
Save this file as socket_client.py. The client connects to the same loopback address and port. Binding is not required on the client side; the OS assigns an ephemeral port number automatically during connect().
import socket
def client_program():
host = '127.0.0.1' # loopback address for local testing
port = 5000 # socket server port number
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # IPv4 TCP socket
client_socket.connect((host, port)) # connect to the server
message = input(" -> ") # take input
while message.lower().strip() != 'bye':
client_socket.sendall(message.encode()) # send message
raw = client_socket.recv(1024) # read up to 1024 bytes; larger messages require multiple recv() calls
if not raw:
break
try:
data = raw.decode('utf-8')
except UnicodeDecodeError:
print("Received non-UTF-8 data from server, skipping")
continue
print('Received from server: ' + data) # show in terminal
message = input(" -> ") # again take input
client_socket.close() # close the connection
if __name__ == '__main__':
client_program()
Run the server script first, then the client script in a separate terminal. Type a message in the client terminal and press Enter to send it to the server.
The server terminal shows:
Outputpython3 socket_server.py
Connection from: ('127.0.0.1', 57822)
from connected user: Hi
-> Hello
from connected user: How are you?
-> Good
from connected user: Awesome!
-> Ok then, bye!
The client terminal shows:
Outputpython3 socket_client.py
-> Hi
Received from server: Hello
-> How are you?
Received from server: Good
-> Awesome!
Received from server: Ok then, bye!
-> Bye
Notice that the server runs on port 5000, but the client also receives a port number assigned by the OS during connect(). In this case, the OS assigned port 57822 to the client side.
TCP is a byte stream, not a message stream. When you call sendall() with 500 bytes, the receiver’s first recv(1024) call may return 200 bytes, 500 bytes, or any amount in between depending on network buffering and timing. There is no guarantee that one sendall() on the sender maps to exactly one recv() on the receiver.
The standard fix is length-prefix framing. Before sending the payload, write a fixed-size header (4 bytes, big-endian unsigned integer) that encodes the payload length in bytes. The receiver reads exactly 4 bytes first, unpacks the length, then calls recv() in a loop until that many payload bytes have arrived. This approach works for any binary or text protocol and costs only four bytes of overhead per message.
The helper functions below implement this pattern. send_msg prepends the header and sends everything with one sendall(). recv_msg uses a bytearray buffer and a private _recv_exactly helper to accumulate bytes across however many recv() calls the kernel requires.
import socket
import struct
def send_msg(sock, data: bytes) -> None:
header = struct.pack('>I', len(data)) # 4-byte big-endian length prefix
sock.sendall(header + data)
def recv_msg(sock) -> bytes | None:
raw_len = _recv_exactly(sock, 4)
if raw_len is None:
return None # connection closed before header arrived
msg_len = struct.unpack('>I', raw_len)[0]
return _recv_exactly(sock, msg_len)
def _recv_exactly(sock, n: int) -> bytes | None:
buf = bytearray()
while len(buf) < n:
chunk = sock.recv(n - len(buf))
if not chunk:
return None # remote end closed the connection
buf.extend(chunk)
return bytes(buf)
Use send_msg and recv_msg in place of sendall and recv in any application where message boundaries matter. The echo server above uses raw recv and sendall for simplicity; for structured messages, replace those calls with send_msg and recv_msg.
A single-client server blocks on accept() and can serve only one connection at a time. To handle several clients concurrently, spawn a new thread for each accepted connection using Python’s threading module.
The complete server below defines handle_client() to manage one connection and server_program() to accept connections in a loop. Each accepted connection starts a daemon thread so threads do not prevent the interpreter from exiting when the main loop ends.
import socket
import threading
def handle_client(conn, addr):
print(f"Connection from: {addr}")
try:
while True:
data = conn.recv(1024)
if not data: # empty bytes means the client closed the connection
break
conn.sendall(data) # echo received data back to the client
except (ConnectionResetError, BrokenPipeError) as e:
print(f"Connection error with {addr}: {e}")
finally:
conn.close()
def server_program():
host = '127.0.0.1' # loopback address for local testing
port = 5000
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # IPv4 TCP socket
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) # allow port reuse immediately after server shutdown
server_socket.bind((host, port))
server_socket.listen(5) # queue up to 5 connection requests
print(f"Server listening on {host}:{port}")
try:
while True:
conn, addr = server_socket.accept()
thread = threading.Thread(target=handle_client, args=(conn, addr), daemon=True)
thread.start()
print(f"Active connections: {threading.active_count() - 1}")
except KeyboardInterrupt:
print("Server shutting down")
finally:
server_socket.close()
if __name__ == '__main__':
server_program()
threading.active_count() returns the number of currently alive threads, including the main thread. Subtracting 1 gives the number of active client threads at any moment.
Pressing Ctrl-C raises KeyboardInterrupt, which the outer try/except catches, allowing the server socket to close cleanly before the process exits.
When multiple threads read and write shared state (a connected client list, a message log, or a counter), protect that state with a threading.Lock(). Without a lock, two threads can interleave their reads and writes and corrupt the shared data.
import threading
client_list = []
lock = threading.Lock()
def handle_client(conn, addr):
with lock:
client_list.append(addr)
# handle the connection...
Use with lock: rather than calling lock.acquire() and lock.release() manually. The with statement guarantees the lock is released even if an exception is raised inside the block.
Each thread reserves up to 8 MB of virtual address space for its stack on Linux, though actual resident memory per idle thread is typically under 100 KB. Under high connection rates the scheduler overhead and context-switch cost matter more than raw memory. As concurrent connections approach hundreds, that cost grows. For servers that must handle hundreds or thousands of simultaneous connections, use selectors.DefaultSelector for I/O multiplexing or asyncio.start_server() for coroutine-based concurrency. Both are covered in the Python Socket Libraries section below.
Production socket code fails in predictable ways: ConnectionResetError when the remote process is killed, BrokenPipeError when you write to an already-closed socket, and socket.timeout when a blocking call stalls. The patterns below handle all three without crashing the server.
ConnectionResetError: raised when the remote host closes the connection unexpectedly, for example when the client process is killed mid-session.BrokenPipeError: raised on send() or sendall() when the receiver has already closed the socket.socket.timeout: raised when a blocking operation exceeds the timeout set by settimeout().Wrap the receive and send loop in a try/except block that handles all three exceptions:
while True:
try:
data = conn.recv(1024)
if not data:
break
conn.sendall(data)
except ConnectionResetError:
print("Client disconnected unexpectedly")
break
except BrokenPipeError:
print("Send failed: client has closed the connection")
break
except socket.timeout:
print("Connection timed out")
break
Checking for empty bytes (if not data: break) is separate from exception handling. recv() returning b'' is a normal condition indicating a clean shutdown by the remote end, not an error.
Place conn.close() inside a finally block to guarantee the socket is closed whether the loop exits normally or via an exception:
conn, addr = server_socket.accept()
try:
while True:
data = conn.recv(1024)
if not data:
break
conn.sendall(data)
finally:
conn.close()
Failing to close sockets leaks file descriptors. Linux defaults to 1024 open file descriptors per process. A server that handles many connections per hour will exhaust this limit unless each connection is properly closed.
Three options come up on nearly every server you write: SO_REUSEADDR prevents the address-already-in-use error on restart, settimeout() prevents hung connections from blocking forever, and setblocking(False) is the entry point for non-blocking I/O.
After a server shuts down, the OS holds the port in a TIME_WAIT state for up to about 60 seconds to allow stray packets from the old connection to expire. Attempting to bind to that port during TIME_WAIT raises:
OSError: [Errno 98] Address already in use
The fix is a single line placed after socket creation and before bind():
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
This tells the OS to allow immediate reuse of the address and port combination, which is standard practice for any server you restart during development or deployment.
socket.settimeout(seconds) applies to all blocking operations on that socket. If an operation does not complete within the specified time, socket.timeout is raised. Set the value to None to restore blocking mode with no timeout.
The following fragment shows a client-side timeout before connecting:
import socket
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # IPv4 TCP socket
client_socket.settimeout(5)
try:
client_socket.connect(('127.0.0.1', 5000))
except socket.timeout:
print("Connection timed out after 5 seconds")
Calling sock.setblocking(False) causes recv(), send(), accept(), and connect() to raise BlockingIOError immediately if they would block, rather than waiting. This is the foundation for I/O multiplexing: you register one or more non-blocking sockets with selectors.DefaultSelector and the selector tells you which sockets are ready to read or write without blocking any of them.
import selectors
import socket
sel = selectors.DefaultSelector()
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # IPv4 TCP socket
sock.setblocking(False)
sel.register(sock, selectors.EVENT_READ, data=None)
For a complete multiplexing server using this pattern, see the Python selectors documentation.
UDP sockets exchange messages without establishing a connection first. The following echo server and client communicate over UDP, using port 5001 to avoid conflicting with the TCP examples above.
UDP uses recvfrom() instead of recv(): it returns the data and the sender’s address as a tuple. sendto() takes the data and the destination address as arguments. There is no listen(), accept(), or connect() call in a UDP server.
import socket
def udp_server():
host = '127.0.0.1' # loopback address for local testing
port = 5001
server_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # IPv4 UDP socket
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) # allow port reuse immediately after server shutdown
server_socket.bind((host, port))
print(f"UDP server listening on {host}:{port}")
while True:
data, addr = server_socket.recvfrom(4096)
print(f"Received from {addr}: {data.decode()}")
server_socket.sendto(data, addr) # echo back to the sender
if __name__ == '__main__':
udp_server()
import socket
def udp_client():
host = '127.0.0.1' # loopback address for local testing
port = 5001
client_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # IPv4 UDP socket
client_socket.sendto(b"Hello, UDP server!", (host, port))
data, addr = client_socket.recvfrom(4096)
print(f"Response from server: {data.decode()}")
client_socket.close()
if __name__ == '__main__':
udp_client()
Run python3 udp_server.py first, then python3 udp_client.py in a separate terminal. Because UDP is connectionless, the client sends a datagram directly without a handshake and receives the echo in one recvfrom() call.
Raw socket calls give you full control at the cost of writing the scaffolding yourself. Before choosing that path, confirm that none of the following abstractions already fit your use case.
socketserver.TCPServer handles bind(), listen(), and accept() internally. You subclass BaseRequestHandler and implement a handle() method containing your per-connection logic:
import socketserver
class EchoHandler(socketserver.BaseRequestHandler):
def handle(self):
data = self.request.recv(1024)
self.request.sendall(data)
if __name__ == '__main__':
with socketserver.TCPServer(('127.0.0.1', 5002), EchoHandler) as server:
server.serve_forever()
socketserver is appropriate for simple request-response servers where you do not need fine-grained socket-option control. For multi-threaded handling, mix in socketserver.ThreadingMixIn.
asyncio.start_server() creates a non-blocking TCP server on the event loop. It scales to thousands of concurrent connections on a single thread using coroutines rather than OS threads. Async socket patterns are the standard high-concurrency approach in Python 3.11 and later:
import asyncio
async def handle_client(reader, writer):
data = await reader.read(1024)
writer.write(data)
await writer.drain()
writer.close()
await writer.wait_closed()
async def main():
server = await asyncio.start_server(
handle_client, '127.0.0.1', 5003
)
async with server:
await server.serve_forever()
if __name__ == '__main__':
asyncio.run(main())
Each connection is handled by a coroutine, so thousands of coroutines can run concurrently without the memory and scheduling overhead of one thread per connection. See the Python asyncio documentation for a full reference.
Use the socket module directly when you need to:
SO_RCVBUF, SO_SNDBUF, or TCP_NODELAY, that higher-level libraries do not expose.Concurrency model comparison:
| Model | Approximate Max Clients | Complexity | Blocking | Recommended Use Case |
|---|---|---|---|---|
| Thread-per-client | Hundreds | Low | Yes | Development and low-traffic services |
| select/selectors | Thousands | Medium | No | I/O-bound servers on a single thread |
| asyncio | Tens of thousands | Medium-high | No | High-concurrency Python 3.x services |
recv() has no default buffer size; the size argument is mandatory and controls the maximum number of bytes the call will read in one operation. Common values are 1024 and 4096 bytes. A buffer of 4096 bytes is a common choice for interactive or line-oriented protocols. For bulk transfers such as file downloads, use a larger value such as 65536 bytes and loop until all data arrives. Choosing a larger buffer reduces the number of system calls but increases per-connection memory use.
After a server process closes, the OS holds the port in TIME_WAIT state for up to about 60 seconds to expire any stale packets from the previous session. Any new bind() call during that period raises OSError: [Errno 98] Address already in use. Set SO_REUSEADDR immediately after socket creation and before bind() to prevent this:
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
This tells the OS to allow reuse of the address even if another socket recently used it.
send() may transmit fewer bytes than the length of the data you pass and returns the number of bytes actually sent. For large payloads, you must call it in a loop, advancing a buffer offset until all bytes are sent. sendall() performs that loop internally and raises an exception only if the transmission fails. For application-level code, use sendall() to avoid partial sends silently dropping part of your message.
Two primary approaches exist: spawn a threading.Thread for each accepted connection, or use selectors.DefaultSelector for non-blocking I/O multiplexing. Threading is simpler to implement and works well under light to moderate load. For high-concurrency scenarios where many clients connect simultaneously, use asyncio with asyncio.start_server() as shown in the libraries section above.
The backlog argument to listen() sets the maximum number of pending connections the OS queues before refusing new ones. It does not limit total connections, only the queue length. A value of 5 suits development; in production, use 128 or socket.SOMAXCONN.
Use UDP when low latency matters more than delivery guarantees, such as in DNS lookups, video streaming, online gaming, or telemetry. UDP does not guarantee that packets arrive, arrive in order, or arrive at all. If your application requires acknowledgment of received data or ordered delivery, use TCP.
Call socket.settimeout(seconds) on the socket before connect() or recv():
client_socket.settimeout(5)
If the operation does not complete within 5 seconds, socket.timeout is raised. Set the value to None to return to blocking mode with no timeout.
recv() returns an empty bytes object (b'') when the remote end closes the connection cleanly. This is not an exception; it is the normal signal for a graceful shutdown. Check the return value inside your receive loop and break when you see b'', then close the socket on the server side. If the client closes the connection abruptly (process kill, network failure), recv() raises ConnectionResetError instead.
This tutorial covered Python socket programming from the ground up: how the socket module maps to the OS Berkeley sockets API, how to build a single-client TCP server and client, how to handle multiple clients with threading, how to configure socket options such as SO_REUSEADDR and timeouts, how to use non-blocking I/O, how to build a UDP echo server, and how to choose between raw sockets and higher-level alternatives including socketserver and asyncio.
With this knowledge, you can build custom TCP and UDP protocols, diagnose common socket errors such as ConnectionResetError and BrokenPipeError, and select the right concurrency model for your workload. The working code examples in this tutorial run as-is on Python 3.x and serve as a foundation you can extend with authentication, TLS encryption via ssl.SSLContext.wrap_socket(), or custom message framing.
To continue, see the DigitalOcean tutorials on Python, What Is a Socket?, and Understanding Sockets.
Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.
Java and Python Developer for 20+ years, Open Source Enthusiast, Founder of https://www.askpython.com/, https://www.linuxfordevices.com/, and JournalDev.com (acquired by DigitalOcean). Passionate about writing technical articles and sharing knowledge with others. Love Java, Python, Unix and related technologies. Follow my X @PankajWebDev
I help Businesses scale with AI x SEO x (authentic) Content that revives traffic and keeps leads flowing | 3,000,000+ Average monthly readers on Medium | Sr Technical Writer(Team Lead) @ DigitalOcean | Ex-Cloud Consultant @ AMEX | Ex-Site Reliability Engineer(DevOps)@Nutanix
Building future-ready infrastructure with Linux, Cloud, and DevOps. Full Stack Developer & System Administrator. Technical Writer @ DigitalOcean | GitHub Contributor | Passionate about Docker, PostgreSQL, and Open Source | Exploring NLP & AI-TensorFlow | Nailed over 50+ deployments across production environments.
your server code is wrong you have to use different names to send the data data = conn.recv(1024).decode() if not data: # if data is not received break break print("from connected user: " + str(data)) message = input(’ -> ') #use different name at the place of data conn.send(message.encode()) # send data to the client
- Aditya raj
host = socket.gethostname() AttributeError: ‘module’ object has no attribute ‘gethostname’ im getting this error…
- adarsh
how do we chat with different persons should we give client program to them and ask to run. and i would host the server in my pc
- sanjay
File “C:/Users/hp/AppData/Local/Programs/Python/Python37/socket_client.py”, line 25, in client_program() File “C:/Users/hp/AppData/Local/Programs/Python/Python37/socket_client.py”, line 9, in client_program client_socket.connect((host, port)) # connect to the server ConnectionRefusedError: [WinError 10061] No connection could be made because the target machine actively refused it I am getting this error
- sdd
Get paid to write technical tutorials and select a tech-focused charity to receive a matching donation.
Full documentation for every DigitalOcean product.
The Wave has everything you need to know about building a business, from raising funding to marketing your product.
Stay up to date by signing up for DigitalOcean’s Infrastructure as a Newsletter.
New accounts only. By submitting your email you agree to our Privacy Policy
Scale up as you grow — whether you're running one virtual machine or ten thousand.
From GPU-powered inference and Kubernetes to managed databases and storage, get everything you need to build, scale, and deploy intelligent applications.