Reputation: 6780
Is there a way using socket
to attempt connection to several IP addresses and get the first one that succeeds?
A simplifed concept is this:
targets = ['1.2.3.4', '2.3.4.5', '3.4.5.6']
try:
s = socket_try_connect_one(targets, port=80, timeout=300)
# at this point, s contains the socket connection to one of the targets
except TimeoutError:
print('Cannot connect to any servers!')
sys.exit(1)
How should I implement socket_try_connect_one
so I get the expected behavior? Is there a built-in method (in the standard libs) to do this?
Edited to add:
I'm avoiding multithreading/multiprocessing because that might be overkill for this simple need.
Rather, I'm looking for something similar to .setblocking(0)
, but applied during connection establishment (setblocking applies to send()
and recv()
). And something similar to select.select
but applicable to connection establishment (instead of triggering on I/O).
Upvotes: 1
Views: 511
Reputation: 18908
So borrowing roughly from the standard library socket.create_connection
function, which does connection to multiple address/port pairs for each IP resolved for any given hostname, with the connection done using blocking sockets following the sequence of IP addresses as returned by DNS. Adapting that to accept multiple raw IP addresses and make use of non-blocking sockets can be done roughly with the following function:
import socket
import errno
def make_socket_from_addresses(addresses, port, *args, **kwargs):
sockets = {} # mapping of the actively checked sockets
dests = [] # the list of all destination pairs
for address in addresses:
dest = (address, port)
sock = socket.socket(*args, **kwargs)
sock.setblocking(0)
sockets[dest] = sock
dests.append(dest)
result = None
while sockets and result is None:
for dest in dests:
sock = sockets.get(dest)
if not sock:
continue
code = sock.connect_ex(dest)
if code == 0:
# success, track the result.
result = sock
sockets.pop(dest)
break
elif code not in (errno.EALREADY, errno.EINPROGRESS):
# assume any not already/in-progress are invalid/dead and so
# prune them.
sockets.pop(dest)
# should insert some very minute timeout here
for _, sock in sockets.items():
# close any remaining sockets
sock.close()
return result
To make use of this function, a list of addresses to check and a port must be supplied, along with the relevant arguments to the socket.socket
constructor. For example:
# various google.com addresses
addresses = ['216.58.203.110', '172.217.25.46', '172.217.6.78']
port = 80
sock = make_socket_from_addresses(
addresses, port, socket.AF_INET, socket.SOCK_STREAM)
print(sock)
Running that:
<socket.socket fd=3, family=AddressFamily.AF_INET, type=2049, proto=0, laddr=('10.0.0.1', 59454), raddr=('172.217.25.46', 80)>
Usage of select
may be beneficial, the example function provided serves only as an illustration on how the non-blocking loop might look like and how it should work.
Upvotes: 2