urlopen ошибка 10045, «адрес уже используется» при загрузке в Python 2.5 для Windows - PullRequest
5 голосов
/ 03 октября 2009

Я пишу код, который будет работать в Linux, OS X и Windows. Он загружает список приблизительно из 55 000 файлов с сервера, затем просматривает список файлов, проверяя, присутствуют ли файлы локально. (С проверкой хэша SHA и несколькими другими полезностями.) Если файлы отсутствуют локально или хеш не совпадает, он загружает их.

На стороне сервера установлен простой Apache 2 в Ubuntu через порт 80.

Клиентская сторона отлично работает на Mac и Linux, но выдает мне эту ошибку на Windows (XP и Vista) после загрузки ряда файлов:

urllib2.URLError: <urlopen error <10048, 'Address already in use'>>

Эта ссылка: http://bytes.com/topic/python/answers/530949-client-side-tcp-socket-receiving-address-already-use-upon-connect указывает на исчерпание порта TCP, но «netstat -n» никогда не показывал мне более шести соединений в состоянии «TIME_WAIT», даже до того, как произошла ошибка.

Код (вызывается один раз для каждого из 55 000 загружаемых файлов):

request = urllib2.Request(file_remote_path)
opener = urllib2.build_opener()
datastream = opener.open(request)
outfileobj = open(temp_file_path, 'wb')
try:
    while True:
        chunk = datastream.read(CHUNK_SIZE)
        if chunk == '':
            break
        else:
            outfileobj.write(chunk)
finally:
    outfileobj = outfileobj.close()
    datastream.close()

ОБНОВЛЕНИЕ: я обнаружил, что, заносив журнал, он входит в процедуру загрузки ровно 3998 раз. Я запускал это несколько раз, и каждый раз это не удается на 3998. Учитывая, что в связанной статье утверждается, что доступные порты 5000-1025 = 3975 (а некоторые, вероятно, истекают и используются повторно), это начинает выглядеть намного больше, как в связанной статье описывается реальная проблема. Тем не менее, я все еще не уверен, как это исправить. Вносить изменения в реестр нельзя.

Ответы [ 5 ]

5 голосов
/ 08 октября 2009

Если это действительно проблема с ресурсами (освобождение ресурсов сокета ОС)

попробуйте это:

request = urllib2.Request(file_remote_path)
opener = urllib2.build_opener()

retry = 3 # 3 tries
while retry :
    try :
        datastream = opener.open(request)
    except urllib2.URLError, ue:
        if ue.reason.find('10048') > -1 :
            if retry :
                retry -= 1
            else :
                raise urllib2.URLError("Address already in use / retries exhausted")
        else :
            retry = 0
    if datastream :
        retry = 0

outfileobj = open(temp_file_path, 'wb')
try:
    while True:
        chunk = datastream.read(CHUNK_SIZE)
        if chunk == '':
            break
        else:
            outfileobj.write(chunk)
finally:
    outfileobj = outfileobj.close()
    datastream.close()

если хотите, можете вставить сон или сделать его зависимым

на моем win-xp проблема не появляется (я загрузил 5000 загрузок)

Я наблюдаю за своими процессами и сетью с помощью Process Hacker .

1 голос
/ 11 октября 2009

Все показания указывают на отсутствие доступных розеток. Вы уверены, что только 6 находятся в состоянии TIME_WAIT? Если вы выполняете так много операций загрузки, вполне вероятно, что netstat переполнит ваш терминальный буфер. Я считаю, что netstat stat заполняет мой терминал в периоды нормального использования.

Решение состоит в том, чтобы либо изменить код для повторного использования сокетов. Или ввести тайм-аут. Также не мешало бы отслеживать, сколько у вас открытых сокетов. Чтобы оптимизировать ожидание. Время ожидания по умолчанию в Windows XP составляет 120 секунд. так что вы хотите спать по крайней мере так долго, если у вас закончились розетки. К сожалению, не похоже, что есть простой способ проверить из Python, когда сокет закрылся и вышел из состояния TIME_WAIT.

Учитывая асинхронный характер запросов и тайм-аутов, лучший способ сделать это может быть в потоке. Дайте каждой угрозе спать на 2 минуты, прежде чем она закончится. Вы можете использовать семафор или ограничить количество активных потоков, чтобы не допустить исчерпания сокетов.

Вот как я справлюсь с этим. Возможно, вы захотите добавить исключительную оговорку во внутренний блок try секции fetch, чтобы предупредить вас о неудачных выборках.

import time
import threading
import Queue

# assumes url_queue is a Queue object populated with tuples in the form of(url_to_fetch, temp_file)
# also assumes that TotalUrls is the size of the queue before any threads are started.


class urlfetcher(threading.Thread)
    def __init__ (self, queue)
        Thread.__init__(self)
        self.queue = queue


    def run(self)
        try: # needed to handle empty exception raised by an empty queue.
            file_remote_path, temp_file_path = self.queue.get()
            request = urllib2.Request(file_remote_path)
            opener = urllib2.build_opener()
            datastream = opener.open(request)
            outfileobj = open(temp_file_path, 'wb')
            try:
                while True:
                    chunk = datastream.read(CHUNK_SIZE)
                    if chunk == '':
                        break
                    else:
                        outfileobj.write(chunk)
            finally:
                outfileobj = outfileobj.close()
                datastream.close()    
                time.sleep(120)
                self.queue.task_done()

elsewhere:


while url_queue.size() < TotalUrls: # hard limit of available ports.
    if threading.active_threads() < 3975: # Hard limit of available ports
         t = urlFetcher(url_queue)
         t.start()
    else: 
        time.sleep(2)

url_queue.join()

Извините, мой питон немного ржавый, поэтому я не удивлюсь, если что-то пропустил.

1 голос
/ 11 октября 2009

Вместо того, чтобы открывать новое TCP-соединение для каждого запроса, вам действительно следует использовать постоянные HTTP-соединения - посмотрите urlgrabber (или, альтернативно, просто keepalive.py , чтобы узнать, как добавить поддержку keep-alive соединения в urllib2).

1 голос
/ 09 октября 2009

Вам следует серьезно подумать о копировании и изменении этого примера pyCurl для эффективной загрузки большой коллекции файлов.

1 голос
/ 03 октября 2009

Если мыслить нестандартно, проблема, которую вы, похоже, пытаетесь решить, уже была решена программой rsync. Вы можете найти реализацию Windows и посмотреть, соответствует ли она вашим потребностям.

...