1

I'm trying to limit a Python server to only accept a connection from one client, but I'm noticing that the Python server always accepts one too many connections. Here is the server code:

import socket 
import time

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind((socket.gethostname(), 1234))
s.listen(0)
clientsocket, address = s.accept()
print(f"Connection from {address} has been established!")
while True:
    time.sleep(1000)
    clientsocket.send(bytes("Welcome to server 1!", "utf-8"))

I would expect this to only allow one client connection at a time, since there is not allowed to be any in the queue (s.listen(0)). However, I'm finding that this code allows me to connect two clients before getting an error. Here are my clients connecting:

>>> import socket
>>> socket.socket(socket.AF_INET, socket.SOCK_STREAM).connect((socket.gethostname(), 1234))
>>> socket.socket(socket.AF_INET, socket.SOCK_STREAM).connect((socket.gethostname(), 1234))
>>> socket.socket(socket.AF_INET, socket.SOCK_STREAM).connect((socket.gethostname(), 1234))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ConnectionRefusedError: [WinError 10061] No connection could be made because the target machine actively refused it

The first client is accepted, which is expected. The second client successfully connects, which I would not expect since the server already has a connection and 0 connections are allowed in the queue. We do not fail until the third client tries to connect and is denied.

If we up the listen() argument to 1, we are allowed 3 connections (1 current, 2 in the queue). If we up it to 2, we are allowed 4 connections (1 current, 3 in the queue). This pattern continues.

How can I specify that my server should allow allow 1 connection and not hold any in the queue?

martineau
  • 119,623
  • 25
  • 170
  • 301
Alex Cavanaugh
  • 415
  • 1
  • 5
  • 16
  • Cannot reproduce. Using your code I get a *broken pipe* error on the server side when the client connects. – Woodford Jul 01 '21 at 21:35
  • Hey sorry I just updated the server side code now. Should work. I was just trying to simplify the code but realized that the way I had it set up, it would immediately try to send data back to a socket that no longer exists since we aren't storing it. – Alex Cavanaugh Jul 01 '21 at 21:41

1 Answers1

2

Regardless of the documentation:

socket.listen([backlog])
Enable a server to accept connections. If backlog is specified, it must be at least 0 (if it is lower, it is set to 0); it specifies the number of unaccepted connections that the system will allow before refusing new connections. If not specified, a default reasonable value is chosen.

Testing indicates the minimum is 1 for unaccepted connections, and not backlog + 1. This may be OS-dependent. I'm using Windows 10. Note I'm not accepting any connections below, just connecting until they are refused.

In [1]: from socket import *
   ...: for backlog in range(-1,6):
   ...:     s=socket()
   ...:     s.bind(('',5000))
   ...:     s.listen(backlog)
   ...:     c=[]
   ...:     try:
   ...:         while True:
   ...:             a=socket()
   ...:             a.connect(('localhost',5000))
   ...:             c.append(a)
   ...:     except ConnectionRefusedError:
   ...:         print(f'{backlog=} successful_connections={len(c)}')
   ...:
backlog=-1 successful_connections=1
backlog=0 successful_connections=1
backlog=1 successful_connections=1
backlog=2 successful_connections=2
backlog=3 successful_connections=3
backlog=4 successful_connections=4
backlog=5 successful_connections=5

Which makes sense. You need at least one unaccepted connection queued or there would be nothing to .accept().

If you want only one accepted connection, call .accept() serially and close it before accepting another. The client can use timeouts if it connects, sends a query and doesn't get a response in a set amount of time.

Mark Tolonen
  • 166,664
  • 26
  • 169
  • 251