2009-07-23 1 views
14

Ich richte einen Server mit einem ServerSocket, eine Verbindung mit einem Client-Rechner herstellen. Sie sind direkt über einen Switch vernetzt und die Ping-Zeit ist < 1ms.Java TCP-Socket: Datenübertragung ist langsam

Jetzt versuche ich eine Menge Daten vom Client auf den Server durch den Ausgangsstrom des Sockets zu schieben. Es dauert 23 Minuten, um 0,6 GB zu übertragen. Ich kann eine viel größere Datei in Sekunden per scp schieben.

Irgendeine Idee, was ich falsch machen könnte? Ich bin im Grunde nur looping und WriteInt auf dem Sockel aufrufen. Das Geschwindigkeitsproblem spielt keine Rolle, woher die Daten kommen, selbst wenn ich nur eine konstante Ganzzahl sende und nicht von der Festplatte lese.

Ich habe versucht, die Sende-und Empfangspuffer auf beiden Seiten auf 4 MB, keine Würfel. Ich benutze einen gepufferten Stream für den Leser und den Schreiber, keine Würfel.

Fehle ich etwas?

EDIT: Code

Hier ist, wo ich die Steckdose machen ist

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())); 

Hier, wo ich die serverseitige Verbindung tun:

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(); 
     } 
    } 

Okay, hier ist, wo ich bin Versand über ~ 0,6 GB Daten.

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); 
     } 
    } 

Es scheint ziemlich einfach so weit ...

+3

Bitte Postleitzahl für Client und Server eingeben. –

+0

Ja, du verpasst definitiv etwas. Nicht sicher was aber. Sie sollten Snippets des Server- und Client-Codes veröffentlichen. –

+3

Welchen Wert haben RECEIVE_BUFFER_SIZE und SEND_BUFFER_SIZE? Ich habe starken Verdacht, dass Sie tatsächlich ein Paket für alle 4 Bytes senden ... –

Antwort

0

Sie sollten einen guten Paket-Sniffer herunterladen. Ich bin ein großer Fan von WireShark persönlich und ich benutze es jedes Mal, wenn ich einige Socket-Programmierung tun. Denken Sie daran, dass der Client und der Server auf verschiedenen Systemen ausgeführt werden müssen, um Pakete abzurufen.

6

Vielleicht sollten Sie versuchen, Ihre Daten in Chunks (Frames) zu senden, anstatt jedes Byte separat zu schreiben. Und richten Sie Ihre Frames mit der TCP-Paketgröße für die beste Leistung aus.

+1

Diese kleine Größe jedes Schreibens ist die Wurzel des Problems. Da die Verbindung relativ schnell ist, wird bei jedem Schreiben einer ganzen Zahl ein Paket gesendet. Dieses Paket wird in ein IP-Paket verpackt und der TCP-Overhead wird anfallen. Senden von Paketen über 1K auf einmal wird Yings gut beschleunigen. –

+2

Es gibt keine "TCP-Paketgröße". – EJP

2

Können Sie versuchen, dies über Loopback zu tun, sollte es dann die Daten in Sekunden übertragen.

Wenn es Minuten dauert, ist etwas mit Ihrer Anwendung nicht in Ordnung. Wenn Daten nur langsam über das Internet gesendet werden, könnte es sich um eine Netzwerkverbindung handeln, die langsam ist.

Meine Vermutung ist, dass Sie ein 10 Mb/s Netzwerk zwischen Ihrem Client und Ihrem Server haben und deshalb ist Ihre Übertragung langsam. Wenn dies der Fall ist, versuchen Sie einen DeflatoutOutputStream und einen InflatorInputStream für Ihre Verbindung zu verwenden.

2

Wie implementieren Sie das Empfangsende? Bitte posten Sie auch Ihren Empfangscode.

Da TCP ein zuverlässiges Protokoll ist, wird es Schritte unternehmen, um sicherzustellen, dass der Client alle vom Absender gesendeten Daten empfangen kann. Dies bedeutet, dass, wenn Ihr Client die Daten nicht rechtzeitig aus dem Datenempfangspuffer abrufen kann, die sendende Seite einfach aufhört, mehr Daten zu senden, bis der Client die Chance hat, alle Bytes im Empfangspuffer zu lesen.

