Datei zum Server senden per Socket - Geht nur wenn socket nicht geschlossen wird

Hi,

Ich sitze an einem Client-Server Programm, bei dem der Client dem Server eine Datei schicken soll. Hier erstmal testweise ohne GUI, alles geschieht direkt automatisch. Es wird die file.txt auf der Clientseite eingelesen und geschickt.
Das Problem ist, dass im Serverordner zwar dann eine file.txt erstellt wird, diese aber leer ist…
Ich habe das ganze hier bereits auf der Serverseite als “Multi”-Threaded (Multi in “”, weil bisher ja nur 1 Thread erstellt wird) aufgebaut, da ja später dann wenn es funktioniert auch mehrere Dateien gesendet werden sollen.

Das ganze funktioniert aber, sobald ich in Controller.java auf der Clientseite die zeile socket.close(); rausnehme…Aber das kann ja nicht die Lösung sein, denn socket muss ja schließbar sein.

Daraufhin habe ich versucht (in der Controller.java)

		    bos.flush();
		    bos.close();
		    is.close();
		    socket.close();

durch

out.flush();
socket.shutdownInput();
socket.shutdownOutput();
socket.close();

zu ersetzen, aber dann wird wieder keine Datei mit Inhalt empfangen…

Hier mal der Code:

Client:

package controller;

public class Main {
	
	public static void main(String[] args) {
		new Controller();
	}
	
}
package controller;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.net.Socket;
import java.net.UnknownHostException;


public class Controller {

	
	public Controller() {
		try {
			sendeDateiZumServer();							
		} catch (IOException e) {
			e.printStackTrace();
		}  

	}

	
	
	public void sendeDateiZumServer() throws UnknownHostException, IOException {
		Socket socket = null;
	    String host = "localhost";

	    socket = new Socket(host, 5555);	// Verbinde zum Server "localhost:5555"

	    String dateiname = "file.txt";		
	    File file = new File(dateiname);	// liest Datei mit diesem Namen ein, falls nicht existent, wird diese erstellt (was hier sinnfrei waere)
	    
	    //Dateinamen zum Server schicken
	    OutputStream outText = socket.getOutputStream();
	    PrintStream outTextP = new PrintStream(outText);
	    outTextP.println(dateiname);
	    
	    // Dateigroeße ermitteln (in Bytes)
	    long dateigroeße = file.length();
	    if (dateigroeße > Long.MAX_VALUE) {
	        System.out.println("Dateigröße ist zu groß!");		// sollte eigentlich hier nicht passieren
	    }else
	    	System.out.println("Dateigröße: " + dateigroeße);
	    
	    byte[] bytes = new byte[(int) dateigroeße];		// Erstelle ein byte-Array mit der Groeße der Dateigroeße
	    
	    FileInputStream fis = new FileInputStream(file);
	    BufferedInputStream bis = new BufferedInputStream(fis);		// Verkette die eingelesene Datei mit diesem Inputstream
	    
	    BufferedOutputStream out = new BufferedOutputStream(socket.getOutputStream());

	    int count;
	    System.out.println("Fange an zu senden:....");
	    
	    while ((count = bis.read(bytes)) > 0) {			// Wird nur 1x durchlaufen!		
	    	System.out.println("count: " + count);
	        out.write(bytes, 0, count);
	    }
	    System.out.println("Fertig gesendet!");
	    
	    out.flush();
	    
	    out.close();
	    fis.close();
	    bis.close();
	    socket.close();
	}
	
	


}

Server:

import java.io.IOException;


public class Main {
	
	public static void main(String[] args) throws IOException {
		new Server();
	}

}
import java.io.IOException;


public class Main {
	
	public static void main(String[] args) throws IOException {
		new Server();
	}

}
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.Socket;


public class Client implements Runnable{
	
	private Socket socket;
	
	public Client(Socket socket) {
		this.socket = socket;
	}

	@Override
	public void run() {

	    test();
	 
	}

