Come posso ottenere socket non bloccanti connect ()?
-
05-07-2019 - |
Domanda
Ho un problema abbastanza semplice qui. Devo comunicare con molti host contemporaneamente, ma non ho davvero bisogno di alcuna sincronizzazione perché ogni richiesta è abbastanza autosufficiente.
Per questo motivo, ho scelto di lavorare con socket asincroni, piuttosto che thread di spamming. Ora ho un piccolo problema:
Le cose asincrone funzionano come un incantesimo, ma quando mi collego a 100 host e ottengo 100 timeout (timeout = 10 secondi), quindi aspetto 1000 secondi, solo per scoprire che tutte le mie connessioni non sono riuscite.
Esiste un modo per ottenere anche connessioni socket non bloccanti? Il mio socket è già impostato su nonBlocking, ma le chiamate a connect () stanno ancora bloccando.
Ridurre il timeout non è una soluzione accettabile.
Lo sto facendo in Python, ma credo che in questo caso il linguaggio di programmazione non abbia importanza.
Devo davvero usare i thread?
Soluzione
È necessario parallelizzare anche le connessioni, poiché i socket si bloccano quando si imposta un timeout. In alternativa, non è possibile impostare un timeout e utilizzare il modulo select.
Puoi farlo con la classe dispatcher nel asyncore . Dai un'occhiata al esempio client http di base . Più istanze di quella classe non si bloccano a vicenda durante la connessione. Puoi farlo altrettanto facilmente usando i thread e penso che semplifichi il monitoraggio dei timeout dei socket, ma poiché stai già utilizzando metodi asincroni potresti anche rimanere sulla stessa traccia.
Ad esempio, quanto segue funziona su tutti i miei sistemi 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()
Dove in cluster50 - cluster100, ci sono numerose macchine che non rispondono o inesistenti. Questo inizia immediatamente a stampare:
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
...
Questo tuttavia non tiene conto di getaddrinfo, che deve essere bloccato. Se riscontri problemi nella risoluzione delle query DNS, tutto deve attendere. Probabilmente dovrai raccogliere le query DNS separatamente da solo e utilizzare gli indirizzi IP nel tuo ciclo asincrono
Se desideri un toolkit più grande di asyncore, dai un'occhiata a Twisted Matrix . È un po 'pesante da entrare, ma è il miglior toolkit di programmazione di rete che puoi ottenere per Python.
Altri suggerimenti
Utilizza il modulo select
. Ciò consente di attendere il completamento dell'I / O su più socket non bloccanti. Ecco alcune ulteriori informazioni sulla selezione. Dalla pagina collegata:
In C, la codifica
select
è abbastanza complessa. In Python, è un gioco da ragazzi, ma è abbastanza vicino alla versione C. che se capisci seleziona in Python, avrai pochi problemi con esso in C.
ready_to_read, ready_to_write, in_error = select.select(
potential_readers,
potential_writers,
potential_errs,
timeout)
Passa a
select
tre elenchi: il primo contiene tutti i socket che potresti voglio provare a leggere; il secondo tutto le prese che potresti voler provare scrivendo a, e l'ultimo (normalmente lasciato vuoto) quelli che vuoi verificare la presenza di errori. Dovresti notare che un socket può entrare in più di uno elenco. La chiamataselect
sta bloccando, ma puoi dargli un timeout. Questo è generalmente una cosa sensata da fare - dare un bel timeout lungo (dire a minuto) a meno che tu non abbia buone ragioni per farlo fare diversamente.In cambio, otterrai tre elenchi. Hanno le prese che lo sono effettivamente leggibile, scrivibile e in errore. Ognuno di questi elenchi è un sottoinsieme (possibilmente vuoto) del corrispondente lista in cui sei passato. E se hai inserito un socket in più di un elenco di input, esso sarà (al massimo) in un solo output lista.
Se nell'output è leggibile un socket lista, puoi esserlo as-a-vicino-certo-come-lo-mai-get-in-questo-business che verrà restituito un
recv
su quel socket qualcosa. Stessa idea per lo scrivibile elenco. Sarai in grado diinviare
qualcosa. Forse non tutto quello che vuoi ma qualcosa è meglio di niente. (In realtà, qualsiasi ragionevolmente sano socket tornerà come scrivibile - esso significa solo buffer di rete in uscita lo spazio è disponibile.)Se hai un " server " presa, mettilo nella lista dei potenziali_readers. Se viene visualizzato nell'elenco leggibile, il tuo accettare funzionerà (quasi certamente). Se è stato creato un nuovo socket per connettersi a qualcun altro, inserirlo nel elenco dei potenziali autori. Se si presenta nell'elenco scrivibile, hai un discreta possibilità che si sia collegato.
Sfortunatamente non esiste un codice di esempio che mostri il bug, quindi è un po 'difficile vedere da dove provenga questo blocco.
Fa qualcosa del tipo:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.setblocking(0)
s.connect(("www.nonexistingname.org", 80))
Il modulo socket utilizza getaddrinfo internamente, il che è un'operazione di blocco, specialmente quando il nome host non esiste. Un client DNS conforme allo standard attenderà un po 'di tempo per vedere se il nome non esiste davvero o se sono coinvolti solo alcuni server DNS lenti.
La soluzione è quella di connettersi solo agli indirizzi IP o utilizzare un client DNS che consente richieste non bloccanti, come pydns .
Usa twisted .
È un motore di rete asincrono scritto in Python, che supporta numerosi protocolli e puoi aggiungerne uno tuo. Può essere utilizzato per sviluppare client e server. Non si blocca su connect.
Hai esaminato il asyncore ? Potrebbe essere proprio quello che ti serve.