似乎我无法将urllib2
超时考虑在内。
我读过 - 我想 - 所有与这个主题相关的帖子似乎都没有做错。我对么?
非常感谢您的帮助。
情境:
在继续使用剩余的脚本之前,我需要检查Internet连接。 然后我写了一个函数(Net_Access),它在下面提供。
一些信息:
cat /proc/sys/kernel/osrelease: 2.6.32-42-generic
我的代码:
#!/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地址替换myhost中的FQDN可以解决问题:超时已生效。
插入LAN连接器...
$ time ./Net_Access
[--- Net_Access()--- Internet Access OK
real 0m0.289s
user 0m0.036s
sys 0m0.040s
LAN连接器已拔下...
$ time ./Net_Access
[--- Net_Access()---无网络访问
real 0m3.082s
user 0m0.052s
sys 0m0.024s
这很好,但这意味着超时只能用于IP,而不能用于FQDN。很奇怪......
有人找到了一种方法来使用urllib2超时而不进入DNS前解析并将IP传递给该函数,或者您是否首先使用套接字来测试连接,然后在您确定可以达到目标时触发urllib2?
非常感谢。
答案 0 :(得分:6)
如果你的问题是DNS查找需要永远(或者太长)在没有网络连接时超时,那么是的,这是一个已知的问题,你在urllib2
内部无能为力解决这个问题。
那么,所有希望都失去了吗?好吧,不一定。
首先,让我们来看看发生了什么。最终,urlopen
依赖于getaddrinfo
,它(与其gethostbyname
之类的亲戚)是众所周知的套接字API的一个关键部分,无法异步或中断运行(并且一些平台,它甚至不是线程安全的)。如果您想自己跟踪来源,urllib2
推迟httplib
创建连接,在socket
上调用create_connection
,socket_getaddrinfo
调用getaddrinfo
_socket
3}},最终调用真正的twisted
函数。这是一个臭名昭着的问题,影响到世界上每种语言编写的每个网络客户端或服务器,并没有好的,简单的解决方案。
一种选择是使用已经解决了这个问题的不同的更高级别的库。我相信requests
依赖urllib3
最终会遇到同样的问题,但pycurl
依赖libcurl
,如果使用c-ares
构建,则会异步进行名称查找,因此可以计时。
或者,当然,您可以使用tornado
或twisted
或其他异步网络库。但显然重写所有代码以使用urllib2
HTTP客户端而不是urllib2
并不是一件容易的事。
另一种选择是通过monkeypatching标准库来“修复”getaddrinfo
。如果你想这样做,有两个步骤。
首先,您必须提供可超时的c-ares
。您可以通过绑定ctypes
或使用getaddrinfo_a
访问特定于平台的API(如linux的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
中的代码
最后一个选择是将其修复到更高级别。如果您的网络内容正在后台线程中发生,您可以在整个事情上抛出更高级别的超时,如果花费超过{{1}}秒来判断它是否超时,您知道它已经
答案 1 :(得分: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))
当我拔下网络适配器时,几乎立即打印False,而在正常情况下,这会打印True。
我不确定为什么它与原始代码相比如此快速(即使不需要设置超时参数),但也许它也适用于你。
我今天早上做了一个实验,结果导致get_header
没有立即返回。我在关闭路由器的情况下启动计算机。然后打开路由器。然后通过Ubuntu GUI启用了网络和无线。这无法建立有效的连接。在此阶段,get_header
未能立即返回。
所以,这是一个较重的解决方案,使用get_header
在子进程中调用multiprocessing.Pool
。 pool.apply_async
返回的对象具有带{timeout}参数的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'))