2009-07-30 12 views
9

Tengo un problema bastante simple aquí. Necesito comunicarme con muchos hosts simultáneamente, pero realmente no necesito ninguna sincronización porque cada solicitud es bastante autosuficiente.¿Cómo puedo obtener socket connect() sin bloqueo?

Debido a eso, elegí trabajar con sockets asincrónicos, en lugar de spamming. Ahora tengo un pequeño problema:

Las cosas asincrónicas funcionan como un amuleto, pero cuando me conecto a 100 hosts, y obtengo 100 tiempos de espera (tiempo de espera = 10 segundos), entonces espero 1000 segundos, solo para descubrir todas mis conexiones fallaron

¿Hay alguna manera de obtener también conexiones de zócalo no bloqueantes? Mi socket ya está configurado para no bloquear, pero las llamadas a connect() todavía están bloqueando.

Reducir el tiempo de espera no es una solución aceptable.

Estoy haciendo esto en Python, pero supongo que el lenguaje de programación realmente no importa en este caso.

¿Realmente necesito usar hilos?

Respuesta

4

Es necesario poner en paralelo los conecta también, ya que los enchufes bloquean cuando se establece un tiempo de espera. Alternativamente, no podría establecer un tiempo de espera y usar el módulo de selección.

Puede hacerlo con la clase de despachador en el módulo asyncore. Eche un vistazo al básico http client example. Varias instancias de esa clase no se bloquearán entre sí en connect. Puede hacerlo fácilmente con subprocesos, y creo que hace más fácil el tiempo de espera del socket de seguimiento, pero como ya está utilizando métodos asíncronos, es mejor que permanezca en la misma pista.

A modo de ejemplo, las siguientes obras en todos mis sistemas 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() 

Donde en cluster50 - cluster100, hay numerosas máquinas que no responden, o no existe. Esto inmediatamente comienza a imprimir:

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 

... 

Sin embargo, esto no tiene en cuenta getaddrinfo, que tiene que bloquear. Si tiene problemas para resolver las consultas DNS, todo tiene que esperar. Probablemente necesite reunir las consultas DNS por separado y usar las direcciones IP en su ciclo asíncrono

Si desea un conjunto de herramientas más grande que asyncore, consulte Twisted Matrix. Es un poco difícil de conseguir, pero es el mejor juego de herramientas de programación de red que puedes obtener para Python.

+0

Muy bien, tengo que disculparme aquí. Cogí el código directamente de los documentos de Python, por lo que no era mi código, daba por sentado que era correcto. Y no funcionó. Me sucedía frecuentemente que la gente me daba consejos, que ni siquiera se confirmaban a sí mismos. Nunca podría haber supuesto que mi sistema operativo sería el problema, en lugar del código, así que pensé que era solo un tipo más que pensaba que era inteligente y copiaba el código de documentación sin siquiera comprobar si funciona. Perdón otra vez por eso. Tiré 3 versiones completas hoy, perdiendo 6 horas, para encontrar que MacOS era el problema. – Tom

+0

por cierto, probé esto de nuevo junto con un amigo en su linux box, e incluso getAddrInfo no parece bloquearse allí. Obtenemos un error: [Errno 115] Operación ahora en progreso. Así que, teóricamente, incluso asyncore con hosts no receptivos podría funcionar en Linux. – Tom

+0

@Tom - np, estoy de acuerdo en que hay toneladas de respuestas desinformadas por aquí, especialmente en los campos que no son Windows. Lo que es peor, es que los equipos de desinformados terminan votando entre sí, lo que dificulta obtener respuestas correctas. – JimB

0

¿Has mirado el módulo asyncore? Puede ser justo lo que necesitas.

+0

estoy usando esto, y todavía bloquea en connect – Tom

8

Utilice el módulo select. Esto le permite esperar la finalización de E/S en múltiples sockets no bloqueantes. Aquí está some more information en seleccionar. Desde el vinculado a la página:

