我似乎无法将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)工作,插入局域网连接器
$ $ time ./Net_Access
[--- Net_Access() --- Internet Access OK
real 0m0.223s
user 0m0.060s
sys 0m0.032s2)超时无法工作,局域网连接器拔出
$ time ./Net_Access
[--- Net_Access() --- No network access
real 1m20.235s
user 0m0.048s
sys 0m0.060s添加到原始帖子:测试结果(使用IP而不是FQDN)
正如@unutbu (参见注释)建议的那样,用IP地址替换myhost中的FQDN解决了这个问题:超时生效。
局域网连接器插上..。
$ time ./Net\_Access [--- Net\_Access() --- Internet Access OKreal 0m0.289s
user 0m0.036s
sys 0m0.040s局域网连接器拔出..。
$ time ./Net\_Access [--- Net\_Access() --- No network accessreal 0m3.082s
user 0m0.052s
sys 0m0.024s这很好,但这意味着超时只能用于IP,而不能用于FQDN。奇怪..。
是否有人找到了一种使用urllib2超时而不进入DNS前解析并将IP传递给函数的方法,或者您是否首先使用套接字测试连接,然后在确定您能够到达目标时触发urllib2?
非常感谢。
发布于 2013-01-03 19:36:45
如果您的问题是DNS查找花费了很长时间(或者太长),无法在没有网络连接的情况下超时,那么是的,这是一个已知的问题,在urllib2内部您无法解决这个问题。
那么,所有的希望都失去了吗?嗯,不一定。
首先,让我们看看发生了什么。最终,urlopen依赖于getaddrinfo,它(以及它的亲戚,比如gethostbyname)是socket API中出了名的不能异步或中断运行的关键部分(在某些平台上,它甚至都不是线程安全的)。如果您希望自己通过源进行跟踪,urllib2会向httplib提交创建连接的方法,后者调用socket上的create_connection,后者调用_socket上的socket_getaddrinfo,后者最终调用真正的getaddrinfo函数。这是一个臭名昭著的问题,影响到世界上每一种语言编写的网络客户端或服务器,没有一个好的、容易的解决方案。
一种选择是使用已经解决了这个问题的不同的高级库。我相信requests依赖于urllib3,这最终也存在同样的问题,但是pycurl依赖于libcurl,如果使用c-ares构建,它确实会异步地命名查找,因此可以超时。
当然,您也可以使用类似于twisted或tornado或其他异步网络库的东西。但是显然,重写所有代码来使用twisted HTTP客户端而不是urllib2并不简单。
另一种选择是通过对标准库进行猴子标记来“修复”urllib2。如果您想这样做,有两个步骤。
首先,您必须提供一个超时的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秒的时间来判断它是否超时,你知道它已经超时了。
发布于 2013-01-03 16:32:05
也许可以试试这个:
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启用网络和无线。这无法建立工作连接。在这个阶段,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'))https://stackoverflow.com/questions/14127115
复制相似问题