2009-07-23 32 views
14

Configuré un servidor con ServerSocket, me conecté a él con una máquina cliente. Están conectados en red directamente a través de un interruptor y el tiempo de ping es < 1ms.Java Socket TCP: la transferencia de datos es lenta

Ahora, intento enviar una gran cantidad de datos del cliente al servidor a través del flujo de salida del socket. Tarda 23 minutos para transferir 0.6Gb. Puedo enviar un archivo mucho más grande en segundos a través de scp.

¿Alguna idea de lo que podría estar haciendo mal? Básicamente estoy bucleando y llamando a writeInt en el socket. El problema de velocidad no importa de dónde provienen los datos, incluso si solo estoy enviando un entero constante y no leyendo desde el disco.

Intenté configurar el búfer de envío y recepción en ambos lados a 4Mb, sin dados. Utilizo una secuencia amortiguada para el lector y el escritor, sin dados.

¿Echo de menos algo?

EDIT: código

Aquí es donde hago la toma

System.out.println("Connecting to " + hostname); 

    serverAddr = InetAddress.getByName(hostname); 

    // connect and wait for port assignment 
    Socket initialSock = new Socket(); 
    initialSock.connect(new InetSocketAddress(serverAddr, LDAMaster.LDA_MASTER_PORT)); 
    int newPort = LDAHelper.readConnectionForwardPacket(new DataInputStream(initialSock.getInputStream())); 
    initialSock.close(); 
    initialSock = null; 

    System.out.println("Forwarded to " + newPort); 

    // got my new port, connect to it 
    sock = new Socket(); 
    sock.setReceiveBufferSize(RECEIVE_BUFFER_SIZE); 
    sock.setSendBufferSize(SEND_BUFFER_SIZE); 
    sock.connect(new InetSocketAddress(serverAddr, newPort)); 

    System.out.println("Connected to " + hostname + ":" + newPort + " with buffers snd=" + sock.getSendBufferSize() + " rcv=" + sock.getReceiveBufferSize()); 

    // get the MD5s 
    try { 
     byte[] dataMd5 = LDAHelper.md5File(dataFile), 
       indexMd5 = LDAHelper.md5File(indexFile); 

     long freeSpace = 90210; // ** TODO: actually set this ** 

     output = new DataOutputStream(new BufferedOutputStream(sock.getOutputStream())); 
     input = new DataInputStream(new BufferedInputStream(sock.getInputStream())); 

Aquí es donde hago la conexión del lado del servidor:

ServerSocket servSock = new ServerSocket(); 
    servSock.setSoTimeout(SO_TIMEOUT); 
    servSock.setReuseAddress(true); 
    servSock.bind(new InetSocketAddress(LDA_MASTER_PORT)); 

    int currPort = LDA_START_PORT; 

    while (true) { 
     try { 
      Socket conn = servSock.accept(); 
      System.out.println("Got a connection. Sending them to port " + currPort); 
      clients.add(new MasterClientCommunicator(this, currPort)); 
      clients.get(clients.size()-1).start(); 

      Thread.sleep(500); 

      LDAHelper.sendConnectionForwardPacket(new DataOutputStream(conn.getOutputStream()), currPort); 

      currPort++; 
     } catch (SocketTimeoutException e) { 
      System.out.println("Done listening. Dispatching instructions."); 
      break; 
     } 
     catch (IOException e) { 
      e.printStackTrace(); 
     } 
     catch (Exception e) { 
      e.printStackTrace(); 
     } 
    } 

Muy bien, aquí es donde estoy de buques de más ~ 0.6 Gb de datos.

public static void sendTermDeltaPacket(DataOutputStream out, TIntIntHashMap[] termDelta) throws IOException { 
    long bytesTransferred = 0, numZeros = 0; 

    long start = System.currentTimeMillis(); 

    out.write(PACKET_TERM_DELTA); // header  
    out.flush(); 
    for (int z=0; z < termDelta.length; z++) { 
     out.writeInt(termDelta[z].size()); // # of elements for each term 
     bytesTransferred += 4; 
    } 

    for (int z=0; z < termDelta.length; z++) { 
     for (int i=0; i < termDelta[z].size(); i++) { 
      out.writeInt(1); 
      out.writeInt(1); 
     } 
    } 

Parece bastante sencillo hasta ahora ...

+3

Por favor, publique el código para el cliente y el servidor. –

+0

Sí, definitivamente te estás perdiendo algo. No estoy seguro de qué. Debería publicar fragmentos del código del servidor y del cliente. –

+3

¿Cuál es el valor de RECEIVE_BUFFER_SIZE y SEND_BUFFER_SIZE? Tengo una fuerte sospecha de que realmente envíe un paquete por cada 4 bytes ... –

Respuesta

0

deberá bajar un buen analizador de paquetes. Soy un gran admirador de WireShark personalmente y termino usándolo cada vez que hago alguna programación de socket. Solo tenga en cuenta que debe tener el cliente y el servidor ejecutándose en diferentes sistemas para poder recoger los paquetes.

6

Quizás deba intentar enviar sus datos en fragmentos (marcos) en lugar de escribir cada byte por separado. Y alinee sus marcos con el tamaño de paquete TCP para obtener el mejor rendimiento.

+1

Este pequeño tamaño de cada escritura es la raíz del problema. Como el enlace es relativamente rápido, enviará un paquete antes de cada escritura de un entero. Este paquete se envolverá en un paquete IP y se incurrirá en la sobrecarga de TCP. El envío de paquetes de aproximadamente 1K a la vez acelerará mucho mejor a los yhings. –

+2

No existe el "tamaño de paquete TCP". – EJP

2

Puede intentar hacer esto a través de un bucle de retroceso, luego debe transferir los datos en segundo.

Si tarda unos minutos, hay algún problema con su aplicación. Si el envío de datos a través de Internet es lento, podría ser que tu enlace de red sea lento.

Supongo que usted tiene una red de 10 Mb/s entre su cliente y su servidor y esta es la razón por la cual su transferencia se realiza lentamente. Si este es el caso, intente utilizar un DeflaoutOutputStream y un InflatorInputStream para su conexión.

2

¿Cómo está implementando el extremo receptor? Por favor, publique su código de recepción también.

Dado que TCP es un protocolo confiable, tomará medidas para asegurarse de que el cliente pueda recibir todos los datos enviados por el remitente. Esto significa que si su cliente no puede sacar los datos del búfer de recepción de datos a tiempo, entonces el lado de envío simplemente dejará de enviar más datos hasta que el cliente tenga la oportunidad de leer todos los bytes en el búfer de recepción.

Si su lado receptor está leyendo datos de un byte a la vez, entonces su remitente probablemente pasará mucho tiempo esperando que el búfer receptor se borre, de ahí los largos tiempos de transferencia.Sugeriré cambiando su código de recepción para leer tantos bytes como sea posible en cada operación de lectura. Vea si eso resolverá su problema.

-1

¿Cómo se establece el tamaño de su pila? Hace poco tuve un problema similar con la transferencia del socket de grandes cantidades de datos y con solo mirar JConsole me di cuenta de que la aplicación estaba pasando la mayor parte del tiempo haciendo GC completos.

Trate -Xmx1g

+0

Puedo decir que * realmente * me molesta cuando las personas rechazan una respuesta razonable sin dejar ningún motivo en la sección de comentarios. Dado que tenía este problema exacto, ¡esta es una sugerencia perfectamente razonable! –

+0

Debes tener un código bastante extraño. No es necesario utilizar grandes cantidades de pila para transferir grandes cantidades de datos. – EJP

0

cosas para probar:

  • es la CPU al 100% mientras se están enviando los datos? Si es así, use visualvm y haga un perfil de CPU para ver dónde se gasta el tiempo
  • Use un SocketChannel de java.nio, estos son generalmente más rápidos ya que pueden usar IO nativo más fácilmente, por supuesto, esto solo ayuda si su operación es CPU enlazado
  • Si no está vinculado a la CPU, algo va mal en el nivel de la red. Use un sniffer de paquetes para analizar esto.
10

Oye, pensé que haría un seguimiento para cualquiera que estuviera interesado.

Ésta es la extraña moral de la historia:

NUNCA USO DataInputStream/DataOutputStream y tomas de corriente !!

Si envuelvo el socket en un BufferedOutputStream/BufferedInputStream, la vida es excelente. Escribirle en crudo está bien.

Pero ajuste el socket en un DataInputStream/DataOutputStream, o incluso tenga DataOutputStream (BufferedOutputStream (sock.getOutputStream())) EXTREMADAMENTE LENTO.

Una explicación para eso sería realmente interesante para mí. Pero después de intercambiar todo dentro y fuera, esto es lo que pasa. Inténtalo tú mismo si no me crees.

Gracias por la gran ayuda.

+2

DataInputStream no almacena en búfer. Si escribe decir 1 u otro número muy bajo de bytes en un flujo no amortiguado, el rendimiento disminuirá. También llama a get/setReceiveBufferSize. Eso es algo que solo hace cuando SABE que es más inteligente que la pila TCP – nos

+0

DataXXXStreams también invoca el esquema de serialización predeterminado que se sabe que es muy lento. Definitivamente debe evitarlos para grandes transferencias. – markbernard

+1

La velocidad de escritura en un 'DataOutputStream' es ** idéntica * a la velocidad de escritura en el' OutputStream' subyacente, menos la sobrecarga de * one * method call. De forma similar, la velocidad de lectura de un 'DataInputStream' es idéntica a la velocidad de lectura del' InputStream' subyacente, con la misma advertencia. @markbernard 'DataXXXStreams' tiene exactamente * nada * que ver con 'el esquema de serialización predeterminado'. – EJP

25

Hace no desea escribir bytes individuales cuando transfiere grandes cantidades de datos.

import java.io.FileInputStream; 
import java.io.IOException; 
import java.io.InputStream; 
import java.io.OutputStream; 
import java.net.ServerSocket; 
import java.net.Socket; 

public class Transfer { 

    public static void main(String[] args) { 
     final String largeFile = "/home/dr/test.dat"; // REPLACE 
     final int BUFFER_SIZE = 65536; 
     new Thread(new Runnable() { 
      public void run() { 
       try { 
        ServerSocket serverSocket = new ServerSocket(12345); 
        Socket clientSocket = serverSocket.accept(); 
        long startTime = System.currentTimeMillis(); 
        byte[] buffer = new byte[BUFFER_SIZE]; 
        int read; 
        int totalRead = 0; 
        InputStream clientInputStream = clientSocket.getInputStream(); 
        while ((read = clientInputStream.read(buffer)) != -1) { 
         totalRead += read; 
        } 
        long endTime = System.currentTimeMillis(); 
        System.out.println(totalRead + " bytes read in " + (endTime - startTime) + " ms."); 
       } catch (IOException e) { 
       } 
      } 
     }).start(); 
     new Thread(new Runnable() { 
      public void run() { 
       try { 
        Thread.sleep(1000); 
        Socket socket = new Socket("localhost", 12345); 
        FileInputStream fileInputStream = new FileInputStream(largeFile); 
        OutputStream socketOutputStream = socket.getOutputStream(); 
        long startTime = System.currentTimeMillis(); 
        byte[] buffer = new byte[BUFFER_SIZE]; 
        int read; 
        int readTotal = 0; 
        while ((read = fileInputStream.read(buffer)) != -1) { 
         socketOutputStream.write(buffer, 0, read); 
         readTotal += read; 
        } 
        socketOutputStream.close(); 
        fileInputStream.close(); 
        socket.close(); 
        long endTime = System.currentTimeMillis(); 
        System.out.println(readTotal + " bytes written in " + (endTime - startTime) + " ms."); 
       } catch (Exception e) { 
       } 
      } 
     }).start(); 
    } 
} 

Esto copia 1 GiB de datos en poco más de 19 segundos en mi máquina. La clave aquí es usar los métodos InputStream.read y OutputStream.write que aceptan una matriz de bytes como parámetro. El tamaño del búfer no es realmente importante, debería ser un poco más grande que, digamos, 5. Experimenta con BUFFER_SIZE arriba para ver cómo afecta la velocidad pero también ten en cuenta que probablemente sea diferente para cada máquina que estés ejecutando este programa en. 64 KiB parece ser un buen compromiso.

+0

Impresionante-- muchas gracias. Vea mi comentario a ignasi35 a continuación sobre lo que podría ser incorrecto al envolver las transmisiones de un socket en DataxxxxStream. No tengo idea de por qué es tan lento, especialmente porque las transferencias son del orden de segundos incluso usando solo un buffer de 4 bytes para empujar los enteros vs DataxxxxxStream.yyyyInt(), que debería (con suerte) estar haciendo ese buffering de 4 bytes detrás las escenas, pero aparentemente está haciendo algo totalmente loco en su lugar –

+0

¿Has transferido ese 1GiB a ** localhost ** oa través de Internet? –

+0

La velocidad es aún muy lenta cuando los datos se transfieren entre dos máquinas diferentes (no HOST LOCAL) incluso utilizando su código. –

1

@Erik: el uso de DataXxxputStream no es el problema aquí. El problema es que estaba enviando datos en trozos demasiado pequeños. Usar un búfer resolvió su problema porque incluso usted escribiría bit a bit, el búfer resolvería el problema. La solución de Bombe es mucho más agradable, genérica y más rápida.

+0

incluso envolviendo el flujo de salida del socket en un BufferedOutputStream no ayudó. Veamos esto: int -> DataOutputStream -> flujo de salida del socket. Entra un int (4 bytes), se convierte en bytes individuales (probablemente como una matriz) y se empuja al socket. Esto es EXTREMADAMENTE lento. 23 min para 0.6Gb. Ahora lo hacemos: int -> matriz de 4 bytes -> secuencia de salida del socket 0.6Gb in ~ 3 seconds. ¿Qué podría hacer DataOutputStream que sea tan diferente de lo que estoy haciendo allí? ¿Qué podría hacer eso es más complicado que convertir un int en una matriz y alimentarlo a la transmisión? –

+0

whoops, parece que StackOverflow come nuevas líneas. eso no está formateado como me gustaría, pero espero que entiendas la esencia. –

-1

Utilizar una solución tampón de bytes para el envío de los datos

2

Dado que todavía no puedo comentar en este sitio, tengo que escribir respuesta a @Erik aquí.

El problema es que DataOutputStream no almacena en búfer. Todo Stream-thing en Java se basa en el patrón de diseño de los decoradores. Así se podría escribir

DataOutputStream out = new DataOutputStream(new BufferedOutputStream(socket.getOutputStream()));

Será envolver el flujo original en un BufferedOutputStream que es más eficiente, que se envuelve entonces en un DataOutputStream que ofrece buenas características adicionales como writeInt(), writeLong(), etc. .

0

Estaba usando PrintWriter para enviar datos. Lo eliminé y envié datos con BufferedOutputStream.send (String.getBytes()) y recibí un envío 10 veces más rápido.

Cuestiones relacionadas