# Mp3s Streamen - Client-Server-Anwendung



## Phenix (9. Jul 2009)

Hi Leute,

ich habe mich mal spasseshalber mit Netzwerkprogrammierung befasst. ICh habe mir als Ziel gesetzt, erst einmal nur eine vordefinierte Mp3-Datei von einem Server an einen Client zu Streamen, sodass es beim Client auf der Festplatte gespeichert wird.

Allerdings habe ich ein Problem bei dem verschachteln von Streams. Welchen sollte ich dafür am Besten nehmen und wie muss ich das schachteln?


```
/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package server;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 *
 * @author Phenix
 */
public class SocketHandler implements Runnable {

    private Socket socket = null;

    public SocketHandler(Socket socket) {
        this.socket = socket;
    }

    public void run() {
        InputStream in = null;
        OutputStream out = null;
        


        try {
            FileInputStream fileIn=new FileInputStream(new File("c:\\Musik\\fff.mp3"));

            in = socket.getInputStream();
            out = socket.getOutputStream();
            ObjectInputStream objIn=new ObjectInputStream(in);
            ObjectOutputStream objOut=new ObjectOutputStream(out);

            byte[] buffer=new byte[1024*1000];
            if(objIn.readUTF().equals("handshake")){
                fileIn.read(buffer);
                objOut.write(buffer);
                objOut.flush();
            }
        } catch (IOException ex) {
            Logger.getLogger(SocketHandler.class.getName()).log(Level.SEVERE, null, ex);
        } finally {
            try {
                in.close();
                out.close();
                socket.close();
            } catch (IOException ex) {
                Logger.getLogger(SocketHandler.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
    }
}
```

Geändert:

Ich habe da jetzt mal rumgebastelt. Also er sollte jetzt 1mb des Liedes in den Ausgabestrom schreiben, damit der Client damit was machen kann. Ist das soweit schonmal richtig?

Die ClientSeite sieht bis jetzt folgendermaßen aus:


```
public void actionPerformed(ActionEvent e) {
        try {
            //SocketStröme holen und kapseln
            Socket serversocket = new Socket("localhost", 10100);
            out=serversocket.getOutputStream();
            in=serversocket.getInputStream();
            objOut=new ObjectOutputStream(out);
            objIn=new ObjectInputStream(in);

            //Suchanfrage zu Server schicken
            objOut.writeUTF(SEARCHFIELD.getText());
            objOut.flush();


            objIn.read(new byte[1024*1000]);
            STATUS.setText("Song received!");
            //System.out.println(objIn.readUTF());

        } catch (UnknownHostException ex) {
            STATUS.setText("Unknown Host!");
        } catch (IOException ex) {
            STATUS.setText("IOException! Error");
        }
    }
```
Die Meldung "Song received" wird ausgegeben. Also denke ich doch mal, dass das 1MB schonmal ankommt oder? Wie muss ich jetzt weiter machen, damit der Client das auf der HDD speichern kann?


----------



## tuxedo (9. Jul 2009)

So wie du die File eingelesen hast kannst du sie auch auf die Platte schreiben...

Ist das gleiche in grün, nur eben umgekehrt.

Allerdings solltest du beim lesen drauf achten wieviel du liest. read() liest nicht bis der komplette buffer voll ist. Demnach solltest du auch nur so viel rausschreiben wie du gelesen hast. Das glt sowohl für's aus der Datei und Senden, sowie das lesem vom Stream und speichern in die neue Datei.

-Alex


----------



## Phenix (10. Jul 2009)

ok, danke schonmal.
Irgendwie muss ich dem aber ja sagen, welche bytes der auf die Festplatte speichern soll. Das mit der größe ist natürlich richtig, das änder ich später, wenn erstmal überhaupt das speichern funktioniert ;-)
Jetzt legt er eine Datei auf der Festplatte an, die auch 1mb groß ist, die is aber denke ich mal nur mit sinnlosen bits gefüllt. Der Ansatz ist folgender:


```
byte[] buffer=new byte[1024*1000];
            in.read(buffer);
            FileOutputStream fileOut=new FileOutputStream(new File("c:\\hallo.mp3"));
            fileOut.write(buffer);
```
in ist dabei der inputstream vom socket. Ich denke mal, dass die Übertragung vom Server zum Client schonmal funktioniert.