	private void test()  {
		try {

	
		    InputStream is = null;
		    FileOutputStream fos = null;
		    BufferedOutputStream bos = null;
		    int bufferSize = 0;
		

	
		    InputStream outText = socket.getInputStream();		// Hier kommt das an, was der Client sendet
		    
		    //Dateinamen empfangen
		    InputStreamReader outTextI = new InputStreamReader(outText);
		    BufferedReader inTextB = new BufferedReader(outTextI);
		    String dateiname = inTextB.readLine();
		    System.out.println("Dateiname: " + dateiname);
		
		    try {
		        is = socket.getInputStream();			// Ist das nicht gleich outText ?
		
		        bufferSize = socket.getReceiveBufferSize();
		        System.out.println("Buffer size: " + bufferSize);
		    } catch (IOException ex) {
		        System.out.println("Can't get socket input stream. ");
		    }
		
		    try {
		        fos = new FileOutputStream(dateiname);	
		        bos = new BufferedOutputStream(fos);		// Dieser Stream schreibt weiter unten dann die empfangene Datei
		
		    } catch (FileNotFoundException ex) {
		        System.out.println("File not found.");
		    }
		
		    byte[] bytes = new byte[bufferSize];
		
		    int count;
		    
		    int testcount = 0;
		    while ((count = is.read(bytes)) > 0) {			// liest hier die Datei, die empfangen wird (wird mehrmals durchlaufen!)
		        bos.write(bytes, 0, count);					// Schreibt hier die empfangene Datei auf den Server
		        testcount++;
		        System.out.println("testcount: " + testcount);																			// wird niemals ausgegeben -> betritt niemals diese schleife
		    }
	
		    
		    bos.flush();
		    
		    bos.close();
		    is.close();
		    socket.close();
		}catch(IOException e) {
			e.printStackTrace();
		}
	}

}

Das Problem ist folgendes: Dein Client liest unentwegt ankommende Daten. Wenn dann nach dem Sendevorgang der Server die Verbindung abrupt beendet springt der Client in die Fehlerbehandlung und schließt die Streams nicht ordnungsgemäß. Um das Problem quick’n’dirty zu lösen könntest du nach jedem write ein flush ausführen, damit die Daten direkt in die Datei geschrieben.
Etwas sauberer wird das ganze wenn du die Streams im finally-Block des try-catch Blocks schließt. Dieser Block wird dann immer durchlaufen und schließt die Streams ordnungsgemäß.

Falls du es aber wirklich richtig machen willst muss der Server dem Client sagen wann die Datei fertig übertragen wurde. Das macht man entweder über eine festgelegte Zeichenfolge die der Server nach dem Senden schickt, oder besser: Du schickt vor dem Senden der Datei die Größe mit, damit der Client weiß wann er fertig ist.

[QUOTE=EikeB]Das Problem ist folgendes: Dein Client liest unentwegt ankommende Daten. Wenn dann nach dem Sendevorgang der Server die Verbindung abrupt beendet springt der Client in die Fehlerbehandlung und schließt die Streams nicht ordnungsgemäß. Um das Problem quick’n’dirty zu lösen könntest du nach jedem write ein flush ausführen, damit die Daten direkt in die Datei geschrieben.
Etwas sauberer wird das ganze wenn du die Streams im finally-Block des try-catch Blocks schließt. Dieser Block wird dann immer durchlaufen und schließt die Streams ordnungsgemäß.

Falls du es aber wirklich richtig machen willst muss der Server dem Client sagen wann die Datei fertig übertragen wurde. Das macht man entweder über eine festgelegte Zeichenfolge die der Server nach dem Senden schickt, oder besser: Du schickt vor dem Senden der Datei die Größe mit, damit der Client weiß wann er fertig ist.[/QUOTE]

Wenn der Client in die Fehlerbehandlung springt, müsste ich aber doch eine Exception auf der Konsole sehen? Das ist aber nicht der Fall… Also wäre die Variante mit dem finally auch erstmal unsinnsig oder? Weil wenn nirgends eine Exception fliegt, wird auch das Ende des try-Blocks erreicht.

Wenn ich auf beiden Seiten immer ein flush() nach jedem schreiben/senden verwende, funktioniert es immer noch nicht.

Und wieso sollte irgendein Stream geschlossen werden, obwohl dieser gerade noch irgendetwas sendet? Muss ich mir das so vorstellen, dass im letzten Durchgang etwas in den Stream geschickt wird, dieser dann sofort geschlossen wird, obwohl sich der zu sendene Inhalt noch auf dem Weg befindet? Könnte man das nicht einfach testweise mit einem Thread.sleep(5000) beheben (ich sende Dateien im Kilobyte bereich, welche nahezu sofort ankommen)? Oder wird durch das sleep() auch das senden des Inhalts im Stream pausiert?

ouh … nehmen wir den code mal auseinander

  1. hier fehlt irgendwas / ist doppelt gepostet > bitte mal noch den restlichen code posten
  2. hier wurden einfachste regeln missachtet, sowohl beim klassenaufbau im allgemeinen als auch beim umgang mit streams und netzwerk-resourcen
  3. google / sufu irgendwie kaputt ? es gibt ähnliche thema im korrekten sub-forum : http://forum.byte-welt.net/forums/101-Netzwerkprogrammierung

zum code

der “Controller” ist schon mal alleine vom klassen-design völliger mist
du machst alles im konstruktor > schlecht
gehört denn das was du da machst zur konstruktion (als zur erstellung) eines objektes der klasse Controller ? NEIN
korrekt wäre also ein objekt zu erzeugen und auf diese dann die methode anzuwenden
also nach folgendem grund-gerüst

