Как я могу получить неблокирующие сокеты connect()?

У меня тут довольно простая проблема. Мне нужно общаться со многими хостами одновременно, но мне не нужна никакая синхронизация, потому что каждый запрос вполне самодостаточен.

Из-за этого я решил работать с асинхронными сокетами, а не спамить потоки. Теперь у меня есть небольшая проблема:

Асинхронные вещи работают как шарм, но когда я подключаюсь к 100 хостам и получаю 100 тайм-аутов (время ожидания = 10 секунд), я жду 1000 секунд, просто чтобы узнать, что все мои соединения не удались.

Есть ли способ также получить неблокирующие соединения сокетов? Мой сокет уже настроен на неблокирующий, но вызовы connect() все еще блокируются.

Уменьшение времени ожидания не является приемлемым решением.

Я делаю это на Python, но я думаю, что язык программирования в данном случае не имеет большого значения.

Мне действительно нужно использовать потоки?


person Tom    schedule 30.07.2009    source источник


Ответы (5)


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

Это можно сделать с помощью класса диспетчера в модуле asyncore. Взгляните на базовый пример HTTP-клиента. . Несколько экземпляров этого класса не будут блокировать друг друга при подключении. Вы можете сделать это так же легко, используя потоки, и я думаю, что это упрощает отслеживание времени ожидания сокета, но, поскольку вы уже используете асинхронные методы, вы можете оставаться на том же пути.

Например, следующее работает на всех моих Linux-системах.

import asyncore, socket

class client(asyncore.dispatcher):
    def __init__(self, host):
        self.host = host
        asyncore.dispatcher.__init__(self)
        self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
        self.connect((host, 22))

    def handle_connect(self):
        print 'Connected to', self.host

    def handle_close(self):
        self.close()

    def handle_write(self):
        self.send('')

    def handle_read(self):
        print ' ', self.recv(1024)

clients = []
for i in range(50, 100):
    clients.append(client('cluster%d' % i))

asyncore.loop()

Где в кластере 50 - кластере 100 есть множество машин, которые не отвечают или не существуют. Сразу начинается печать:

Connected to cluster50
  SSH-2.0-OpenSSH_4.3

Connected to cluster51
  SSH-2.0-OpenSSH_4.3

Connected to cluster52
  SSH-2.0-OpenSSH_4.3

Connected to cluster60
  SSH-2.0-OpenSSH_4.3

Connected to cluster61
  SSH-2.0-OpenSSH_4.3

...

Это, однако, не принимает во внимание getaddrinfo, который должен блокироваться. Если у вас возникли проблемы с разрешением DNS-запросов, все должно подождать. Вероятно, вам нужно собирать DNS-запросы отдельно и использовать IP-адреса в вашем асинхронном цикле.

Если вам нужен набор инструментов побольше, чем asyncore, взгляните на Twisted Matrix. В него немного сложно вникнуть, но это лучший набор инструментов для сетевого программирования, который вы можете получить для Python.

person JimB    schedule 30.07.2009
comment
Хорошо, я должен извиниться здесь. Я взял код прямо из документации Python, так что это был не мой код, я считал его правильным. И это не сработало. Со мной часто случалось, что люди давали мне советы, в которых даже сами не проверяли. Я никогда не мог предположить, что проблема будет в моей ОС, а не в коде, поэтому я подумал, что вы просто еще один парень, считающий себя умным, и копирует и вставляет мне код документации, даже не проверяя, работает ли он. Извините еще раз за это. Сегодня я выбросил 3 полных версии, потратив 6 часов, чтобы обнаружить, что проблема была в MacOS. - person Tom; 31.07.2009
comment
Кстати, я снова проверил это вместе с другом на его Linux-боксе, и даже getAddrInfo, похоже, не блокируется там. Получаем ошибку: [Errno 115] Выполняется операция. Так что теоретически даже асинхронный сервер с неотвечающими хостами может работать в Linux. - person Tom; 31.07.2009
comment
@Tom - np, я согласен, что здесь много неосведомленных ответов, особенно в областях, не связанных с Windows. Что еще хуже, команды неосведомленных в конечном итоге голосуют друг за друга, что затрудняет получение правильных ответов. - person JimB; 31.07.2009
comment
Получаем ошибку: [Errno 115] Выполняется операция. Так что теоретически даже asyncore с не отвечающими хостами может работать в Linux - я почти уверен, что это так, я просто не мог достаточно взломать свой DNS, чтобы зависнуть, чтобы проверить это. - person JimB; 31.07.2009
comment
@JimB, пожалуйста, дайте ответ для модуля asyncio? - person Vova; 29.09.2018

Используйте модуль select. Это позволяет дождаться завершения ввода-вывода на нескольких неблокирующих сокетах. Вот дополнительная информация о выборе. Со страницы, на которую есть ссылка:

В C кодирование select довольно сложно. В Python это проще простого, но достаточно близко к версии C, поэтому, если вы понимаете select в Python, у вас не возникнет проблем с ним в C.

ready_to_read, ready_to_write, in_error = select.select(
                  potential_readers, 
                  potential_writers, 
                  potential_errs, 
                  timeout)

