【问题标题】:How can I get non-blocking socket connect()'s?如何获得非阻塞套接字连接()?
【发布时间】:2010-11-15 09:45:09
【问题描述】:

我有一个非常简单的问题。我需要同时与许多主机通信,但我并不需要任何同步,因为每个请求都非常自给自足。

因此,我选择使用异步套接字,而不是垃圾邮件线程。 现在我确实有一个小问题:

异步的东西就像一个魅力,但是当我连接到 100 台主机时,我得到 100 次超时(超时 = 10 秒)然后我等待 1000 秒,只是为了找出我所有的连接都失败了。

有没有什么办法也能获得非阻塞套接字连接? 我的套接字已设置为非阻塞,但对 connect() 的调用仍处于阻塞状态。

减少超时不是一个可接受的解决方案。

我在 Python 中执行此操作,但我猜在这种情况下编程语言并不重要。

我真的需要使用线程吗?

【问题讨论】:

    标签: python sockets asynchronous nonblocking


    【解决方案1】:

    使用select 模块。这允许您在多个非阻塞套接字上等待 I/O 完成。这是 some more information 选择。从链接到的页面:

    在 C 中,编码select 相当复杂。 在 Python 中,这是小菜一碟,但是 它与C版本足够接近 如果你理解 select in Python,你不会有什么麻烦 在 C 中使用它。

    ready_to_read, ready_to_write, in_error = select.select(
                      potential_readers, 
                      potential_writers, 
                      potential_errs, 
                      timeout)
    

    你通过select三个列表:第一个 包含您可能使用的所有套接字 想尝试阅读;第二个 您可能想尝试的套接字 写到,最后一个(通常 留空)那些你想要的 检查错误。你应该注意 一个套接字可以进入多个 列表。 select 调用被阻塞,但是 你可以给它一个超时。这是 通常是一件明智的事情- 给它一个很好的长时间超时(比如 分钟)除非你有充分的理由 否则,请不要这样做。

    作为回报,您将获得三个列表。 他们的插座是 实际上可读,可写并且在 错误。这些列表中的每一个都是一个子集 (可能为空)对应的 列出你传入的列表。如果你放一个 在多个输入列表中的套接字,它 只会(最多)在一个输出中 列表。

    如果一个套接字在输出可读 清单,你可以 几乎可以确定,因为我们曾经从事这项业务 该套接字上的recv 将返回 某物。可写的想法相同 列表。你可以send 某物。也许不是你想要的, 但有总比没有好。 (实际上,任何合理健康的 套接字将作为可写返回 - 它 仅表示出站网络缓冲区 有空位。)

    如果你有一个“服务器”套接字,把它 在 potential_readers 列表中。如果它 出现在可读列表中,您的 接受(几乎可以肯定)会起作用。 如果您创建了一个新的套接字 连接到别人,把它放在 潜在作家名单。如果出现 在可写列表中,您有一个 它已经连接的可能性很大。

    【讨论】:

    • 他特别说他在 connect() 上被阻止了。 Select 只告诉你什么可读或可写。
    • 见我回答的最后一段。使用select 多路复用,您无需等待 1000 秒即可进行有用的工作。通过短暂的超时,如果所有端点都未连接,您仍然可以做有用的工作,只需短暂等待。 Twisted 当然是另一种选择,但正如您自己所说的“进入它有点沉重”。
    • 啊,我看到了问题...他设置了超时,这意味着套接字必须阻塞。
    • 我没有明确设置任何东西,我使用的是 python 的 asyncore 模块,它似乎或多或少是 select() 的包装器。我创建了另一个简短的示例测试脚本,只是创建了一个套接字并将其设置为非阻塞,但它仍然会在连接时阻塞,而不是在读取时阻塞。
    • @Tom - 看,你没有提到你正在使用带有超时选项的 asyncore,所以我的一个合乎逻辑的猜测是你使用的是 socket.settimeout(),它设置了阻塞。你的平台和 python 版本是什么 - 使用 setblocking(0) 连接不会在我的系统上阻塞
    【解决方案2】:

    很遗憾,没有显示该错误的示例代码,因此很难看出该块来自何处。

    他做了这样的事情:

    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.setblocking(0)
    s.connect(("www.nonexistingname.org", 80))
    

    socket模块内部使用getaddrinfo,这是一个阻塞操作,尤其是主机名不存在的时候。符合标准的 dns 客户端将等待一段时间,以查看该名称是否真的不存在,或者是否仅涉及一些慢速 dns 服务器。

    解决方案是仅连接到 IP 地址或使用允许非阻塞请求的 dns 客户端,例如 pydns

    【讨论】:

    • 这几乎切入了问题的核心。似乎我遇到了 DNS 问题。我的应用程序的行为(至少在初始阶段)与端口扫描器非常相似:我依赖于非常快速的结果,无论连接是否有效。在不存在的主机名块上使用 getaddrinfo 也用于非阻塞套接字,这很糟糕(对我来说)。我可能还连接到很多不存在的主机,我无法在每个不存在的主机上等待 10 秒。
    • 我的目的非常不同,但通过更改顺序得到了解决。即先连接然后设置阻塞。
    • @Ben 也解决了我的问题!谢谢!
    • 有趣的是,我遇到了完全相同的问题,但是当我将 www.nonexistingname.org 替换为 127.0.0.1 时,问题仍然存在
    【解决方案3】:

    您还需要并行化连接,因为当您设置超时时套接字会阻塞。或者,您不能设置超时,而使用选择模块。

    您可以使用asyncore 模块中的调度程序类来执行此操作。看看基本的http client example。该类的多个实例在连接时不会相互阻塞。您可以像使用线程一样轻松地做到这一点,我认为跟踪套接字超时更容易,但由于您已经在使用异步方法,您最好保持在同一轨道上。

    例如,以下适用于我所有的 linux 系统

    import asyncore, socket
    
    class client(asyncore.dispatcher):
        def __init__(self, host):
            self.host = host
            asyncore.dispatcher.__init__(self)
            self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
            self.connect((host, 22))
    
        def handle_connect(self):
            print 'Connected to', self.host
    
        def handle_close(self):
            self.close()
    
        def handle_write(self):
            self.send('')
    
        def handle_read(self):
            print ' ', self.recv(1024)
    
    clients = []
    for i in range(50, 100):
        clients.append(client('cluster%d' % i))
    
    asyncore.loop()
    

    在 cluster50 - cluster100 中,有许多机器没有响应或不存在。这会立即开始打印:

    Connected to cluster50
      SSH-2.0-OpenSSH_4.3
    
    Connected to cluster51
      SSH-2.0-OpenSSH_4.3
    
    Connected to cluster52
      SSH-2.0-OpenSSH_4.3
    
    Connected to cluster60
      SSH-2.0-OpenSSH_4.3
    
    Connected to cluster61
      SSH-2.0-OpenSSH_4.3
    
    ...
    

    然而,这并没有考虑 getaddrinfo,它必须阻止。如果您在解决 dns 查询时遇到问题,一切都必须等待。您可能需要自己单独收集 dns 查询,并在异步循环中使用 ip 地址

    如果您想要比 asyncore 更大的工具包,请查看 Twisted Matrix。入门有点繁琐,但它是你可以为 python 获得的最好的网络编程工具包。

    【讨论】:

    • 好吧,我要在这里道歉。我直接从 Python 文档中获取了代码,所以它不是我的代码,我认为它是正确的。它没有用。我经常遇到人们给我的建议,他们甚至没有验证自己。我永远不会猜到我的操作系统会是问题,而不是代码,所以我认为你只是另一个认为他很聪明的人,甚至没有检查它是否有效就复制粘贴我的文档代码。再次为此感到抱歉。今天扔了3个完整版本,浪费了6个小时,才发现问题出在MacOS上。
    • 顺便说一句,我和一个朋友在他的 linux 机器上再次测试了这个,甚至 getAddrInfo 似乎也没有在那里阻塞。我们收到一个错误:[Errno 115] Operation now in progress。所以理论上,即使是非响应主机的异步也可以在 linux 中工作。
    • @Tom - np,我同意这里有大量不知情的答案,尤其是在非 Windows 领域。更糟糕的是,不知情的团队最终会互相投票,很难得到正确的答案。
    • “我们得到一个错误:[Errno 115] 操作正在进行中。所以理论上即使是非响应主机的异步也可以在 linux 中工作” - 我很确定它可以,我只是不能让我的 dns 损坏到足以挂起以验证它。
    • @JimB,请给出asyncio模块的答案?
    【解决方案4】:

    使用twisted

    它是一个用 Python 编写的异步网络引擎,支持多种协议,您可以添加自己的协议。它可用于开发客户端和服务器。它不会在连接时阻塞。

    【讨论】:

    • Twisted 带来了这样的快乐。我每天都在使用它,并试图说服那些在并发方面苦苦挣扎的人,它会让他们的生活变得更加轻松。当然,我的同事至少能看到其中的不同。
    • 我以前用过twisted,挺不错的,但是文档也是twisted。也很难将我的资源整合到其中。您确定它不会阻止连接吗?那时可能会尝试这样做。
    【解决方案5】:

    当将socket.connect 与非阻塞套接字一起使用时,最初预计会得到BlockingIOError。有关原因的说明,请参阅TCP Connect error 115 Operation in Progress What is the Cause?

    解决方案是捕获并忽略异常或使用socket.connect_ex 而不是socket.connect,因为该方法不会引发异常。特别注意 Python 文档中描述的最后一句话:

    socket.connect_ex(address)

    connect(address) 类似,但返回一个错误指示符,而不是针对 C 级 connect() 调用返回的错误引发异常(其他问题,例如“找不到主机”,仍然会引发异常)。如果操作成功,则错误指示符为 0,否则为 errno 变量的值。 这对于支持异步连接等很有用。

    来源:https://docs.python.org/3/library/socket.html#socket.socket.connect_ex

    如果你想继续使用socket.connect,你可以捕捉并忽略负责的EINPROGRESS错误:

    >>> import socket
    >>> 
    >>> # bad
    >>> s = socket.socket()
    >>> s.setblocking(False)
    >>> s.connect(("127.0.0.1", 8080))
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    BlockingIOError: [Errno 115] Operation now in progress
    >>> 
    >>> # good
    >>> s = socket.socket()
    >>> s.setblocking(False)
    >>> try:
    ...     s.connect(("127.0.0.1", 8080))
    ... except OSError as exc:
    ...     if exc.errno != 115:  # EINPROGRESS
    ...         raise
    ... 
    >>> 
    

    【讨论】:

      【解决方案6】:

      你看过asyncore 模块吗?可能正是您所需要的。

      【讨论】:

      • 我正在使用它,但它仍然在连接时阻塞
      猜你喜欢
      • 2013-07-20
      • 2017-04-18
      • 1970-01-01
      • 2016-07-05
      • 1970-01-01
      • 2010-10-31
      • 2013-10-15
      • 1970-01-01
      相关资源
      最近更新 更多