Wenn Ihre empfangende Seite Daten byteweise liest, wird Ihr Absender wahrscheinlich viel Zeit darauf verwenden, auf die Löschung des Empfangspuffers zu warten, daher die langen Übertragungszeiten.Ich schlage vor, Ihren Empfangscode zu ändern, so viele Bytes wie möglich in jeder Leseoperation zu lesen. Sehen Sie, ob das Ihr Problem löst.

-1

Wie wird Ihre Heap-Größe festgelegt? Ich hatte kürzlich ein ähnliches Problem mit der Socket-Übertragung von großen Datenmengen und nur durch Betrachten JConsole erkannte ich, dass die Anwendung die meiste Zeit damit verbrachte, vollständige GCs zu machen.

Versuchen -Xmx1g

+0

Darf ich sagen, dass es mich * wirklich * ärgert, wenn Leute eine vernünftige Antwort ablehnen, ohne irgendeinen Grund in den Kommentaren zu lassen. Da ich genau dieses Problem hatte, ist dies ein vollkommen vernünftiger Vorschlag! –

+0

Sie müssen einen ziemlich merkwürdigen Code gehabt haben. Es ist nicht nötig, große Mengen an Heap zu verwenden, um große Datenmengen zu übertragen. – EJP

0

Dinge versuchen:

  • die CPU bei 100%, während die Daten gesendet werden? Wenn ja, benutze visualvm und führe eine CPU Profilerstellung durch, um zu sehen, wo die Zeit verbracht wird
  • Benutze einen SocketChannel von java.nio - diese sind in der Regel schneller, da sie nativer IO einfacher verwenden können - natürlich hilft das nur, wenn deine Operation ist CPU gebunden
  • Wenn es nicht CPU-gebunden ist, ist auf Netzwerkebene etwas schief gelaufen. Verwenden Sie einen Paket-Sniffer, um dies zu analysieren.
10

Hey, ich dachte, ich würde für jeden weitermachen, der daran interessiert war.

Hier ist die bizarre Moral der Geschichte:

NEVER USE Datainputstream/Dataoutputstream und Steckdosen !!

Wenn ich den Socket in einem BufferedOutputStream/BufferedInputStream wickeln, ist das Leben toll. Roh zu schreiben ist in Ordnung.

Aber wickeln Sie die Buchse in einem Datainputstream/Dataoutputstream oder sogar Dataoutputstream (BufferedOutputStream (sock.getOutputStream())) haben, ist extrem langsam.

Eine Erklärung dafür wäre sehr interessant für mich. Aber nachdem wir alles ausgetauscht haben, ist es soweit. Probieren Sie es selbst, wenn Sie mir nicht glauben.

Danke für die schnelle Hilfe, obwohl.

+2

DataInputStream puffert nicht. Wenn Sie eine 1 oder eine andere sehr niedrige Anzahl von Bytes in einen nicht gepufferten Stream schreiben, sinkt die Leistung. Sie rufen auch get/setReceiveBufferSize auf. Das ist etwas, was Sie nur tun, wenn Sie wissen, dass Sie schlauer sind als der TCP-Stack. – nos

+0

DataXXXStreams ruft auch das Standard-Serialisierungsschema auf, von dem bekannt ist, dass es sehr langsam ist. Sie sollten sie auf jeden Fall für große Transfers vermeiden. – markbernard

+1

Die Geschwindigkeit des Schreibens in einen 'DataOutputStream' ist ** identisch * mit der Geschwindigkeit des Schreibens in den zugrundeliegenden' OutputStream', abzüglich des Overheads von * einem * Methodenaufruf. In ähnlicher Weise ist die Geschwindigkeit des Lesens von einem 'DataInputStream' identisch mit der Geschwindigkeit des Lesens von dem zugrundeliegenden 'InputStream' mit dem gleichen Vorbehalt. @Markbernard 'DataXXXStreams' haben genau * nichts * mit 'dem Standard-Serialisierungsschema' zu tun. – EJP