Вы передаете select три списка: первый содержит все сокеты, которые вы, возможно, захотите прочитать; второй — все сокеты, в которые вы, возможно, захотите попробовать записать, а последний (обычно оставленный пустым) — те, которые вы хотите проверить на наличие ошибок. Вы должны заметить, что сокет может входить в более чем один список. Вызов select блокируется, но вы можете дать ему тайм-аут. Как правило, это разумный поступок - дайте ему хороший длительный тайм-аут (скажем, минуту), если у вас нет веских причин поступать иначе.

Взамен вы получите три списка. У них есть сокеты, которые на самом деле доступны для чтения, записи и ошибок. Каждый из этих списков является подмножеством (возможно, пустым) соответствующего списка, который вы передали. И если вы поместите сокет более чем в один входной список, он будет (максимум) только в одном выходном списке.

Если сокет находится в выходном читаемом списке, вы можете быть настолько близки к уверенности, насколько мы когда-либо занимаемся этим делом, что recv в этом сокете что-то вернет. Та же идея для записываемого списка. Вы сможете send кое-что сделать. Может быть, не все, что вы хотите, но что-то лучше, чем ничего. (На самом деле, любой достаточно работоспособный сокет будет возвращен как доступный для записи - это просто означает, что пространство исходящего сетевого буфера доступно.)

Если у вас есть «серверный» сокет, поместите его в список потенциальных_читателей. Если он появится в читаемом списке, ваш accept (почти наверняка) сработает. Если вы создали новый сокет для подключения к кому-то еще, поместите его в список потенциальных_писателей. Если он отображается в списке доступных для записи, у вас есть неплохая вероятность, что он подключен.

person Vinay Sajip    schedule 30.07.2009
comment
Он конкретно говорит, что его блокируют на connect(). Select только сообщает вам, что доступно для чтения или записи. - person JimB; 30.07.2009
comment
Смотрите последний абзац моего ответа. Благодаря мультиплексированию select вам не нужно ждать 1000 секунд, прежде чем выполнять полезную работу. С коротким временем ожидания вы все равно можете выполнять полезную работу, если все конечные точки не подключены, с небольшим ожиданием. Twisted, конечно, является альтернативой, но, как вы сами сказали, в него немного тяжело вникнуть. - person Vinay Sajip; 30.07.2009
comment
Ах, я вижу проблему ... он установил тайм-аут, что означает, что сокет должен блокироваться. - person JimB; 30.07.2009
comment
Я ничего не устанавливал явно, я использовал асинхронный модуль Python, который кажется более или менее оболочкой для select(). Я создал еще один короткий пример сценария тестирования, просто создав сокет и установив для него неблокирующий режим, но он по-прежнему блокируется при подключении, а не при чтении. - person Tom; 31.07.2009
comment
@Tom, видите ли, вы не упомянули, что используете asyncore с опцией тайм-аута, поэтому я логично предположил, что вы использовали socket.settimeout(), который устанавливает блокировку. Какая у вас платформа и версия Python? Connect не блокируется в моих системах с помощью setblocking(0) - person JimB; 31.07.2009

К сожалению, нет примера кода, показывающего ошибку, поэтому немного сложно понять, откуда берется этот блок.

Он делает что-то вроде:

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.setblocking(0)
s.connect(("www.nonexistingname.org", 80))

Модуль сокета использует внутри себя getaddrinfo, что является блокирующей операцией, особенно когда имя хоста не существует. Стандартный совместимый DNS-клиент подождет некоторое время, чтобы увидеть, действительно ли имя не существует или задействованы только некоторые медленные DNS-серверы.

Решение состоит в том, чтобы подключаться только к ip-адресам или использовать DNS-клиент, который разрешает неблокирующие запросы, например pydns< /а>.

person ebo    schedule 31.07.2009
comment
это в значительной степени сокращает суть проблемы. Кажется, у меня проблемы с DNS. Поведение моего приложения (по крайней мере, на начальном этапе) очень похоже на сканер портов: я завишу от очень быстрых результатов, независимо от того, работает соединение или нет. Использование getaddrinfo для несуществующих имен хостов также блокирует неблокирующие сокеты, что плохо (для меня). Я также могу подключиться к множеству несуществующих хостов, и я не могу позволить себе ждать 10 секунд на каждом несуществующем хосте. - person Tom; 31.07.2009
comment
Моя цель была совсем другой, но была исправлена ​​​​путем изменения порядка. т.е. сначала подключитесь, затем установите блокировку. - person Ben; 27.02.2013
comment
@ Бен, это тоже решило мою проблему! Спасибо! - person jeromej; 20.02.2017

Используйте twisted.

Это асинхронный сетевой движок, написанный на Python, поддерживающий множество протоколов, и вы можете добавить свой собственный. Его можно использовать для разработки клиентов и серверов. Он не блокируется при подключении.

person nosklo    schedule 30.07.2009
comment
Twisted приносит такое счастье. Я работаю с ним каждый день и пытаюсь убедить тех, кто борется с параллелизмом, что это сделает их жизнь намного проще. Конечно, мои коллеги по крайней мере видят разницу. - person Dustin; 31.07.2009
comment
Раньше я использовал скручивание, это неплохо, но документация тоже скручена. Также будет сложно интегрировать мой источник в это. Вы уверены, что он не блокируется при подключении? Может попробовать пойти на это тогда. - person Tom; 31.07.2009

Вы смотрели модуль asyncore? Может быть как раз то, что вам нужно.

person Maxim Sloyko    schedule 30.07.2009
comment
я использую это, и он все еще блокируется при подключении - person Tom; 31.07.2009