In C, coding select is fairly complex. In Python, it's a piece of cake, but it's close enough to the C version that if you understand select in Python, you'll have little trouble with it in C.

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

You pass select three lists: the first contains all sockets that you might want to try reading; the second all the sockets you might want to try writing to, and the last (normally left empty) those that you want to check for errors. You should note that a socket can go into more than one list. The select call is blocking, but you can give it a timeout. This is generally a sensible thing to do - give it a nice long timeout (say a minute) unless you have good reason to do otherwise.

In return, you will get three lists. They have the sockets that are actually readable, writeable and in error. Each of these lists is a subset (possibly empty) of the corresponding list you passed in. And if you put a socket in more than one input list, it will only be (at most) in one output list.

If a socket is in the output readable list, you can be as-close-to-certain-as-we-ever-get-in-this-business that a recv on that socket will return something. Same idea for the writeable list. You'll be able to send something. Maybe not all you want to, but something is better than nothing. (Actually, any reasonably healthy socket will return as writeable - it just means outbound network buffer space is available.)

If you have a "server" socket, put it in the potential_readers list. If it comes out in the readable list, your accept will (almost certainly) work. If you have created a new socket to connect to someone else, put it in the potential_writers list. If it shows up in the writeable list, you have a decent chance that it has connected.

+0

Él dice específicamente que está siendo bloqueado en connect(). Seleccionar solo le dice lo que se puede leer o escribir. – JimB

+1

Ver el último párrafo de mi respuesta. Con la multiplexación 'select', no necesita esperar 1000 segundos antes de realizar un trabajo útil. Con un tiempo de espera corto, aún puede hacer un trabajo útil si todos los puntos finales no están conectados, con solo una corta espera. Twisted es, por supuesto, una alternativa, pero como dijiste "es un poco pesado". –

+0

Ahh, veo el problema ... estableció un tiempo de espera, lo que significa que el socket * tiene * que bloquear. – JimB

4

Use twisted.

Es un motor de red asíncrono escrito en Python que admite numerosos protocolos y puede agregar el suyo. Se puede usar para desarrollar clientes y servidores. No bloquea al conectar

+2

Twisted trae tanta felicidad. Trabajo con eso todos los días y trato de convencer a aquellos que luchan con la concurrencia de que harán sus vidas mucho más fáciles. Por supuesto, mis compañeros de trabajo al menos llegan a ver la diferencia. – Dustin

+1

He usado twisted before, es bastante agradable, pero la documentación también está torcida. También será difícil integrar mi fuente en eso. ¿Estás seguro de que no bloquea las conexiones? Podría intentar ir por eso entonces. – Tom

6

Desafortunadamente no hay un código de ejemplo que muestre el error, por lo que es un poco difícil ver de dónde viene este bloque.

Él hace algo como:

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

El módulo conector utiliza getaddrinfo internamente, que es una operación de bloqueo, especialmente cuando no existe el nombre de host. Un cliente de DNS compatible con el estándar esperará algún tiempo para ver si el nombre realmente no existe o si solo hay algunos servidores dns lentos involucrados.

La solución es conectar con direcciones IP solamente o usar un cliente dns que permita solicitudes sin bloqueo, como pydns.

+0

que prácticamente reduce el meollo del problema. Parece que estoy teniendo problemas de DNS. El comportamiento de mi aplicación (al menos en la etapa inicial) es bastante similar a un escaneo de puertos: dependo de resultados muy rápidos, ya sea que la conexión funcione o no.El uso de getaddrinfo en los nombres de host inexistentes también bloquea los sockets que no bloquean, lo cual es malo (para mí). También podría conectarme a una gran cantidad de hosts inexistentes, y no puedo permitir esperar 10 segundos en cada host inexistente. – Tom

+1

Mi propósito era muy diferente, pero se solucionó al cambiar el orden. es decir, primero conecte y luego bloquee. – Ben

+0

@Ben ¡eso solucionó mi problema también! ¡Gracias! – JeromeJ

Cuestiones relacionadas