Python 2.6中urlib2超时问题(Python 2.6 urlib2 timeout is

2019-08-05 08:06发布

看来我不能让urllib2超时将被考虑在内。 我看过 - 我猜想 - 与该主题相关的所有帖子,似乎我没有做错什么。 我对么? 非常感谢你的帮助。

场景:

我需要一个脚本的其余继续之前检查互联网连接。 然后我写的函数(Net_Access),这将在下面提供。

  • 当我和我的连接局域网或WiFi接口,并通过检查现有的主机名执行此代码:一切都很好,因为没有错误或问题,因此没有超时。
  • 如果我拔掉我的LAN连接器或我对证一个不存在的主机名,超时值似乎被忽略。 这有什么错我的代码吗?

一些信息:

  • Ubuntu的10.04.4 LTS(运行成的VirtualBox v4.2.6 VM,主机OS是MAC OS X的狮子)
  • cat /proc/sys/kernel/osrelease: 2.6.32-42-generic
  • Python的2.6.5

我的代码:

#!/usr/bin/env python

import socket
import urllib2

myhost = 'http://www.google.com'
timeout = 3

socket.setdefaulttimeout(timeout)
req = urllib2.Request(myhost)

try:
    handle = urllib2.urlopen(req, timeout = timeout)
except urllib2.URLError as e:
    socket.setdefaulttimeout(None)
    print ('[--- Net_Access() --- No network access')
else:
    print ('[--- Net_Access() --- Internet Access OK')

1)工作,具有LAN连接器已插入

$ $ time ./Net_Access 
[--- Net_Access() --- Internet Access OK

real    0m0.223s
user    0m0.060s
sys 0m0.032s

2)超时不工作,与LAN连接器上拔出

$ time ./Net_Access 
[--- Net_Access() --- No network access

real    1m20.235s
user    0m0.048s
sys 0m0.060s

添加到原来的职位:测试结果(使用IP而不是FQDN)

正如@unutbu建议(见注释)的IP地址替换FQDN在myhost的解决了这个问题:超时取生效。

LAN连接器插入...
$时间./Net_Access [--- Net_Access()---互联网接入OK

real    0m0.289s
user    0m0.036s
sys 0m0.040s

LAN连接器上拔出...
$时间./Net_Access [--- Net_Access()---没有网络接入

real    0m3.082s
user    0m0.052s
sys 0m0.024s

这是好的,但它意味着超时只能用IP而不是FQDN使用。 奇怪的...

是不是有人发现了一种使用的urllib2超时没有进入,DNS预解析,并通过IP的功能,或者是你第一次使用套接字来测试连接,然后解雇的urllib2当你确信你能达到目标?

非常感谢。

Answer 1:

如果你的问题是DNS查找到永远(或只是太长时间)超时时,有没有网络连接,那么是的,这是一个已知的问题,并没有什么可以做内部urllib2自己解决这个问题。

所以,在所有的希望失去了什么? 好了,不一定。

首先,让我们来看看发生了什么事情。 最终, urlopen依靠getaddrinfo ,(加上其亲属等一起gethostbyname )是出了名的套接字API不能异步运行或中断(以及在某些平台上,它甚至不是线程安全的)的一个关键部分。 如果你想通过自己的源追踪, urllib2推迟到httplib用于创建连接,它要求create_connectionsocket ,要求socket_getaddrinfo_socket ,最终调用真正getaddrinfo功能。 这是影响写在世界上所有的语言每一个网络客户端或服务器的臭名昭著的问题,而且也没有很好的,简单的解决方案。

一种选择是使用已经解决了这个问题,不同的更高级别的图书馆。 我相信requests依靠urllib3最终有同样的问题,但pycurl依靠libcurl ,而如果建有c-ares ,的确名称查找异步的,因此可以一次出来。

或者,当然,你可以使用像twistedtornado或其他一些异步联网库。 但显然重写代码的使用twisted HTTP客户端而不是urllib2是不完全微不足道。

另一种选择是“修复” urllib2通过的Monkeypatching标准库。 如果你想这样做,有两个步骤。

首先,你必须提供一个timeoutable getaddrinfo 。 你可以通过绑定做到这一点c-ares ,或使用ctypes访问特定平台的API,如Linux的getaddrinfo_a ,甚至仰视的域名服务器,并与他们直接通信。 但真正简单的方法来做到这一点是使用线程。 如果你正在做大量的这些,你需要使用一个单独的线程或小线程池,但小规模的使用,只是分拆线程每个呼叫。 一个真正快速和肮脏的(阅读:坏)的实现是:

def getaddrinfo_async(*args):
    result = None
    t = threading.Thread(target=lambda: result=socket.getaddrinfo(*args))
    t.start()
    t.join(timeout)
    if t.isAlive():
        raise TimeoutError(blahblahblah)
    return result

接下来,你必须得到所有你关心使用这个库。 根据你想如何无处不在的(和危险)你的补丁是,你可以替换socket.getaddrinfo本身,或者只是socket.create_connection ,或者只是在代码httplib甚至urllib2

最后一个选择是在更高层次上解决此问题。 如果你的网络的东西是在后台线程上发生的事情,你可以扔在整个事情上一级超时,如果它不是花了timeout秒弄清楚无论是超时或没有,你知道它有。



Answer 2:

也许试试这个:

import urllib2

def get_header(url):
    req = urllib2.Request(url)
    req.get_method = lambda : 'HEAD'
    try:
        response = urllib2.urlopen(req)
    except urllib2.URLError:
        # urllib2.URLError: <urlopen error [Errno -2] Name or service not known>
        return False
    return True

url = 'http://www.kernel.org/pub/linux/kernel/v3.0/linux-3.7.1.tar.bz2'
print(get_header(url))

当我我拔掉网络适配器,该打印假几乎是立即,而正常情况下这个打印true。

我不知道为什么这个工程这么快比你原来的代码(甚至无需设置超时参数),但也许它会为你工作了。


我今天早上这并导致做了一个实验get_header没有立即返回。 我启动电脑与路由器关闭。 然后在路由器中开启。 然后,网络和无线通过Ubuntu的GUI功能。 这未能建立起有效的连接。 在这个阶段, get_header未能立即返回。

所以,这里是调用一个重量级的解决方案get_header在使用子multiprocessing.Pool 。 通过返回的对象pool.apply_async有一个get与超时参数的方法。 如果结果不从返回get_header由指定的持续时间内timeout ,子进程被终止。

因此, check_http应在约1秒返回结果,在任何情况下。

import multiprocessing as mp
import urllib2

def timeout_function(cmd, timeout = None, args = (), kwds = {}):
    pool = mp.Pool(processes = 1)
    result = pool.apply_async(cmd, args = args, kwds = kwds)
    try:
        retval = result.get(timeout = timeout)
    except mp.TimeoutError as err:
        pool.terminate()
        pool.join()
        raise
    else:
        return retval

def get_header(url):
    req = urllib2.Request(url)
    req.get_method = lambda : 'HEAD'
    try:
        response = urllib2.urlopen(req)
    except urllib2.URLError:
        return False
    return True

def check_http(url):
    try:
        response = timeout_function(
            get_header,
            args = (url, ),
            timeout = 1)
        return response
    except mp.TimeoutError:
        return False

print(check_http('http://www.google.com'))


文章来源: Python 2.6 urlib2 timeout issue