SO_REUSEADDR and SO_REUSEPORT let multiple processes bind to the same IP address and port combination, but they do it in fundamentally different ways, with SO_REUSEADDR being a much older and more permissive option.
Let’s see SO_REUSEADDR in action. Imagine you have a server application that listens on port 8080.
import socket
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind(('localhost', 8080))
sock.listen(5)
print("Server listening on port 8080 with SO_REUSEADDR")
# Try to start another server *without* SO_REUSEADDR
try:
sock2 = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock2.bind(('localhost', 8080)) # This will likely fail without SO_REUSEADDR
except OSError as e:
print(f"Second server bind failed as expected: {e}")
# Now try to start another server *with* SO_REUSEADDR
try:
sock3 = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock3.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock3.bind(('localhost', 8080))
print("Third server also bound to port 8080 with SO_REUSEADDR")
sock3.close()
except OSError as e:
print(f"Third server bind failed unexpectedly: {e}")
# Keep the first socket open to demonstrate
input("Press Enter to close the first server...")
sock.close()
When you run this, you’ll see that the first server binds successfully. The second attempt to bind to the same port without SO_REUSEADDR will fail with an OSError: [Errno 98] Address already in use. However, the third attempt, also using SO_REUSEADDR, succeeds. This is the core behavior: SO_REUSEADDR allows multiple sockets to bind to the same address and port.
The problem SO_REUSEADDR solves is the "linger" issue. When a TCP connection is closed, the socket on the server side enters a TIME_WAIT state. This state is crucial for ensuring that any delayed packets from the old connection are not misinterpreted as belonging to a new connection. However, if your server restarts quickly after shutting down, the port might still be in TIME_WAIT, preventing a new server instance from binding to it. SO_REUSEADDR tells the kernel, "It’s okay to reuse this address and port, even if it’s in TIME_WAIT." This is invaluable for development and for applications that need to restart rapidly.
Internally, SO_REUSEADDR works by allowing a new socket to bind to an address/port that is already bound, provided that the existing socket is in the TIME_WAIT state or the new socket is not going to conflict with an actively listening socket. The "conflict" part is key: if another socket is actively listen()ing on that exact address and port, SO_REUSEADDR on a new socket will not allow it to bind. It’s primarily about overcoming TIME_WAIT.
Now, SO_REUSEPORT is a more modern and granular option, introduced to handle scenarios where you do want multiple processes to actively listen on the same port simultaneously. This is common in high-performance network applications that want to leverage multiple CPU cores by having each core’s process listen on the same port.
Let’s see SO_REUSEPORT in action.
import socket
import os
# First process
sock1 = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# SO_REUSEADDR is often set by default or is good practice
sock1.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock1.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
sock1.bind(('localhost', 8080))
sock1.listen(5)
print(f"Process {os.getpid()} listening on port 8080 with SO_REUSEPORT")
# To simulate another process, we'd fork or run a separate script.
# For demonstration, we'll just show the option:
#
# # In a *separate* process/script:
# sock2 = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# sock2.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# sock2.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
# sock2.bind(('localhost', 8080))
# sock2.listen(5)
# print(f"Another process listening on port 8080 with SO_REUSEPORT")
# Keep the first socket open
input("Press Enter to close the first server...")
sock1.close()
When you run multiple instances of a script like this (each with SO_REUSEPORT enabled), they will all successfully bind and listen on the same port. The kernel then distributes incoming connections among these listening sockets. This is true parallel listening.
The key difference and the most surprising thing about SO_REUSEADDR is that it doesn’t actually allow two sockets to actively listen on the same port at the same time if the first one isn’t in TIME_WAIT. It’s primarily for dealing with the TIME_WAIT state after a shutdown. SO_REUSEPORT is the option that enables true concurrent listening by multiple sockets. It requires the specific SO_REUSEPORT flag to be set on all sockets that wish to share the port.
Choosing between them depends on your goal:
SO_REUSEADDR: Use this if you need your server to restart quickly after a crash or shutdown without waiting for the kernel to clear theTIME_WAITstate. It’s a single listener that can bind even if the address is inTIME_WAIT.SO_REUSEPORT: Use this if you want multiple processes to actively listen on the same port concurrently, distributing the load. This is for true parallelism.
When you set SO_REUSEPORT, the kernel takes responsibility for load-balancing incoming connections across all sockets that have bound to that port with SO_REUSEPORT enabled. This distribution is typically done using a hash-based algorithm to ensure that connections from the same client IP and port are consistently sent to the same listening socket.
The next thing you’ll likely encounter is the finer-grained control offered by SO_BINDTODEVICE for binding sockets to specific network interfaces.