----------



## tuxedo (10. Jul 2009)

Ja. Das sollte funktionieren. Mit dem einen Problem mit der Anzahl der gelesenen Bytes.

Aktuell erstellst du einen Buffer mit 1024*1000 bytes und versucht das auch so zu lesen.

Wenn du aber mal den return-value von in.read() anschaust, wirst du feststellen, dass er weit weniger als 1024*1000 bytes gelesen hat.

Da du das bisher ignorierst, wird deine Datei immer 1MB groß sein, aber nur die ersten bytes der Datei werden "sinnvoll" gefüllt sein. Der Rest ist entweder 0x00 oder 0xFF (weiß nicht mehr was default initialisiert wird).

Du brauchst dazu noch ein Protokoll... Auf der Seite auf der du die Datei sendest weißt du ja wie groß die Datei ist. Schick die größe einfach als long-Wert (8byte) vorraus. Dann weiß der Server: die ersten 8 bytes beschrieben die Größe der Datei.

Auf Senderseite schreibst du immer häppchenweise die Daten in den Stream (auch hier: erst schauen wieviel aus der Datei gelesen werden konnte, und dann genau so viel in den Netzwerkstream schreiben).

Auf der Empfängerseite liest du dann häppchenweise die Daten. Je nachdem wieviel auf einmal gelesen werden konnte, schreibst du das dann in die Ziel-Datei. 

Beachtest du die gelesene Größe NICHT, wirst du immer Murks raus bekommen.

- Alex


----------



## Phenix (10. Jul 2009)

ok. Besten Dank, sehr gut erklärt.

Wenn ich jetzt aber testweise mal die Anzahl der Bytes auf beiden Seiten verkleinere, sagen wir mal 300kb oder so dann müsste doch ein Teil des Liedes schon abspielfähig sein oder nicht? Ich kann das FIle nicht öffnen.

Was ich auch nicht verstehe:

Woher weiss der Outputstream, dass er die angekommenen bytes schreiben soll? Also ich meine, man sagt dem der soll ne bestimmte Anzahl an bytes schreiben, aber eigentlich ja nicht, welche. Es wäre ja theoretisch möglich dass es mehrere Streams gibt, über die bytes reinkommen. Weiss nich genau wie ich das ausdrücken soll.


----------



## tuxedo (10. Jul 2009)

Selbst 300kbyte sind etwas viel. Typischerweise sind solche buffer <10k ... 

Du kommst also so oder so nicht drum rum die Anzahl der gelesenen bytes mit einfließen zu lassen. Versteh auch nicht warum du dich so dagegen wehrst. Das ist ne Arbeit von <1min und braucht max. 2-5 Zeilen Code zusätzlich. 

>> Woher weiss der Outputstream, dass er die angekommenen bytes schreiben soll?

Ein Outputstream hat exakt ein Ziel. Wenn du da jetzt ein byte[] mit 100 bytes reinsteckst, wird er dir 100bytes an dieses Ziel schreiben.

Könnte mir vorstellen du meinst die Sache mit: Ich will 3 Dateien über einen Stream schicken... 

In diesem Fall: Ein Protokoll ausdenken mit dessen Hilfe Client und Server die Dateien ausseinander halten können.

So, ich geh jetzt ins Wochenende und damit in meinem 1 wöchigen Urlaub 

Gruß
Alex


----------



## Phenix (10. Jul 2009)

ok danke. Schönen Urlaub noch. 

Vielleicht kann mir ja indes jemand anders helfen. Ich habe die Größe nun berücksichtigt(allerdings als fixen Wert), waren etwas über 3mb (deswegen weiss ich nich, warum der Puffer zu groß gewesen sein soll aber naja). Die Datei wurde angelegt, die Größe stimmte, konnte sie aber dennoch nicht abspielen. Jemand ne Idee wo der Fehler liegt?
Nochmal der Code:


```
import java.awt.Color;
import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.net.UnknownHostException;
import javax.swing.JComponent;
import javax.swing.JFrame;

/**
 *
 * @author Phenix
 */
public class GUI extends JComponent implements ActionListener, Names{


    ObjectOutputStream objOut;
    InputStream in;
    OutputStream out;

    public void init(){
        //Fenster mit Panel wird initialisiert
        FRAME.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        FRAME.setLayout(null);
        FRAME.setResizable(false);
        FRAME.setSize(300, 200);
        FRAME.setVisible(true);
        PANEL.setLayout(null);
        PANEL.setSize(300, 200);
        PANEL.setVisible(true);
        PANEL.setBackground(Color.WHITE);
        FRAME.add(PANEL);

        //Komponenten
        SEARCHFIELD.setBounds(5,5,200,20);
        SEARCHBUTTON.setBounds(5,30,70,20);
        SEARCHBUTTON.setFont(new Font("Bart",Font.BOLD,11));
        SEARCHBUTTON.addActionListener(this);
        STATUS.setBounds(5,50,100,20);
        STATUS.setFont(new Font("Bart",Font.BOLD,12));
        STATUS.setForeground(Color.RED);
        PANEL.add(SEARCHFIELD);
        PANEL.add(SEARCHBUTTON);
        PANEL.add(STATUS);
    }

    public void actionPerformed(ActionEvent e) {
        try {
            //SocketStröme holen und kapseln
            Socket serversocket = new Socket("localhost", 10100);
            out=serversocket.getOutputStream();
            in=serversocket.getInputStream();
            objOut=new ObjectOutputStream(out);

            //Suchanfrage zu Server schicken
            objOut.writeUTF(SEARCHFIELD.getText());
            objOut.flush();

            byte[] buffer=new byte[3836157];
            in.read(buffer);
            FileOutputStream fileOut=new FileOutputStream(new File("c:\\hallo.mp3"));
            fileOut.write(buffer);
            //System.out.println(objIn.readUTF());

        } catch (UnknownHostException ex) {
            STATUS.setText("Unknown Host!");
        } catch (IOException ex) {
            STATUS.setText("IOException! Error");
        }
    }
}
```


und Server:


```
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 *
 * @author Phenix
 */
public class SocketHandler implements Runnable {

    private Socket socket = null;

    public SocketHandler(Socket socket) {
        this.socket = socket;
    }

    public void run() {
        InputStream in = null;
        OutputStream out = null;
        
        try {
            File f=new File("c:\\Musik\\fff.mp3");
            FileInputStream fileIn=new FileInputStream(f);

            in = socket.getInputStream();
            out = socket.getOutputStream();
            ObjectInputStream objIn=new ObjectInputStream(in);
            ObjectOutputStream objOut=new ObjectOutputStream(out);

            byte[] buffer=new byte[3836157];
            if(objIn.readUTF().equals("handshake")){
                fileIn.read(buffer);
                objOut.write(buffer);
                objOut.flush();
            }
        } catch (IOException ex) {
            Logger.getLogger(SocketHandler.class.getName()).log(Level.SEVERE, null, ex);
        } finally {
            try {
                in.close();
                out.close();
                socket.close();
            } catch (IOException ex) {
                Logger.getLogger(SocketHandler.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
    }
}
```


----------



## Phenix (10. Jul 2009)

Könnte das daran liegen, dass ich die Datei nicht serialisiert habe? Und wenn ja, wie mache ich das am besten? Benutze ich einen ObjectOuptutStream und die Methode writeObject()?


----------



## Phenix (12. Jul 2009)

Ich habe mal getestet und herausgefunden, dass der Stream auf der Serverseite die richtige Anzahl an Bytes liest. Auf der ClientSeite werden aber immer nur 4Bytes gelesen. Keiner ne Idee warum?

Server

```
try {
            File f=new File("c:\\Musik\\a.mp3");
            FileInputStream fileIn=new FileInputStream(f);

            in = socket.getInputStream();
            out = socket.getOutputStream();
            ObjectInputStream objIn=new ObjectInputStream(in);
            ObjectOutputStream objOut=new ObjectOutputStream(out);

            byte[] buffer=new byte[1197999];
            if(objIn.readUTF().equals("handshake")){
                fileIn.read(buffer);
                objOut.write(buffer);
                objOut.flush();
            }
```

Client

