# Java socket Programmierung Filme verschicken



## Nesselbrand (2. Jul 2020)

Ich habe mich mal mit Java sockets auseinader gesetzt und wollte nun einen Dateien Server erstellen

Server:

```
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Server {

    public void run_server(){
        try {

            ExecutorService executorService = Executors.newFixedThreadPool(100);
            ServerSocket server = new ServerSocket(1234);
            System.out.println("Waiting for client at port " + server.getLocalPort() + "\n");

            while (true) {
                Socket client = server.accept();
                executorService.execute(new Handler(client));
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        Server s = new Server();
        s.run_server();
    }
}
```


```
import java.io.DataOutputStream;
import java.io.FileInputStream;
import java.net.Socket;

public class Handler implements Runnable {

    private Socket client;

    public Handler(Socket client) {
        this.client = client;
    }

    @Override
    public void run() {
        try {
            System.out.println("client connected: " + client.getInetAddress());
            FileInputStream fin = new FileInputStream("D:\\Musik\\Musik\\Charts\\21.mp3");
            byte[] buffer = new byte[1000000000];
            System.out.println(fin.read());
            fin.read(buffer,0,buffer.length);

            DataOutputStream out = new DataOutputStream(client.getOutputStream());
            out.write(buffer,0,buffer.length);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
```

Der Client:


```
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.Socket;

public class Client implements Runnable {

    public static void main(String[] args) {
        new Thread(new Client()).start();
    }

    @Override
    public void run() {
        try {
            Socket client = new Socket("localhost", 1234);
            DataInputStream in = new DataInputStream(client.getInputStream());

            FileOutputStream fout = new FileOutputStream("D:\\test\\test.mkv");

            System.out.println(in.read());
            byte[] buffer = new byte[1000000000];
            in.read(buffer,0,buffer.length);
            fout.write(buffer,0,buffer.length);

        } catch (IOException e) {
            e.printStackTrace();
        }
    }
```
Ich haeb nun probiert eine .mkv Datei über den Server herunterzuladen. Als ich die neue Datei dann geöffnet habe kam bei dem Film ein Standbild und es lief nicht weiter. Wo ist das Problem?
Schonmal Danke im Vorraus.


----------



## kneitzel (2. Jul 2020)

Vermutlich wurde nur ein Teil übertragen und nicht die ganze Datei.
Du lädst nur das, was schon übertragen wurde und bricht dann ab.


----------



## LimDul (3. Jul 2020)

Das lesen (und dadurch auch das Senden) kommt in der Regel nicht ohne eine Schleife aus.

read liefert dir zurück wie viel wirklich gelesen wurde - das ist maximal so viel, wie du wolltest, kann aber auch weniger sein.
Erst wenn read den Wert -1 zurückliefert, wurde das Ende des Streams erreicht. Dementsprechend musst du in einer Schleife so lange lesen, bis -1 erreicht ist. Und beim senden auch nur so viel senden, wie wirklich gelesen wurde.


----------



## kneitzel (3. Jul 2020)

Wobei das problematisch sein kann, da er nach dem Senden die Verbindung nicht schließt. Dadurch blockiert der read Aufruf und es wird kein -1 zurück gegeben.

Daher entweder einbauen, dass die Verbindung direkt geschlossen wird oder eben ein kleines Protokoll erstellen. Das könnte z.B. sein, dass er erst einen Datensatz übermittelt wie z.B. Dateiname und Größe. Und dann in Ruhe die Daten. Dann weiss der Client, wann der Server mit der Übertragung fertig ist.


----------



## LimDul (3. Jul 2020)

Stimmt, so weit hab ich nicht geschaut. wie man es dreht und wendet, bei einer Netzwerkübertragung reicht in der Regel ein Aufruf von read nicht aus.


----------



## kneitzel (3. Jul 2020)

Nur um es bildlich zu verdeutlichen:

Du orderst ganz viele Ziegelsteine. Diese können aber nicht als Ganzes verschickt werden - sind einfach zu viele. Daher werden die Steine auf einzelnen Paletten verladen und mit vielen Paletten zu Dir gefahren. Der Transporteur hat aber keine großen LKWs (Die machen nur Probleme auf den Straßen... wer will schon Jumbo Packets  ) sondern hat ganz viele Sprinter. Also immer eine Palette in einen Sprinter und los geht es.

Wenn ein Sprinter bei Dir ankommt, lädt er die Palette einfach auf der Straße ab.

