When looking at python UDP sockets option 'SO_REUSEPORT', I found something strange. According to the article, How do SO_REUSEADDR and SO_REUSEPORT differ?, 'Additionally the kernel performs some "special magic" for SO_REUSEPORT sockets that isn't found in other operating systems: For UDP sockets, it tries to distribute datagrams evenly, for TCP listening sockets, it tries to distribute incoming connect requests (those accepted by calling accept()) evenly across all the sockets that share the same address and port combination. Thus an application can easily open the same port in multiple child processes and then use SO_REUSEPORT to get a very inexpensive load balancing.' But when I did some experiments, 'SO_REUSEPORT' does not do load balancing: only One socket receives packets. The other socket receives nothing!
On the computer, Python version is 3.8.12, OS is CentOS7 3.10.
I have two scripts, udp echo server and echo client. They are very simple: the client sends a message to the server; the server listens and prints incoming messages.
echo_client.py
import socket
bytesToSend = "Hello UDP Server".encode()
serverAddressPort = ("127.0.0.1", 20001)
localPort = 21111
bufferSize = 1024
cnt = 0
try:
# Create a UDP socket at client side
UDPClientSocket = socket.socket(family=socket.AF_INET, type=socket.SOCK_DGRAM)
# Must set SO_REUSEADDR/SO_REUSEPORT, otherwise incur exception 'Address already in use'
# Must set before the first bind
UDPClientSocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
UDPClientSocket.bind(("", localPort))
UDPClientSocket.sendto(f'Hello server: {cnt}'.encode(), serverAddressPort)
cnt += 1
except Exception as e:
print(f'An exception occurred {e}')
print('close client, bye !')
UDPClientSocket.close()
echo_server.py
import socket
localIP = "127.0.0.1"
localPort = 20001
bufferSize = 1024
msgFromServer = "Hello UDP Client"
bytesToSend = str.encode(msgFromServer)
cnt = 0
# Create a datagram socket
UDPServerSocket = socket.socket(family=socket.AF_INET, type=socket.SOCK_DGRAM)
UDPServerSocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
# Bind to address and ip
UDPServerSocket.bind((localIP, localPort))
print("UDP server up and listening")
# Listen for incoming datagrams
while True:
print(f'\nwait for client msg ... {cnt}')
cnt += 1
try:
bytesAddressPair = UDPServerSocket.recvfrom(bufferSize)
message = bytesAddressPair[0]
address = bytesAddressPair[1]
except Exception as e:
print(f'An exception occurred in recvfrom {e}')
continue
print(f'Message from Client:{message.decode()}')
print(f'Client IP Address:{address}')
try:
# Sending a reply to client
UDPServerSocket.sendto(bytesToSend, address)
except Exception as e:
print(f'An exception occurred in sendto {e}')
Two echo servers are started in different terminals (python echo_server.py). Then from third terminal send messages a few times (python echo_client.py). However, only the first terminal receives the messages! I would expect both servers receive messages, as 'SO_REUSEPORT' supposes to do load balancing.
first server display on terminal 1:
$ python echo/echo_server.py UDP server up and listening
wait for client msg ... 0 Message from Client:Hello server: 0 Client IP Address:('127.0.0.1', 21111)
wait for client msg ... 1 Message from Client:Hello server: 0 Client IP Address:('127.0.0.1', 21111)
wait for client msg ... 2 Message from Client:Hello server: 0 Client IP Address:('127.0.0.1', 21111)
wait for client msg ... 3 Message from Client:Hello server: 0 Client IP Address:('127.0.0.1', 21111)
wait for client msg ... 4 Message from Client:Hello server: 0 Client IP Address:('127.0.0.1', 21111)
wait for client msg ... 5 Message from Client:Hello server: 0 Client IP Address:('127.0.0.1', 21111)
wait for client msg ... 6 Message from Client:Hello server: 0 Client IP Address:('127.0.0.1', 21111)
wait for client msg ... 7 Message from Client:Hello server: 0 Client IP Address:('127.0.0.1', 21111)
wait for client msg ... 8
second server display on terminal 2:
$ python echo/echo_server.py UDP server up and listening
wait for client msg ... 0
Any idea what could go wrong? Thanks.