25

Sie tun nicht wollen einzelne Bytes schreiben, wenn Sie große Datenmengen übertragen.

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(); 
    } 
} 

Dies kopiert 1 GiB Daten kurz über 19 Sekunden auf meinem Rechner. Der Schlüssel hier ist die Verwendung der Methoden InputStream.read und OutputStream.write, die ein Byte-Array als Parameter akzeptieren. Die Größe des Puffers ist nicht wirklich wichtig, es sollte nur ein bisschen größer sein als, sagen wir mal 5. Experimentieren Sie mit BUFFER_SIZE oben, um zu sehen, wie es die Geschwindigkeit beeinflusst, aber denken Sie auch daran, dass es wahrscheinlich für jede Maschine anders ist dieses Programm auf. 64 KiB scheinen ein guter Kompromiss zu sein.

+0

Awesome-- Vielen Dank. Sehen Sie sich meinen Kommentar zu ignasi35 darüber an, was mit dem Wrapping der Streams eines Sockets in DataxxxxStream falsch sein könnte. Ich habe keine Ahnung, warum das so langsam ist, besonders weil Übertragungen in der Größenordnung von Sekunden sind, sogar mit nur einem 4-Byte-Puffer, um die ganzen Zahlen gegen DataxxxxxStream.yyyyInt() zu schieben, die (hoffentlich) die 4-Byte-Pufferung dahinter tun sollte die Szenen, aber anscheinend macht es etwas total verrückt statt –

+0

Hast du das 1GiB auf ** localhost ** oder über das Internet übertragen? –

+0

Geschwindigkeit ist immer noch sehr langsam, wenn Daten zwischen zwei verschiedenen Maschinen (nicht LOCAL HOST) übertragen werden, selbst wenn Sie Ihren Code verwenden. –

1

@Erik: mit DataXxxputStream ist nicht das Problem hier. Problem ist, dass Sie Daten in zu kleinen Teilen gesendet haben. Die Verwendung eines Puffers löste Ihr Problem, denn selbst Sie würden Stück für Stück schreiben, der Puffer würde das Problem lösen. Bombes Lösung ist viel schöner, generischer und schneller.

+0

sogar das Umbrechen des Ausgangsdatenstroms des Sockets in einem BufferedOutputStream half jedoch nicht. Sehen wir uns das an: int -> DataOutputStream -> Socket Ausgabe-Stream. Ein int (4 Bytes) geht hinein, wird in einzelne Bytes (wahrscheinlich als ein Array) umgewandelt und wird an den Socket gesendet. Das ist extrem langsam. 23 Minuten für 0.6Gb. Jetzt machen wir: int -> 4-Byte-Array -> Ausgangsstrom des Sockels 0.6Gb in ~ 3 Sekunden. Was könnte DataOutputStream tun, das ist so anders als das, was ich dort mache? Was könnte es tun, das ist komplizierter als ein int in ein Array zu verwandeln und es dem Stream zuzuführen? –

+0

Whoops, sieht aus wie StackOverflow isst Zeilenumbrüche. das ist nicht so formatiert, wie ich es möchte, aber ich hoffe, du bekommst das Wesentliche. –

-1

NUtZeN Byte Puffer für das Senden der Daten

2

Da ich auf dieser Seite noch nicht kommentieren kann, muss ich Antwort schreiben hier @Erik.

Das Problem ist, dass DataOutputStream nicht zwischenspeichert. Das ganze Stream-Ding in Java basiert auf Dekorationsdesign. So könnten Sie schreiben

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

Es wird den ursprünglichen Strom in einem BufferedOutputStream wickeln, die effizienter ist, die dann in ein Dataoutputstream gewickelt, die zusätzliche nette Features wie writeInt bietet(), writeLong() und so weiter .

0

Ich verwendete PrintWriter, um Daten zu senden. Ich entfernte das und schickte Daten mit BufferedOutputStream.send (String.getBytes()) und bekam ungefähr 10x schnelleres Senden.