Und nun sagst Du: Hol alle Steine, die gerade da sind, rein.
Ist noch keine Palette da, dann wartest Du: Da wird ja schon eine Palette kommen...
Aber sobald Du mindestens eine Palette siehst, packst Du dir alle Paletten auf einmal (Hast halt Spinat gegessen - da nimmst Du auch gerne mal 5 Paletten mit Ziegelsteinen auf einmal  ) und holst die rein.

Von den zig Paletten sind aber erst wenige angekommen. Aber egal - Dir wurde gesagt: alle Paletten rein holen und das hast Du gemacht... Das da eine große Menge weiterer Paletten abgeladen wird, interessiert Dich nicht mehr.

Daher der Vorschlag von @LimDul, dass Du doch regelmäßig nach neuen Paletten schaust. Du musst dann auch nicht alle Paletten, die da sind, auf einmal nehmen. Ein kleiner Puffer reicht aus. Sowohl beim Senden als auch beim Empfangen: Da die Paletten eh. einzelnd versendet werden, brauchst Du keine 1000 Paletten auf einmal nehmen. Da reichen ein paar wenige Paletten aus. Ebenso beim Empfang: Nur eben musst Du halt immer wieder schauen, ob Paletten da sind.

Und Du willst natürlich nicht ewig warten. Du brauchst ein Signal, das Dir sagt: Jetzt kommt nichts mehr.
- Der Fahrer vom letzten Fahrzeug kann Dir klar sagen: Du Arsch, mit dir machen wir keine Geschäfte mehr! Dann ist klar: Da wird jetzt keine Palette mehr kommen. Du weißt aber nicht: Sind alle Paletten da? Evtl. hat da der Fahrer ja nur miese Laune, weil da eine Brücke kaputt gegangen ist und er nun keine Route mehr gibt?
- Du kannst einen Lieferschein haben. Dann weisst Du: Ahh, das ist Palette x von x. Wir sind fertig. Das hat den Vorteil: Der gleiche Lieferservice ist noch stand by. Wen also noch irgend etwas anderes benötigt wird, kann der direkt schnell liefern...

Das erste ist das Schließen der Verbindung. Nur da kannst Du auch nicht sicher sein, dass der Transfer erfolgreich war. Evtl. ist der Server ja auch gestoppt worden oder es gab eine Netzwerk-Unterbrechung ...
Das zweite ist: Du bekommst als erstes einen Lieferschein. Dann weisst Du, wann Du fertig bist.

Aber beim zweiten musst Du aufpassen:. Evtl. kommt ja noch mehr an Daten nach! Lieferwagen werden immer ganz voll gemacht! Also wenn mehrere Sachen gesendet werden, dann sind die ggf. alle im read mit drin.

Also mal einfach als Text dargestellt:
Der Server sendet:
- Hallo Du!\n
- Ich mag Dich sehr!\n

Wenn Du nun mehrfach read() aufrufst, dann bekommst Du mehrere Teile von "Hallo Du!\nIch mag Dich sehr!\n"

Das kann also z.B. sein:
"Hallo D"
"u!\nI"
"ch mag Di"
"ch sehr!\n"

Also gut aufpassen. Wenn Du den "Lieferschein" nutzt, dann pass auf: Du willst z.B. 1000 Bytes lesen - dann nutz auch nur 1000 Bytes!


----------



## Nesselbrand (3. Jul 2020)

👍👍👍
Vielen Dank
Aber wie bekomme ich die größe der Datei Heraus?

Ich habe den Code für den Client jetzt so umgeändert:


```
Socket client = new Socket("localhost", 1234);
            DataInputStream in = new DataInputStream(client.getInputStream());

            FileOutputStream fout = new FileOutputStream("D:\\test\\test.mkv");

            byte[] buffer = new byte[6095462];
            for (int n = in.read(buffer); n > 0; n = in.read(buffer)) {
                fout.write(buffer,0,n);
            }
```
Ich bekomme halt nur noch immer längere Dateien als sie eigentlich sind, da der buffer zu groß ist.
Ich habe zunächst auch den "Lieferschein" weggelassen, da das erstmal ein Projekt zum ausprobieren ist.


----------



## Nesselbrand (3. Jul 2020)

Wenn ich des aber so mache wird kiomischerwiese der Code hinter der for schleife nicht mehr ausgeführt


----------



## kneitzel (3. Jul 2020)

Nesselbrand hat gesagt.:


> Ich bekomme halt nur noch immer längere Dateien als sie eigentlich sind, da der buffer zu groß ist.
> Ich habe zunächst auch den "Lieferschein" weggelassen, da das erstmal ein Projekt zum ausprobieren ist.



Das ist so erst einmal Quatsch (so du den Client meinst), denn Du schreibst nur die Daten, die Du empfangen hast. Der Buffer kann beliebig gross sein.

Du liest ja Daten und bekommst zurück, wie viele Daten gelesen wurden. Und dann schreibst Du nur genau die gelesenen Daten. Den Buffer kannst Du aber durchaus erst einmal kleiner machen.

Aber die Schleife ist so zumindest ungewöhnlich - üblich ist da eher eine while Schleife. (for Schleife ist die übliche Zähl-Schleife und du zählst hier ja nicht. Und du hast Code doppelt: Das in.read hast Du zwei Mal im Code...)


```
byte[] buffer = new byte[6095462];
            int n;
            while ((n = in.read(buffer)) > 0) {
                fout.write(buffer,0,n);
            }
```

Das Problem ist also auf Serverseite:


```
byte[] buffer = new byte[1000000000];
            in.read(buffer,0,buffer.length);
            fout.write(buffer,0,buffer.length);
```
Hier musst du 1:1 so vorgehen wie im Client. Also ruhig mit einem kleinen Buffer in einer Schleife immer lesen, so lange etwas da ist und dann natürlich nicht buffer.length schreiben sondern nur die Anzahl Bytes, die gelesen wurden. Also in.read gibt ja etwas zurück.




Nesselbrand hat gesagt.:


> Wenn ich des aber so mache wird kiomischerwiese der Code hinter der for schleife nicht mehr ausgeführt


Ja, das ist das, was ich auch erwähnt habe: Du musst am Ende natürlich die Verbindung zu machen, damit der Client weiss: Es kommt nichts mehr.

Dazu kannst Du z.B. try-with-resources verwneden um die Instanzen, die AutoClosable implementieren, zu schließen. Das wäre der Socket und der Stream.


----------



## mihe7 (3. Jul 2020)

Mal ein Beispiel:


Spoiler: Handler.java





```
import java.io.FileInputStream;
import java.io.OutputStream;
import java.net.Socket;

public class Handler implements Runnable {

    private Socket client;

    public Handler(Socket client) {
        this.client = client;
    }

    @Override
    public void run() {
        try(Socket client = this.client;
                FileInputStream in = new FileInputStream("21.mp3");
                OutputStream out = client.getOutputStream()) {

            System.out.println("client connected: " + client.getInetAddress());

            byte[] buffer = new byte[3000];
            int n;
            while ((n = in.read(buffer)) != -1) {
                out.write(buffer, 0, n);
                out.flush(); // falls sofort gesendet werden soll
                slowDown();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private void slowDown() {
        try {
            Thread.sleep((int)(Math.random() * 200));
        } catch (InterruptedException ex) {
            // IGNORE
        }
    }
}
```






Spoiler: Client.java





```
import java.io.InputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.Socket;

public class Client implements Runnable {

    public static void main(String[] args) {
        new Thread(new Client()).start();
    }

    @Override
    public void run() {
        try(Socket client = new Socket("localhost", 1234);
                InputStream in = client.getInputStream();
                FileOutputStream out = new FileOutputStream("received.mp3")) {
            System.out.println("Receiving file");

            long count = 0;

            byte[] buffer = new byte[8192];
            int n;
            while ((n = in.read(buffer)) != -1) {
                out.write(buffer, 0, n);
                count += n;
                System.out.printf("received %d bytes\n", count);
            }

        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
```


----------



## Nesselbrand (3. Jul 2020)

Vielen vielen Dank
Habs jetzt verstanden und es hat auch Funktioniert.
Man kann die Verbindung in der Klasse Handler doch einfach mit :

```
client.close();
```
schließen oder?


----------



## kneitzel (3. Jul 2020)

Das ist im Prinzip richtig, aber da das schließen ja immer erfolgen soll, also auch bei Exceptions und so, läuft dies dann auf etwas hinaus, das so aussehen könnte:

```
Socket client;
try {
  client = ....
} finally {
  if (client != null) client.close();
}
```

Daher ist das try-with-resources eingeführt worden. Das ist das, was @mihe7 in seinem Code verwendet hat:

```
try(Socket client = this.client;
                FileInputStream in = new FileInputStream("21.mp3");
                OutputStream out = client.getOutputStream()) {
```
Diese 3 Variablen (client, in und out) werden bei Verlassen des Try Blocks geschlossen.

