problemas de socket en Python

Tengo un servidor que está escrito en C, y quiero escribir un cliente en Python. El cliente de Python enviará una cadena “enviar algunos_archivo” cuando quiera enviar un archivo, seguido del contenido del archivo y la cadena “finalizar algún archivo”. Aquí está mi código de cliente:

file = sys.argv[1] host = sys.argv[2] port = int(sys.argv[3]) sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM) sock.connect((host,port)) send_str = "send %s" % file end_str = "end %s" % file sock.send(send_str) sock.send("\n") sock.send(open(file).read()) sock.send("\n") sock.send(end_str) sock.send("\n") 

El problema es este:

  • el servidor recibe la cadena “send some_file” de un recv

  • en el segundo recv, el contenido del archivo y las cadenas de “archivo final” se envían juntas

En el código del servidor, el tamaño del búfer es 4096. Noté por primera vez este error al intentar enviar un archivo que es inferior a 4096k. ¿Cómo puedo asegurarme de que el servidor reciba las cadenas de forma independiente?

Con la progtwigción de socket, incluso si realiza 2 envíos independientes, no significa que la otra parte los reciba como 2 rec. Independientes.

Una solución simple que funciona tanto para cadenas como para datos binarios es: Primero envíe el número de bytes en el mensaje, luego envíe el mensaje.

Esto es lo que debe hacer para cada mensaje, ya sea un archivo o una cadena:

Lado del remitente:

  • Envíe 4 bytes que contengan el número de bytes en el siguiente envío
  • Enviar los datos reales

Lado del receptor:

  • Desde el lado del receptor, haga un bucle que bloquee en una lectura de 4 bytes.
  • Luego haga un bloque en una lectura para la cantidad de caracteres especificados en los 4 bytes anteriores para obtener los datos.

Junto con el encabezado de 4 bytes de longitud que mencioné anteriormente, también puede agregar un encabezado de tipo de comando de tamaño constante (de nuevo entero) que describa lo que se incluye en la siguiente rec.

También podría considerar el uso de un protocolo como HTTP que ya hace mucho trabajo por usted y tiene bibliotecas de envoltorios agradables.

Hay dos formas mucho más simples en las que puedo resolver esto. Ambos implican algunos cambios en los comportamientos tanto del cliente como del servidor.

Lo primero es usar relleno. Digamos que está enviando un archivo. Lo que harías es leer el archivo, codificarlo en un formato más simple como Base64, luego enviar suficientes caracteres de espacio para completar el rest del ‘fragmento’ de 4096 bytes. Lo que harías es algo como esto:

 from cStringIO import StringIO import base64 import socket import sys CHUNK_SIZE = 4096 # bytes # Extract the socket data from the file arguments filename = sys.argv[1] host = sys.argv[2] port = int(sys.argv[3]) # Make the socket sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM) sock.connect((host,port)) # Prepare the message to send send_str = "send %s" % (filename,) end_str = "end %s" % (filename,) data = open(filename).read() encoded_data = base64.b64encode(data) encoded_fp = StringIO(encoded_data) sock.send(send_str + '\n') chunk = encoded_fp.read(CHUNK_SIZE) while chunk: sock.send(chunk) if len(chunk) < CHUNK_SIZE: sock.send(' ' * (CHUNK_SIZE - len(chunk))) chunk = encoded_fp.read(CHUNK_SIZE) sock.send('\n' + end_str + '\n') 

Este ejemplo parece un poco más complicado, pero asegurará que el servidor pueda seguir leyendo datos en fragmentos de 4096 bytes, y todo lo que tiene que hacer es Base64-decodificar los datos en el otro extremo (una biblioteca de C para la cual está disponible aquí El decodificador Base64 ignora los espacios adicionales y el formato puede manejar archivos binarios y de texto (¿qué pasaría, por ejemplo, si un archivo contuviera la línea de "nombre de archivo final"? Confundiría al servidor).

El otro enfoque es prefijar el envío del archivo con la longitud del archivo. Entonces, por ejemplo, en lugar de send filename de envío, podría decir que send 4192 filename de send 4192 filename para especificar que la longitud del archivo es de 4192 bytes. El cliente tendría que comstackr el send_str basándose en la longitud del archivo (como se lee en la variable de data en el código anterior), y no tendría que usar la encoding Base64 ya que el servidor no intentaría interpretar ninguna syntax de end filename aparezca en El cuerpo del archivo enviado. Esto es lo que sucede en HTTP; Content-length encabezado HTTP de Content-length se utiliza para especificar la duración de los datos enviados. Un ejemplo de cliente podría verse así:

 import socket import sys # Extract the socket data from the file arguments filename = sys.argv[1] host = sys.argv[2] port = int(sys.argv[3]) # Make the socket sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM) sock.connect((host,port)) # Prepare the message to send data = open(filename).read() send_str = "send %d %s" % (len(data), filename) end_str = "end %s" % (filename,) sock.send(send_str + '\n') sock.send(data) sock.send('\n' + end_str + '\n') 

De cualquier manera, tendrá que hacer cambios tanto en el servidor como en el cliente. Al final, probablemente sería más fácil implementar un servidor HTTP rudimentario (o obtener uno que ya se haya implementado) en C, ya que parece que eso es lo que está haciendo aquí. La solución de encoding / relleno es rápida pero crea una gran cantidad de datos enviados de forma redundante (ya que Base64 generalmente causa un aumento del 33% en la cantidad de datos enviados), la solución de prefijo de longitud también es fácil desde el lado del cliente pero puede ser más difícil el servidor.

Posiblemente utilizando

 sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) 

ayudará a enviar cada paquete como desee ya que esto deshabilita el algoritmo de Nagle , ya que la mayoría de las stacks TCP usan esto para unir varios paquetes de datos de pequeño tamaño juntos (y está activado por defecto, creo)

Los datos TCP / IP se almacenan en búfer, más o menos al azar.

Es sólo un “flujo” de bytes. Si lo desea, puede leerlo como si estuviera delimitado por ‘\ n’ caracteres. Sin embargo, no se divide en trozos significativos; ni puede ser Debe ser un flujo continuo de bytes.

¿Cómo lo estás leyendo en C? ¿Estás leyendo hasta un ‘\ n’? ¿O simplemente estás leyendo todo en el búfer?

Si estás leyendo todo en el búfer, deberías ver las líneas en búfer más o menos al azar.

Sin embargo, si lee hasta un ‘\ n’, verá cada línea una por una.

Si desea que esto realmente funcione, debe leer http://www.w3.org/Protocols/rfc959/ . Esto muestra cómo transferir archivos de manera simple y confiable: use dos sockets. Uno para los comandos, el otro para los datos.