看来我不能让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当你确信你能达到目标?
非常感谢。
如果你的问题是DNS查找到永远(或只是太长时间)超时时,有没有网络连接,那么是的,这是一个已知的问题,并没有什么可以做内部urllib2
自己解决这个问题。
所以,在所有的希望失去了什么? 好了,不一定。
首先,让我们来看看发生了什么事情。 最终, urlopen
依靠getaddrinfo
,(加上其亲属等一起gethostbyname
)是出了名的套接字API不能异步运行或中断(以及在某些平台上,它甚至不是线程安全的)的一个关键部分。 如果你想通过自己的源追踪, urllib2
推迟到httplib
用于创建连接,它要求create_connection
的socket
,要求socket_getaddrinfo
上_socket
,最终调用真正getaddrinfo
功能。 这是影响写在世界上所有的语言每一个网络客户端或服务器的臭名昭著的问题,而且也没有很好的,简单的解决方案。
一种选择是使用已经解决了这个问题,不同的更高级别的图书馆。 我相信requests
依靠urllib3
最终有同样的问题,但pycurl
依靠libcurl
,而如果建有c-ares
,的确名称查找异步的,因此可以一次出来。
或者,当然,你可以使用像twisted
或tornado
或其他一些异步联网库。 但显然重写代码的使用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
秒弄清楚无论是超时或没有,你知道它有。
也许试试这个:
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'))