Siehe dazu auch: https://docs.oracle.com/javase/tutorial/essential/exceptions/tryResourceClose.html


----------



## Nesselbrand (3. Jul 2020)

ok vielen Dank für die Antworten


----------



## mihe7 (3. Jul 2020)

JustNobody hat gesagt.:


> läuft dies dann auf etwas hinaus, das so aussehen könnte:


@Nesselbrand Hierbei treten dann weitere Probleme auf, in dem Fall z. B. dass close() selbst eine Exception auslösen kann. Das wird dann nicht nur richtig hässlich, sondern führt auch zu Code-Duplizierung und man kann leicht etwas falsch machen. Daher: mach Dir das Leben leicht und verwende try-with-Resources. 

flush() und slowDown() habe ich Dir nur zum Ausprobieren reingemacht. Das flush() sorgt dafür, dass über die Leitung geschickt wird, was gerade im Sendepuffer steht (Deine writes werden gepuffert). 

In der Regel ist es besser, auf das flush() in der Schleife zu verzichten. Zum Beispiel werden im Handler ohne das slowDown() und mit flush() Pakete mit 3000 Bytes über die Leitung (loopback Interface, wg. localhost) geschickt. Lässt Du das flush() weg, werden die Pakete zum Teil 64 KiB groß. Es müssen also weniger Pakete für die gleiche Datenmenge versendet werden. Beim Schließen wird automatisch geflushed.

Warum gibt es dann überhaupt flush? Wenn Du ein Protokoll implementierst, bei dem sich Sender und Empfänger "unterhalten", also mehrfach Daten austauschen, bevor die Verbindung geschlossen wird, dann muss sichergestellt werden, dass die jeweiligen Antworten auch wirklich abgesendet werden. Ansonsten kann es passieren, dass die Daten ewig im Puffer stehen bleiben.


----------



## mrBrown (3. Jul 2020)

JustNobody hat gesagt.:


> Aber die Schleife ist so zumindest ungewöhnlich - üblich ist da eher eine while Schleife. (for Schleife ist die übliche Zähl-Schleife und du zählst hier ja nicht. Und du hast Code doppelt: Das in.read hast Du zwei Mal im Code...)


Die meisten sind sich einfach nur zu faul, aus der while- eine for-Schleife zu machen  ich find eine for-schleife trotz des doppelten read schöner: keine Zuweisung innerhalb der Bedingung und der Scope von i ist möglichst klein.


----------



## kneitzel (3. Jul 2020)

Also ich selbst sehe for als Zählschleife an, aber das kann gerne jeder so sehen, wie er möchte. Wenn Dir die for Schleife da gut gefällt, dann bitte. Ich finde diese eher schwer zu lesen.

Wenn es aber einfach nur um das lesen aus einem Stream und schreiben in einen anderen geht, dann würde ich da eh noch ein Refactoring einwerfen:

```
inStream.transferTo(outStream);
```

Das Rad muss man ja nicht ständig neu erfinden. Da kann man ja nutzen, was man so bekommt.


Was mich etwas wundert: Diese "Streams" haben keine "Streams"?
Da könnte man doch ein Stream<byte[]> Sinn machen, oder? So dass man dann etwas hat wie
`inputStream.stream().forEach(this::doSomething)`
oder so....


----------



## mihe7 (3. Jul 2020)

Was die for-Schleife betrifft: da stimme ich @JustNobody zu. Die Intention ist in meinen Augen bei der while-Schleife wesentlich klarer: so lange noch was gelesen werden kann, ... Beim for muss man schon zweimal hinschauen:

```
for (int n; (n = in.read(buffer)) != -1; )
```
Das genügt auch meinen ästhetischen Ansprüchen nicht


----------



## Nesselbrand (3. Jul 2020)

Ok
Jetzt habe ich nur noch ein Problem und zwar will ich eine DefaultMutableTreeNode über die Socketverbindung verschicken.
Wisst ihr rein zuffällig wie das funktioniert?


----------



## mihe7 (4. Jul 2020)

Was haben die Sockets damit zu tun? Du schreibst Daten raus, liest sie wieder ein und erzeugst daraus Objekte.


----------



## Nesselbrand (4. Jul 2020)

ich meine wie kann ich die Daten aus dieser Node herauslesen ?


----------



## Nesselbrand (4. Jul 2020)

Ok hat sich erledigt habs geschafft trotzdem Danke


----------