```
try {
            //SocketStröme holen und kapseln
            Socket serversocket = new Socket("localhost", 10100);
            out=serversocket.getOutputStream();
            in=serversocket.getInputStream();
            objOut=new ObjectOutputStream(out);

            //Suchanfrage zu Server schicken
            objOut.writeUTF(SEARCHFIELD.getText());
            objOut.flush();

            byte[] buffer=new byte[1197999];
            System.out.println(in.read(buffer));
            FileOutputStream fileOut=new FileOutputStream(new File("c:\\a.mp3"));
            fileOut.write(buffer);
```


----------



## madboy (12. Jul 2009)

Ich habe zwar nicht alles hier gelesen, aber warum baust du eine GUI, ohne dass das Versenden und Empfangen von Daten klappt?
Immer Schritt für Schritt, dann lässt sich eventuellen Problemen besser auf den Grund gehen.
Mein Vorschlag: Erstelle einen Client, der nix anderes tut, als die vom Server empfangenen Daten auf die Festplatte zu schreiben.
Erstelle einen Server, der auf den Client wartet und so bald sich dieser verbindet, eine Datei (immer die selbe, ohne dynamische Eingaben oder sonst was)  zum Client schickt.
So bald das klappt, baue ein, dass der Client dem Server mitteilt, welche Datei gesendet werden soll.
So bald das klappt, baue eine GUI drum herum.
Fürs Erste würde ich auch keine Threads, mehrere Klassen usw. machen bis du beim GUI-Schritt angelangt bist.


----------



## Phenix (12. Jul 2009)

@madboy

An sich hast du natürlich recht, das man erst backend und dann GUI machen sollte. Allerdings stört diese hierbei nicht (Ich habe das gemacht, da ich bisher immer nur ohne GUI (außer Applets) geproggt habe und das einfach mal ausprobieren wollte). Ich weiss, dass die Übertragung mit Zeichenketten wunderbar funktioniert, allerdings nicht mit den mp3s. Entweder ist der Fehler, dass die nicht richtig an den Client gesendet werden, oder der Client macht beim lesen was falsch. Wie gesagt, er liest immer 4 bytes und stopft dann die Festplattendatei mit "nul" voll.


----------



## madboy (12. Jul 2009)

Versuche mal, im Client so zu lesen:

```
byte[] buffer=new byte[1024];
FileOutputStream fileOut=new FileOutputStream(new File("c:\\a.mp3"));
for ( int len; (len = in.read(buffer)) != –1; ) 
      fileOut.write( buffer, 0, len ); 
}
fileOut.close();
```
Damit liest und schreibst du nur zu empfangenen Bytes und nicht das ganze Array, das evtl. nur zur Hälfte (oder in deinem Fall 4 Byte) gefüllte.

Edit: das, was ich geschrieben habe, ohne GUI und Schnickschnack zu testen, hilft normalerweise nicht nur dem, der programmiert sondern auch denen, die helfen wollen. Bei deinem Problem interessiert mich als Helfendem nicht, was für Buttons etc. du hast. Ich muss nur mehr Code lesen und schauen, wo das Problem anfängt usw. Am liebsten ist mir immer ein kleinstmögliches Beispiel, an dem ich das Problem sehe und nachvollziehen kann


----------



## Phenix (12. Jul 2009)

Besten Dank auch, hat funktioniert :applaus:. Habe die Puffergröße noch kleiner gemacht und das funktioniert jetzt gut. Allerdings wundert mich das, dass das ein Problem war, weil etwas über 1Mb ja eigentlich Problemlos in den Speicher passen sollte oder nicht!?

Bevor ich ein neues Topic aufmache: WEiss jemand, wie ich bei Netbeans mein Programm als eine ausführbare Datei (Jar/exe) speichern kann? Danke

Edit:
Und noch eine Frage: Kann man über einen Strom immer nur eine Nachricht/Datei schicken? Und wenn man einen Strom schliesst, schliesst man auch den Socket? 
Habe nämlich folgendes Problem. Es soll zuerst ein Handshake gemacht werden, woraufhin der Server zum CLient ein "welcome" schickt. Das funktioniert auch wunderbar. Dann soll wieder eine Testnachricht an den Server geschickt werden, woraufhin er dann (eigentlich den Song Streamen soll. Wegen Debugging aber ne Konsolenausgabe macht) reagieren soll. Habe mit einem Sniffer rausgefunden, dass die Nachricht vom CLient richtig an den Server gesendet wird. Allerdings scheint die MEthode readUTF() in der if-Bedingung zu blockieren(Server). Ne Idee?

