关闭urllib2连接

9

我正在使用urllib2从ftp和http服务器加载文件。

其中一些服务器仅支持每个IP一个连接。问题是,urllib2不会立即关闭连接。请看示例程序。

from urllib2 import urlopen
from time import sleep

url = 'ftp://user:pass@host/big_file.ext'

def load_file(url):
    f = urlopen(url)
    loaded = 0
    while True:
        data = f.read(1024)
        if data == '':
            break
        loaded += len(data)
    f.close()
    #sleep(1)
    print('loaded {0}'.format(loaded))

load_file(url)
load_file(url)

代码从一个只支持1个连接的ftp服务器加载两个文件(这里的两个文件是相同的)。这将打印以下日志:
loaded 463675266
Traceback (most recent call last):
  File "conection_test.py", line 20, in <module>
    load_file(url)
  File "conection_test.py", line 7, in load_file
    f = urlopen(url)
  File "/usr/lib/python2.6/urllib2.py", line 126, in urlopen
    return _opener.open(url, data, timeout)
  File "/usr/lib/python2.6/urllib2.py", line 391, in open
    response = self._open(req, data)
  File "/usr/lib/python2.6/urllib2.py", line 409, in _open
    '_open', req)
  File "/usr/lib/python2.6/urllib2.py", line 369, in _call_chain
    result = func(*args)
  File "/usr/lib/python2.6/urllib2.py", line 1331, in ftp_open
    fw = self.connect_ftp(user, passwd, host, port, dirs, req.timeout)
  File "/usr/lib/python2.6/urllib2.py", line 1352, in connect_ftp
    fw = ftpwrapper(user, passwd, host, port, dirs, timeout)
  File "/usr/lib/python2.6/urllib.py", line 854, in __init__
    self.init()
  File "/usr/lib/python2.6/urllib.py", line 860, in init
    self.ftp.connect(self.host, self.port, self.timeout)
  File "/usr/lib/python2.6/ftplib.py", line 134, in connect
    self.welcome = self.getresp()
  File "/usr/lib/python2.6/ftplib.py", line 216, in getresp
    raise error_temp, resp
urllib2.URLError: <urlopen error ftp error: 421 There are too many connections from your internet address.>

因为第一个连接没有关闭,所以第一个文件被加载了,而第二个文件加载失败。

但是当我在f.close()后使用sleep(1)时,错误就不会发生:

loaded 463675266
loaded 463675266

有没有办法强制关闭连接,以便第二次下载不会失败?

1
可能是should I call close() after urllib.urlopen()?的重复问题。 - moinudin
@marcog 我认为这不是同一个问题 :-) 另一个线程的用户问是否应该关闭“连接”。我知道我应该关闭连接(我会关闭它 :-)),但如上所述,在使用close()时连接不会立即关闭...或者使用contextlib.closing(调用close)。 - Biggie
好的,抱歉,是我不对。如果可以的话,我会撤回投票。 - moinudin
4个回答

4

问题的根本原因是文件描述符泄漏。我们发现使用jython时,问题比使用cpython更加明显。

一位同事提出了这个解决方案:

 
fdurl = urllib2.urlopen(req,timeout=self.timeout) realsock = fdurl.fp._sock.fp._sock** # 我们稍后要关闭“真实”的socket req = urllib2.Request(url, header) try: fdurl = urllib2.urlopen(req,timeout=self.timeout) except urllib2.URLError,e: print "urlopen exception", e realsock.close() fdurl.close()

这个修复方案虽然不太好看,但起到了作用,不再出现"too many open connections"的错误了。


1
urlopen 被调用两次有什么好的理由吗?为什么在分配之前要使用 req - drevicko

3

Biggie: 我认为这是因为连接没有被shutdown()。

请注意,close()释放与连接相关联的资源,但不一定立即关闭连接。如果您想及时关闭连接,请在close()之前调用shutdown()。

您可以在f.close()之前尝试类似以下的操作:

import socket
f.fp._sock.fp._sock.shutdown(socket.SHUT_RDWR)

(是的,如果这样做可以解决问题,但这并不是正确的方法。)

3

0

4
您可以在这里看到,contextlib.closing只是使用了close()。这也是我在上面的代码中手动执行的操作。因此,问题仍然存在,即第二次下载将失败,因为第一次连接没有立即使用close()关闭。 - Biggie
嗯,我明白了,抱歉我的回答。如果我成功解决问题,我会及时通知您。 - Sandro Munda

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接