首页 > 解决方案 > 尝试连接多个 IP 地址并获取第一个成功的

问题描述

有没有办法socket尝试连接到多个 IP 地址并获得第一个成功的 IP 地址?

一个简化的概念是这样的:

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)

我应该如何实施socket_try_connect_one才能获得预期的行为?是否有内置方法(在标准库中)可以做到这一点?


编辑添加:

我正在避免使用多线程/多处理,因为对于这个简单的需求来说这可能是多余的。

相反,我正在寻找类似于的东西.setblocking(0),但在连接建立期间应用(setblocking 适用于send()and recv())。和类似的东西select.select但适用于连接建立的东西(而不是触发 I/O)。

标签: pythonsockets

解决方案


因此,大致从标准库socket.create_connection函数中借用,该函数为为任何给定主机名解析的每个 IP 连接到多个地址/端口对,并使用阻塞套接字按照 DNS 返回的 IP 地址序列完成连接。可以通过以下函数粗略地调整它以接受多个原始 IP 地址并使用非阻塞套接字:

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                                                               

要使用此函数,必须提供要检查的地址列表和端口,以及socket.socket构造函数的相关参数。例如:

# 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)

运行:

<socket.socket fd=3, family=AddressFamily.AF_INET, type=2049, proto=0, laddr=('10.0.0.1', 59454), raddr=('172.217.25.46', 80)>                            

的使用select可能是有益的,提供的示例函数仅用于说明非阻塞循环的外观以及它应该如何工作。


推荐阅读