Comment puis-je obtenir une connexion socket () non bloquante?
-
05-07-2019 - |
Question
J'ai un problème assez simple ici. J'ai besoin de communiquer simultanément avec de nombreux hôtes, mais je n'ai pas vraiment besoin de synchronisation, car chaque requête est assez autonome.
À cause de cela, j'ai choisi de travailler avec des sockets asynchrones plutôt que des threads de spam. Maintenant, j'ai un petit problème:
Le processus asynchrone fonctionne à merveille, mais lorsque je me connecte à 100 hôtes et que je reçois 100 délais d’expiration (timeout = 10 secondes), j’attends 1000 secondes pour découvrir que toutes mes connexions ont échoué.
Existe-t-il un moyen d'obtenir également des connexions de socket non bloquantes? Mon socket est déjà défini sur nonBlocking, mais les appels à connect () bloquent toujours.
Réduire le délai d'attente n'est pas une solution acceptable.
Je le fais en Python, mais j'imagine que le langage de programmation n'a pas vraiment d'importance dans ce cas.
Dois-je vraiment utiliser des threads?
La solution
Vous devez également paralléliser les connexions, car les sockets se bloquent lorsque vous définissez un délai. Sinon, vous ne pouvez pas définir de délai d'expiration et utiliser le module select.
Vous pouvez le faire avec la classe dispatcher du module asyncore . . Jetez un coup d’œil à la exemple de client http de base . Plusieurs instances de cette classe ne se bloqueront pas lors de la connexion. Vous pouvez le faire tout aussi facilement en utilisant des threads, ce qui facilite le suivi des délais de socket, mais puisque vous utilisez déjà des méthodes asynchrones, vous pouvez également rester sur la même piste.
Par exemple, ce qui suit fonctionne sur tous mes systèmes 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()
Où dans cluster50 - cluster100, il existe de nombreuses machines qui ne répondent pas ou qui n'existent pas. Cela commence immédiatement à imprimer:
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
...
Ceci ne prend cependant pas en compte getaddrinfo, qui doit être bloqué. Si vous rencontrez des problèmes pour résoudre les requêtes DNS, tout doit attendre. Vous devrez probablement rassembler vous-même les requêtes DNS et utiliser les adresses IP dans votre boucle async.
Si vous souhaitez une boîte à outils plus grande qu'un asyncore, consultez Twisted Matrix . C’est un peu lourd, mais c’est la meilleure boîte à outils de programmation réseau pour python.
Autres conseils
Utilisez le module select
. Cela vous permet d’attendre la fin des E / S sur plusieurs sockets non bloquants. Voici quelques informations supplémentaires sur select. A partir de la page liée:
En C, coder
select
est assez complexe. En Python, c'est un morceau de gâteau, mais c'est assez proche de la version C que si vous comprenez sélectionnez dans Python, vous aurez peu de problèmes avec en C.
ready_to_read, ready_to_write, in_error = select.select(
potential_readers,
potential_writers,
potential_errs,
timeout)
Vous passez
sélectionnez
trois listes: la première contient toutes les sockets que vous pourriez vouloir essayer de lire; le second tout les prises que vous voudrez peut-être essayer écrit à, et le dernier (normalement laissés vides) ceux que vous voulez vérifier les erreurs. Vous devriez noter que une prise peut entrer dans plus d'un liste. L’appelselect
bloque, mais vous pouvez lui donner un temps mort. C'est généralement une chose sensée à faire - donnez-lui un long délai d’attente (disons un minute) sauf si vous avez de bonnes raisons de faire autrement.En retour, vous obtiendrez trois listes. Ils ont les prises qui sont effectivement lisible, inscriptible et en Erreur. Chacune de ces listes est un sous-ensemble (éventuellement vide) du correspondant liste que vous avez passé. Et si vous mettez un socket dans plusieurs listes d'entrées, il sera (au plus) dans une sortie liste.
Si une socket est dans la sortie lisible liste, vous pouvez être aussi proche de certains que nous arrivons toujours dans cette entreprise qu'un
recv
sur ce socket retournera quelque chose. Même idée pour l'écriture liste. Vous pourrezenvoyer
quelque chose. Peut-être pas tout ce que tu veux, mais quelque chose vaut mieux que rien. (En fait, toute personne raisonnablement en bonne santé socket retournera en écriture - il signifie simplement tampon réseau sortant l'espace est disponible.)Si vous avez un " serveur " prise, le mettre dans la liste potential_readers. Si ça vient dans la liste lisible, votre acceptera (presque certainement) le travail. Si vous avez créé un nouveau socket pour se connecter à quelqu'un d'autre, le mettre dans le liste de potentiels. Si cela se présente dans la liste accessible en écriture, vous avez un chance décente qu'il s'est connecté.
Malheureusement, il n'y a pas d'exemple de code montrant le bogue, il est donc un peu difficile de voir d'où vient ce bloc.
Il fait quelque chose comme:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.setblocking(0)
s.connect(("www.nonexistingname.org", 80))
Le module de socket utilise getaddrinfo en interne, ce qui constitue une opération de blocage, en particulier lorsque le nom d'hôte n'existe pas. Un client DNS conforme aux normes attendra quelque temps avant de voir si le nom n'existe pas réellement ou si quelques serveurs DNS lents sont impliqués.
La solution consiste à se connecter aux adresses IP uniquement ou à utiliser un client DNS qui autorise les requêtes non bloquantes, comme pydns .
Utilisez tordu .
C’est un moteur de réseau asynchrone écrit en Python, prenant en charge de nombreux protocoles et que vous pouvez ajouter le vôtre. Il peut être utilisé pour développer des clients et des serveurs. Il ne bloque pas lors de la connexion.
Avez-vous consulté le module asyncore ? Peut-être juste ce dont vous avez besoin.