{
	public Controller
	{
	}
	public void sendFileToServer()
	{
	}
}```
und von außen dann korrekt so gecallt
```public class Main
{
	public static void main(String[] args)
	{
		Controller controller=new Controller();
		controller.sendFileToServer();
	}
}```

weiter im Controller

in zeile 39 holst du dir den OutputStream des Sockets und packst diesen in die variable outText
in zeile 40 verpackst du diesen dann in einen PrintStream und steckst das ganze in outTextP

soweit, so gut, problem : ab dieser stelle müsstest du eigentlich konsequent outTextP als "höchste abstraktion" weiter verwenden, was aber ziemlich schlecht ist wenn du binär-daten versenden willst

wenn man also verschieden typen von daten über einen stream senden will macht das sinnvoller weise mit einem DataOutputStream
der zugehörige InputStream auf server-seite heißt entsprechend DataInputStream

welche vorteile bringen Data*Stream ?
recht einfach : sie ermöglichen, ohne das man selbst sowas wie ein protokoll entwickeln muss, verschiedene daten in einer bestimmten reihenfolge zu übertragen

du willst also den namen der datei übertragen
dafür bietet sich DataOutputStream.writeUTF(String) an
warum ? weil writeUTF(String) sowohl das zerlegen des Strings in eine byte-fogle übernimmt als auch die information überträgt wie lang der string ist
man muss also nicht vorher selbst die länge ermitteln und diese dem server mitteilen damit dieser weis wie viel er lesen muss, sondern lässt die einfach durch die Data*Stream machen


jetzt wäre es auch noch clever dem server mitzuteilen wie lang das file ist damit dieser weis wie viele bytes er denn lesen muss um das file vollständig zu erhalten

und da kommt schon der nächste fehler in deinem code

1) ein long kann in java niemals größer sein als Long.MAX_VALUE, sonst würde es diese konstante nicht geben
2) ich bezweifle das irgendein otto-normal-user auch nur ansatzweise die speicher-kapazitäten hat um ein file zu erzeugen dessen länge auch nur in die nähe von Long.MAX_VALUE kommt
das sind nämlich 2^63-1, oder als zahl : 9223372036854775807, also unvorstellbare 8 ExaByte = 8192 PetaByte = 8388608 TeraByte
und mal eben locker 8 Mio TeraByte ausm ärmel zu schütteln fällt selbst großen rechenzentren wie z.b. denen von google gar nicht mal so leicht

ergo : dieses IF kann bedenkenlos rausfliegen


weiter im text : zeile 50
sicher, bei kleinen dateien im kB bereich, vielleicht sogar noch wenige MB, kann man durch aus das gesamte File auf einmal in den RAM lesen
das spart zeit, belastet die platte recht wenig da es ein sequentieller read ist und landet dank DMA direkt im RAM ohne die CPU zu belasten

man sollte es aber dennoch nicht verwenden, vor allem nicht wenn man vorher nicht genau weis wie groß die input-daten sind
sinnvoller ist es einen festen buffer zu nutzen
über eine "standard-größe" kann man sich streiten und es gibt die verschiedensten meinungen darüber im netz, ich persönlich nutze einen buffer der größe 1MB
bei sieht diese zeile also in der regel so aus
```byte[] buffer=new byte[0x100000];```

ob man sich nun die mühe macht und einen FileInputStream in einen BufferedInputStream packt, hmm, kann man sicher machen um system-resourcen zu sparen, kann bei schlechter implementierung aber auch nach hinten losgehen

ansonsten ist der loop selbst ziemlich standard
wichtig : anstatt auf ">0" sollte man lieber dierekt auf "!=-1" prüfen, denn "0" ist ein valides return, nämlich genau dann wenn genau 0 bytes gelesen wurden
-1 allerdings zeigt einen abbruch an, und das wenn entweder bei einem File EOF erreicht wurde oder bei einem Socket-Stream dieser abgebrochen ist / geschlossen wurde

der rest ist klar : letzmalig flushen und dann sauber closen, wobei hier auf grund deiner ir-witzigen verschachtelungen und mehrfach-nutzung das ganze nicht mehr ganz so sauber ist

wenn man deine "Controller"-klasse also mal ordentlich aufräumt kommt z.b. folgendes bei raus

```public class Controller
{
	public Controller() { }
	public void sendFileToServer()
	{
		String host="...";
		int port=12345;
		
		Socket socket=new Socket(host, port);
		DataOutputStream out=new DataOutputStream(socket.getOutputStream());
		
		String fileName="...";
		File file=new File(fileName);
		
		out.writeUTF(fileName);
		out.writeLong(file.length());
		
		FileInputStream in=new FileInputStream(file);
		
		byte[] buffer=new byte[0x100000];
		int i=0;
		
		while((i=in.read(buffer))!=-1)
		{
			out.write(buffer, 0, i);
			out.flush();
		}
		
		in.close();
		out.close();
		socket.close();
	}
}```

mehr ist es im endeffekt nicht
1) verbindung herstellen
2) file-infos an den server senden
3) file-inhalt versenden
4) verbindung wieder sauber trennen



in deiner empfangs-klasse (der name "Client" ist hier schlecht gewählt, besser wäre sowas wie "ClientConnection" oder ähnliches) hast du ähnliche fehler gemacht
ich verzichte daher hier darauf diese noch mal zu nennen und poste dir lieber gleich ein zum obigen code passenden receive-code

```public class ClientConnection
{
	private Socket clientSocket;
	public ClientConnection(Socket clientSocket)
	{
		this.clientSocket=clientSocket;
	}
	public receiveFileFromClient()
	{
		DataInputStream in=new DataInputStream(clientSocket.getInputStream());
		
		String fileName=in.readUTF();
		long length=in.readLong();
		
		File file=new File(fileName);
		FileOutputStream out=new FileOutputStream(file);
		
		byte[] buffer=new byte[0x100000];
		int i=0;
		int l=0;
		
		while((i=in.read(buffer))!=-1)
		{
			out.write(buffer, 0, i);
			out.flush();
			l+=i;
			if(l==(int)length)
				break;
		}
		
		out.close();
		in.close();
		clientSocket.close();
	}
}```

der code dürfte selbsterklärend sein



nur so nebenbei : das ganze ist jetzt nicht getestet (weil es so auch garnicht compilen würde) sondern nur mal frei-hand notiert, sollte aber soweit eigentlich passen

wenn du jetzt noch mehrere files versenden willst musst du nur drauf achten die streams und den socket nicht zu schließen sondern für die gesamte übertragung offen zu halten


bei fragen oder fehlern helf ich gerne weiter

Das ganze wirkt nun deutlich übersichtlicher und schlanker mit deinen Verbesserungsvorschlägen. Das sind ja jetzt wirklich nur noch eine handvoll Zeilen eigentlich.

Vielen dank für deine Hilfe :wink:

Falls noch jemand Intresse an der nun funktionierenden und kompletten Version hat, hier nochmal der gesamte Code:

Client:

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


public class App {
	
	public static void main(String[] args) {
		try {
			
			String host="localhost";
	        int port=5555;
	       
	        Socket socket=new Socket(host, port);
	        DataOutputStream out=new DataOutputStream(socket.getOutputStream());
	       
	        String fileName="file.txt";
	        File file=new File(fileName);
	       
	        out.writeUTF(fileName);
	        out.writeLong(file.length());
	       
	        FileInputStream in=new FileInputStream(file);
	       
	        byte[] buffer=new byte[0x100000];
	        int i=0;
	       
	        while((i=in.read(buffer))!=-1)
	        {
	            out.write(buffer, 0, i);
	            out.flush();
	        }
	       
	        in.close();
	        out.close();
	        socket.close();
	        System.out.println("Datei gesendet.");
        
		}catch(Exception e) {
			e.printStackTrace();
		}
	}

}

Server:


public class App {

	public static void main(String[] args) {
		Server server = new Server();
		server.warteAufClient();
	}

}

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;


public class Server {
	
	private ServerSocket serverSocket;
	private Socket socket = null;
	
	public Server() {
		try {
			serverSocket = new ServerSocket(5555);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	public void warteAufClient() {
		try {
			while(true) {
				socket = serverSocket.accept();
				Thread thread = new Thread(new ClientConnection(socket));
				thread.start();
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

}

import java.io.DataInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.net.Socket;


public class ClientConnection implements Runnable {
	
	private Socket socket;
	
	public ClientConnection(Socket socket) {
		this.socket = socket;
	}

	@Override
	public void run() {
		
		try {
			DataInputStream in=new DataInputStream(socket.getInputStream());
	       
	        String fileName=in.readUTF();
	        long length=in.readLong();
	       
	        File file=new File(fileName);
	        FileOutputStream out=new FileOutputStream(file);
	       
	        byte[] buffer=new byte[0x100000];
	        int i=0;
	        int l=0;
	       
	        while((i=in.read(buffer))!=-1)
	        {
	            out.write(buffer, 0, i);
	            out.flush();
	            l+=i;
	            if(l==(int)length)
	                break;
	        }
	       
	        out.close();
	        in.close();
	        socket.close();
	        System.out.println("Datei empfangen.");
	       
		}catch(Exception e) {
			e.printStackTrace();
		}
	}

}