Java socket Programmierung Filme verschicken

Diskutiere Java socket Programmierung Filme verschicken im Netzwerkprogrammierung Bereich.
N

Nesselbrand

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

Server:
Java:
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();
    }
}
Code:
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:

Code:
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.
 
J

JustNobody

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

LimDul

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.
 
J

JustNobody

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.
 
L

LimDul

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.
 
J

JustNobody

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!
 
N

Nesselbrand

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

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

Code:
            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.
 
N

Nesselbrand

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

JustNobody

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...)

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:

Code:
            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.


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

mihe7

Mal ein Beispiel:
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
        }
    }
}

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

Nesselbrand

Vielen vielen Dank
Habs jetzt verstanden und es hat auch Funktioniert.
Man kann die Verbindung in der Klasse Handler doch einfach mit :
Code:
client.close();
schließen oder?
 
J

JustNobody

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:
Code:
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:
Code:
        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
 
mihe7

mihe7

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

mrBrown

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.
 
J

JustNobody

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:
Code:
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

mihe7

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:
Java:
for (int n; (n = in.read(buffer)) != -1; )
Das genügt auch meinen ästhetischen Ansprüchen nicht :p
 
N

Nesselbrand

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

mihe7

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

Nesselbrand

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

Java socket Programmierung Filme verschicken

Passende Stellenanzeigen aus deiner Region:
Anzeige

Neue Themen

Anzeige

Anzeige
Oben