How can I set a limit on the number of connections that a server socket can accept at once? I want to be able to set a max number of connections, and then once that limit is reached, any further attempts from clients to connect will result in a timeout. So far, I have tried something like this for the server:
sock = socket.socket()
sock.setblocking(0)
sock.bind(address)
sock.listen(0)
connections = []
while True:
readable, writable, exceptional = select.select([sock], [], [])
if readable and len(connections) < MAX_CONNECTIONS:
connection, client_address = s.accept()
connections.append(connection)
# Process connection asynchronously
and for the client:
try:
sock = socket.create_connection(self.address, timeout=TIMEOUT)
sock.settimeout(None)
print "Established connection."
except socket.error as err:
print >> sys.stderr, "Socket connection error: " + str(err)
sys.exit(1)
# If connection successful, do stuff
Because of the structure of the rest of the program, I have chosen to use a non-blocking socket on the server and I do not wish to change this. Right now, the clients are able to connect to the server, even after the limit is reached and the server stops accepting them. How do I solve this? Thanks.
I believe there might be a slight misunderstanding of select()
at play here. According to the manpage, select()
returns file descriptors that are "ready for some class of IO operation", where ready means "it is possible to perform a corresponding IO operation without blocking".
The corresponding IO operation on a listening socket is accept()
, which can only be performed without blocking if the OS already made the full TCP handshake for you, otherwise you might block waiting for the client's final ACK, for instance.
This means that as long as the listening socket is open, connections will be accepted by the OS, even if not being handled by the application. If you want to reject connections after a set number, you have basically two options:
The second option is more convoluted and requires use of the SO_REUSEADDR
option, which might not be the right thing in your case. It might also not work on all OSs, though it does seem to work reliably on Linux.
Here's a quick sketch of the second solution (since the first is pretty straightforward).
def get_listening_socket():
sock = socket.socket()
sock.setblocking(0)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind(('0.0.0.0', 5555))
sock.listen(0)
return sock
sock = get_listening_socket()
LIMIT = 1
conns = {sock}
while True:
readable, writable, exceptional = select.select(conns, [], [])
if sock in readable: # new connection on the listening socket
conn, caddr = sock.accept()
conns.add(conn)
if len(conns) > LIMIT: # ">" because "sock" is also in there
conns.remove(sock)
sock.close()
else: # reading from an established connection
for c in readable:
buf = c.recv(4096)
if not buf:
conns.remove(c)
sock = get_listening_socket()
conns.add(sock)
else:
print("received: %s" % buf)
You may, however, want to rethink why you'd want to do this in the first place. If it's only about saving some memory on the server, than you might be over-optimizing and should be looking into syn-cookies instead.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With