Client:

```
if (e.getSource() == SEARCHBUTTON) {
                //Suchanfrage zu Server schicken
                objOut.writeUTF(SEARCHFIELD.getText());
                objOut.flush();
//                ObjectOutputStream temp=new ObjectOutputStream(serversocket.getOutputStream());
//                temp.writeUTF("a");
//                temp.flush();

                byte[] buffer = new byte[8];
                fileOut = new FileOutputStream(new File("c:\\a.mp3"));
                for (int len = 0; len != -1; len = in.read(buffer)) {
                    fileOut.write(buffer, 0, len);
                    fileOut.flush();
                    System.out.println(len);
                }//end for
            }//end if
                   <--------Hier folgt der Handshake. Der wird zuerst gesendet---------->
            if (e.getSource() == CONNECT) {
                objOut.writeUTF("handshake");
                objOut.flush();
                ObjectInputStream objIn = new ObjectInputStream(serversocket.getInputStream());
                if (objIn.readUTF().equals("welcome")) {
                    STATUS.setForeground(Color.GREEN);
                    STATUS.setText("Connected to " + serversocket.getInetAddress() + ":" + serversocket.getPort());
                }

            }//end if
```

Server:

```
File f=new File("c:\\Musik\\a.mp3");
            FileInputStream fileIn=new FileInputStream(f);

            in = socket.getInputStream();
            out = socket.getOutputStream();
            ObjectInputStream objIn=new ObjectInputStream(in);
<--------AUch hier wieder: erst der Handshake. Dann soll sweeeet gesendet werden-->
            //Welcome Client
            if(objIn.readUTF().equals("handshake")){
                ObjectOutputStream handshakeout=new ObjectOutputStream(out);
                handshakeout.writeUTF("welcome");
                handshakeout.flush();
//                handshakeout.close();
            }


            //Stream song
            byte[] buffer=new byte[8];
            //Hier blockert er. Server hat keinen input.
            ObjectInputStream i=new ObjectInputStream(in);
            if(i.readUTF().equals("sweeeeeeeeeeeeeeeeet")){
                  System.out.println("it works!!!!");
            .....
```


----------



## Phenix (14. Jul 2009)

Ok. Das Problem habe ich jetzt gelöst. Die zwei read()-Methoden haben sich gegenseitig blockiert. Nun habe ich noch das Problem, dass mir nach der Übertragung des Liedes die GUI des Clients abschmiert. Muss ich da wie in einem Applet ne repaint()-Methode aufrufen, oder woran kann das liegen?


----------



## Phenix (15. Jul 2009)

keiner ne Idee?


----------



## madboy (15. Jul 2009)

Wenn du mit "abschmiert" meinst, dass die GUI während der Übertragung "einfriert", dann lies hier (sogar mit Fortschrittsbalken  ): 
http://www.java-forum.org/java-faq-beitraege/7395-progressbars-algorithmen-und-multithreading.html


----------



## Phenix (15. Jul 2009)

Ich meinte zwar was anderes, trotzdem kann ich das sehr gut gebrauchen, Danke. Habe es auch jetzt hinbekommen. Ich denke das Topic kann geclosed werden *freu*


----------



## tuxedo (20. Jul 2009)

Phenix hat gesagt.:


> Besten Dank auch, hat funktioniert :applaus:. Habe die Puffergröße noch kleiner gemacht und das funktioniert jetzt gut. Allerdings wundert mich das, dass das ein Problem war, weil etwas über 1Mb ja eigentlich Problemlos in den Speicher passen sollte oder nicht!?



*back from vacation*

Das ist das, was ich dir die ganze Zeit versuvht habe beizubringen:

Du kannst nicht beliebig viele Bytes, die an sich ja vielleicht noch in den Speicher passe, komplett am Stück versenden. Das darunterliegende OS und die Netzwerkkarte haben Puffer die weit kleiner sind (i.d.R. 8kb oder so). Das führt zu einer Fragmentierung deiner Daten. Und das muss man eben berücksichtigen. Im übrigen ist das nicht nur mit dem Netzwerk so. Bein lesen und schreiben von Daten auf der Platte ist das genau so.

- Alex


----------

