2011-02-04 8 views
14

Cuando cierro la toma situada en un extremo de una conexión, el otro extremo se pone un error la segunda vez que envía los datos, pero no la primera vez:Python no detecta una toma cerrada hasta la segunda enviar

import socket 

server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 
server.bind(("localhost", 12345)) 
server.listen(1) 

client = socket.create_connection(("localhost",12345)) 
sock, addr = server.accept() 
sock.close() 

client.sendall("Hello World!") # no error 
client.sendall("Goodbye World!") # error happens here 

He intentado configurar TCP_NODELAY, usando send en lugar de sendall, comprobando el fileno(), no encuentro ninguna forma de obtener el primer envío para arrojar un error o incluso detectar que falló. EDITAR: llamando al sock.shutdown antes de sock.close no ayuda. EDIT # 2: incluso agregando un time.sleep después del cierre y antes de escribir no importa. EDIT # 3: comprobar el conteo de bytes devuelto por send no ayuda, ya que siempre devuelve el número de bytes en el mensaje.

De modo que la única solución que puedo encontrar si quiero detectar errores es seguir cada sendall con un que generará un error. Pero esto parece hackish. Estoy en un Linux 2.6.x así que incluso si una solución solo funciona para ese sistema operativo, estaría feliz.

+0

estoy teniendo el mismo problema. ¿Cómo terminaste resolviendo el problema en tu código? – HighCommander4

+0

@HighCommander: Terminé agregando un 'cliente.sendall ("") 'después de cada' enviar', que técnicamente no está garantizado para el error, pero funciona para mi programa dando los tiempos y detalles de lo que estoy haciendo. Esto es poco elegante y realmente desearía que hubiera una forma de acceder al estado del socket TCP subyacente, pero desafortunadamente parece que no lo está, ya sea en Python o C. –

+0

¿funciona para usted cuando el cliente y el servidor están en máquinas diferentes? No es para mí, el sendall() tiene éxito en ese caso. – HighCommander4

Respuesta

13

Esto se espera, y cómo se implementan las API de TCP/IP (por lo que es similar en casi todas las lenguas y en todos los sistemas operativos)

El cuento es, no se puede hacer nada para garantizar que un envío() la llamada devuelve un error directamente si esa llamada a send() de alguna manera no puede entregar datos al otro extremo. las llamadas de envío/escritura solo entregan los datos a la pila TCP, y depende de la pila TCP entregarlos cuando sea posible.

TCP es también solo un protocolo de transporte, si necesita saber si los "mensajes" de su aplicación han llegado al otro extremo, debe implementarlo usted mismo (alguna forma de ACK), como parte de su protocolo de aplicación. ningún otro almuerzo gratis.

Sin embargo, si lee() desde un socket, puede recibir una notificación inmediatamente cuando ocurre un error, o cuando el otro extremo cierra el socket - generalmente necesita hacer esto en algún tipo de bucle de evento de multiplexión (que es , usando select/poll o alguna otra facilidad de multiplexación IO).

Simplemente tenga en cuenta que no puede leer() desde un socket para saber si el envío/escritura más reciente tuvo éxito, aquí hay algunos casos del por qué (pero son los casos que uno no piensa que siempre lo obtendrán)

  • varios write() quedó amortiguada debido a la congestión de la red, o porque la ventana TCP estaba cerrado (tal vez un lector lento) y luego el otro extremo se cierra el socket o una red de disco se produce un error, por lo que se puede' d decir si fue la última escritura que no se realizó, o una escritura que hizo hace 30 segundos.
  • Error de red o cortafuegos silenciosamente descarta sus paquetes (no se generan respuestas ICMP), tendrá que esperar hasta que el TCP agote la conexión para obtener un error que puede durar varios segundos, generalmente varios minutos.
  • TCP está ocupado haciendo retransmisión como se llama a send -. Tal vez esas retransmisiones generan un error (en realidad es el mismo que el primer caso)
+0

Gracias por la respuesta detallada, pero todavía estoy un poco confundido acerca de esto. ¿El otro lado de una conexión no envía paquetes ACK para confirmar que recibió los datos? Supongo por su respuesta que 'enviar' no espera a este acuse de recibo, pero ¿hay alguna otra manera de bloquearlo (en el nivel de transporte) hasta que sepamos que nuestros datos han llegado? Entiendo que, en general, esto debería estar sucediendo a nivel de aplicación, pero actualmente estoy hablando de un protocolo que no tiene tal reconocimiento, por lo que me pregunto cuáles son mis opciones en el nivel de transporte. –

+0

Además, ¿TCP no envía un paquete FIN cuando se cierra? –

+3

@Eli Courtwright. Sí. TCP envía ACK. Sin embargo, eso es ACK en el TCP (capa de transporte). TCP no sabe acerca de su mensaje, es solo una secuencia de bytes en lo que se refiere a TCP. Un ACK TCP NO acepta una llamada a send(). Puede anotar la acumulación de 100 de * sus * mensajes. O 3 bytes de tu mensaje. – nos

1

Según el docs, intente llamar al sock.shutdown() antes de llamar a sock.close().

+0

Upvoted para sugerir algo que no había intentado (no mencionan shutdown en el socket.close docstring), pero desafortunadamente esto tampoco tiene ningún efecto. –

+2

shutdown() no logrará nada en un cierre() en este caso. Y aunque lo hiciera, si hay una pequeña demora en la red, el otro extremo no recibirá la solicitud de apagado inmediatamente, la primera llamada a send() seguirá teniendo éxito y usted tendrá el mismo problema otra vez. – nos

Cuestiones relacionadas