Objekt verschicken und anschließend Datei

Problembeschreibung:

  1. Fall: Der Server schickt an den Client eine Bilddatei, der Client empfängt die Datei und speichert sie ab (Funktioniert)
  2. Fall: Der Server schickt an den Client ein Objekt der Klasse DataInfo, der Client empfängt das Objekt und gibt den Inhalt
    eines Attributs auf der Konsole aus (Funktioniert)
  3. Fall: Der Server schickt zuerst ein Objekt der Klasse DataInfo und anschließend eine Bilddatei an den Client, der Client empfängt
    das Objekt und gibt den Inhalt eines Attributs auf der Konsole aus (Funktioniert) anschließend empfängt der Client noch das Bild und
    speichert es ab (Funktioniert nicht).
    Beim Client ist zwar dieselbe Anzahl an Bytes angekommen, wie vom Server verschickt wurden, aber wenn ich versuche das Bild
    anschließend zu öffnen dann wird mir gesagt das Bild kann nicht geöffnet werden, da es möglicherweise beschädigt wurde.
    Das angekommene Bild ist aber genauso groß wie das vom Server versendete. Wieso wird mir hierbei das Bild also nicht angzeigt?
    Der 3. Fall ist ja einfach nur eine Kombination aus dem 1. Fall und dem 2. Fall. Die ersten beiden Fälle funktionieren ja. Was muss
    ich am 3. Fall ändern, damit er auch funktioniert?

Code 1. Fall
[spoiler]

#################################### 1. Fall #######################################################

Server Code - Server verschickt eine Bild Datei an den Client (Funktioniert)

public class MyMain
{
	public static void main(String[] args) 
	{
		System.out.println("ServerNetworkPicPC");
		new MyServerThread().start();
	}
}
public class MyServerThread extends Thread
{
	private int port;
	private String filename = "C:/Users/pj2/Documents/Tulips.png";
	
	public MyServerThread()
	{
		this.port = 1024;
	}
	
	
	public void run()
	{
		try 
		{
			ServerSocket serverSocket = new ServerSocket(this.port);
			OutputStream outputStream;
			
			while(true)
			{
				Socket clientSocket = serverSocket.accept();
				clientSocket.setKeepAlive(true);
				clientSocket.setSoTimeout(0);
			
				outputStream = clientSocket.getOutputStream();
			
				File file = new File(filename);
				FileInputStream fileInputStream = new FileInputStream(file);
			
				byte[] buffer = new byte[16*1024];
				int len = 0;
			
				while ((len = fileInputStream.read(buffer)) > 0)
				{
					outputStream.write(buffer, 0, len);
					System.out.println(len);
				}
				outputStream.flush();
	        
				System.out.println("nach WHILE");
				fileInputStream.close();
				outputStream.close();
			}
		}
		catch (IOException e) 
		{
			e.printStackTrace();
		}
	}
}

Client Code - Client erhält eine Bild Datei vom Server (Funktioniert)

public class MyMain 
{
	public static void main(String[] args) 
	{
		 System.out.println("ClientNetworkPicPC Version 1");
		 new MyClientThread().start();
	}
}
public class MyClientThread extends Thread
{
	private String ip;
	private int port;
	private String filename;
	
	public MyClientThread()
	{
		this.ip = "131.234.70.81";
		this.port = 1024;
		this.filename = "C:/Users/pj2/Documents/test.png";
	}
	
	
	public void run()
	{
		try
		{
			Socket socket = new Socket(this.ip, this.port);
			socket.setKeepAlive(true);
			socket.setSoTimeout(0);
			File file = new File(this.filename);
			
			InputStream inputStream = socket.getInputStream();
			FileOutputStream fileOutputStream = new FileOutputStream(file);
	
			byte[] buffer = new byte[16*1024];
			int len = 0;
				
			while ((len = inputStream.read(buffer)) > 0)
			{
		       	fileOutputStream.write(buffer, 0, len);
		        System.out.println(len);
		    }
		    System.out.println("nach WHILE");
		        
			fileOutputStream.flush();
			fileOutputStream.close();
		} 
		catch (UnknownHostException e)
		{
			e.printStackTrace();
		} 
		catch (IOException e) 
		{
			e.printStackTrace();
		}
	}
}

[/spoiler]

Code 2. Fall
[spoiler]
######################## 2. Fall #########################################

Server Code - Server verschickt ein Objekt der Klasse DataInfo an den Client (Funktioniert)

public class MyMain
{
	public static void main(String[] args) 
	{
		System.out.println("ServerNetworkPicPC");
		new MyServerThread().start();
	}
}
public class MyServerThread extends Thread
{
	private int port;
	private String filename = "C:/Users/pj2/Documents/Tulips.png";
	
	public MyServerThread()
	{
		this.port = 1024;
	}
	
	
	public void run()
	{
		try 
		{
			ServerSocket serverSocket = new ServerSocket(this.port);
			serverSocket.setSoTimeout(0);
			OutputStream outputStream;
			ObjectOutputStream objectOutputStream;
			
			while(true)
			{
				Socket clientSocket = serverSocket.accept();
				clientSocket.setKeepAlive(true);
				clientSocket.setSoTimeout(0);
			
				outputStream = clientSocket.getOutputStream();
				objectOutputStream = new ObjectOutputStream(outputStream);
			
				File file = new File(filename);
			
				DataInfo dataInfo = new DataInfo(file.length());
				objectOutputStream.writeObject(dataInfo);
				objectOutputStream.reset();
				
				System.out.println("DataInfo Objekt versendet");
			}
		}
		catch (IOException e) 
		{
			e.printStackTrace();
		}		
	}
}

Client Code -Client erhält ein Objekt der Klasse DataInfo vom Server (Funktioniert)

public class MyMain 
{
	public static void main(String[] args) 
	{
		 System.out.println("ClientNetworkPicPC Version 1");
		 MyClientThread myClientThread = new MyClientThread();
		 myClientThread.start();
	}
}
public class MyClientThread extends Thread
{
	private String ip;
	private int port;
	private String filename;
	
	public MyClientThread()
	{
		this.ip = "131.234.70.81";
		this.port = 1024;
		this.filename = "C:/Users/pj2/Documents/test.png";
	}
	
	
	public void run()
	{
		try
		{
			Socket socket = new Socket(this.ip, this.port);
			socket.setKeepAlive(true);
			socket.setSoTimeout(0);
			
			InputStream inputStream = socket.getInputStream();
			ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
			
			Object object = objectInputStream.readObject();	
			
			if(object instanceof DataInfo)
			{
				DataInfo dataInfo = (DataInfo) object;
				long dataSize = dataInfo.getDataSize();
				System.out.println("dataSize: " + dataSize);
			}
		} 
		catch (UnknownHostException e)
		{
			e.printStackTrace();
		} 
		catch (IOException e) 
		{
			e.printStackTrace();
		}
		catch (ClassNotFoundException e)
		{
			e.printStackTrace();
		}
	}
}

[/spoiler]

Code 3. Fall
[spoiler]
################################### 3. Fall ###########################################################

Server Code - Server verschickt zuerst ein Objekt der Klasse DataInfo und anschließend eine Bild Datei an den Client (Funktioniert nicht).

public class MyMain
{
	public static void main(String[] args) 
	{
		System.out.println("ServerNetworkPicPC");
		new MyServerThread().start();
	}
}
public class MyServerThread extends Thread
{
	private int port;
	private String filename = "C:/Users/pj2/Documents/Tulips.png";
	
	public MyServerThread()
	{
		this.port = 1024;
	}
	
	
	public void run()
	{
		try 
		{
			ServerSocket serverSocket = new ServerSocket(this.port);
			OutputStream outputStream;
			ObjectOutputStream objectOutputStream;
			
			while(true)
			{
				Socket clientSocket = serverSocket.accept();
				clientSocket.setKeepAlive(true);
				clientSocket.setSoTimeout(0);
			
				outputStream = clientSocket.getOutputStream();
				objectOutputStream = new ObjectOutputStream(outputStream);
			
				File file = new File(filename);
				FileInputStream fileInputStream = new FileInputStream(file);
				
				DataInfo dataInfo = new DataInfo(file.length());
				objectOutputStream.writeObject(dataInfo);
				objectOutputStream.reset();
				objectOutputStream.flush();
				
				System.out.println("DataInfo Objekt versendet");
			
				byte[] buffer = new byte[16*1024];
				int len = 0;
			
				while ((len = fileInputStream.read(buffer)) > 0)
				{
					outputStream.write(buffer, 0, len);
					System.out.println(len);
				}
				outputStream.flush();
	        
				System.out.println("nach WHILE");
				fileInputStream.close();
			}
		}
		catch (IOException e) 
		{
			e.printStackTrace();
		}
	}
}

Client Code - Client erhält zuerst ein Objekt der Klasse DataInfo und anschließend eine Bild Datei vom Server (Funktioniert nicht)

public class MyMain 
{
	public static void main(String[] args) 
	{
		 System.out.println("ClientNetworkPicPC Version 1");
		 new MyClientThread().start();
	}
}
public class MyClientThread extends Thread
{
	private String ip;
	private int port;
	private String filename;
	
	public MyClientThread()
	{
		this.ip = "131.234.70.81";
		this.port = 1024;
		this.filename = "C:/Users/pj2/Documents/test.png";
	}
	
	
	public void run()
	{
		try
		{
			Socket socket = new Socket(this.ip, this.port);
			socket.setKeepAlive(true);
			socket.setSoTimeout(0);
			File file = new File(this.filename);
			
			InputStream inputStream = socket.getInputStream();
			FileOutputStream fileOutputStream = new FileOutputStream(file);
			
			ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);	
			Object object = objectInputStream.readObject();	
			
			if(object instanceof DataInfo)
			{
				DataInfo dataInfo = (DataInfo) object;
				long dataSize = dataInfo.getDataSize();
				System.out.println("dataSize: " + dataSize);
			}
	
			byte[] buffer = new byte[16*1024];
			int len = 0;
				
			while ((len = inputStream.read(buffer)) > 0)
			{
		       	fileOutputStream.write(buffer, 0, len);
		        System.out.println(len);
		    }
		    System.out.println("nach WHILE");
		        
			fileOutputStream.flush();
			fileOutputStream.close();
		} 
		catch (UnknownHostException e)
		{
			e.printStackTrace();
		} 
		catch (IOException e) 
		{
			e.printStackTrace();
		} 
		catch (ClassNotFoundException e) 
		{
			e.printStackTrace();
		}
	}
}

############################ Versendetes Objekt ###############################################################

public class DataInfo implements Serializable
{
	private static final long serialVersionUID = 4893811447239313830L;
	
	private long dataSize;
	
	public DataInfo()
	{
		
	}
	
	public DataInfo(long dataSize)
	{
		this.dataSize = dataSize;
	}
	
	
	public void setDataSize(long dataSize)
	{
		this.dataSize = dataSize;
	}
	
	public long getDataSize()
	{
		return this.dataSize;
	}
}

[/spoiler]

mit der Länge an Bytes ist es nicht getan, untersuche ganz konkret, was ankommt, welche Bytes genau,
zur Vereinfachung vielleicht mit einer Datei mit nur einem Byte anfangen, dann mehrere,
evtl. Buffer auf kleine Größe setzen, z.B. 3 bei insgesamt 7 Bytes, um Fehler dazu zu finden

zur Vereinfachung in diesen Testfällen auch möglichst einfache Byte-Werte 0, 1, 2, 3 …

Problem könnte sein dass der ObjectStream puffert/ buffert, paar Bytes der Datei abgreift,
oder andersrum zusätzliches hineinschreibt, was in die Datei kommt,
falls aber das geloggte ‘len’ stimmt, dann durchaus interessant


generell ist nicht zu empfehlen, Streams zu mischen,
wenn du einen ObjectStream verwendest, dann auch nur diesen, damit kannst du auch ein byte[] als ein Objekt verschicken,
ich denke ausreichend bis normal effizient, Aufteilen brauchst du selber nicht,
falls doch buffer, wegen Einlesen der Datei, dann beim Senden wahrscheinlich hinsichtlich reset() aufpassen

Ich glaube in einem anderen Thread haben wir Dich schon einmal darauf hingewiesen, dass ObjectStreams etwas komplexer sind. (Immerhin können diese ja Objekte streamen)

Zunächst einmal möchte ich mich SlaterBs Anmerkung anschließen “generell ist nicht zu empfehlen, Streams zu mischen”.
In dem Fall würde ich gar nicht mit ObjectStreams arbeiten.
Entweder ein eigenes Protokoll definieren und einfach Bytes verschicken (z.B. DataStreams).
Oder verfügbare Frameworks nutzen.

Dein Code hat ein grundsätzliches Problem: Die Lese-Schleife zum Empfangen der Datei wird nie beendet.
Aufgrund der Anmerkung in der Doku zu InputStream read(byte[])
If the length of b is zero, then no bytes are read and 0 is returned; otherwise, there is an attempt to read at least one byte. If no byte is available because the stream is at the end of the file, the value -1 is returned;
würde ich mir mal angewöhnen in den Schleifen auf -1 zu prüfen, da es scheinbar legitim ist auch 0 bzw. nichts zurück zu liefern z.B. while ((len = inStream.read(buffer)) !=-1) {
Das löst allerdings das Problem noch nicht, denn read versucht solange zu lesen, solange der Stream offen ist und liefert deshalb weder 0 noch -1, sondern wartet einfach (blockierend). In Deinem Fall müsstest Du die Bedienung an die zuvor übermittelte Dateigröße knüpfen.

Da kommt Dir dann das Problem mit den gemischten Streams in die Quere - speziell weil hier ein ObjectStream beteiligt ist.

Versuche grundsätzlich erst einmal “einfache” Dateien/Daten zu übertragen - z.B. eine Textfile, das man selbst mit korrupten Daten immer lesen kann.

Folgend Dein Code abgewandelt. Diesen bitte nicht als Vorlage verwenden.
Hier werden die zwei Probleme deutlich.

  1. Lese Schleife im Client endet nicht
  2. ObjectStream überträgt nach Versenden eines Objekt ein zusätzliches Byte

ProblemDemo
[spoiler]

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.io.Serializable;
import java.net.ServerSocket;
import java.net.Socket;

public class ServerClientDemo {
	public final static int PORT = 666;
	public final static String INPUT_File = "C:/temp/test.txt";
	public final static String OUTPUT_File = "C:/temp/test_out.txt";

	public static void main(String[] s) {
		new Server();
		new Client();
	}
}

class SendObject implements Serializable {
	private static final long serialVersionUID = -1718571405100109903L;
	public long size;
}

class Server {
	public Server() {
		new Thread(new Runnable() {
			public void run() {
				try {
					ServerSocket serverSocket = new ServerSocket(ServerClientDemo.PORT);
					System.out.println("server started and waiting for client ...");
					System.out.println();
					Socket socket = serverSocket.accept();
					send(socket);
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		}).start();
	}

	private void send(Socket socket) throws Exception {
		System.out.println(this.getClass().getSimpleName() + ": connected to client " + socket.getInetAddress() + ":"
				+ socket.getPort());

		OutputStream outStream = socket.getOutputStream();
		ObjectOutputStream objOutStream = new ObjectOutputStream(outStream);

		File file = new File(ServerClientDemo.INPUT_File);
		FileInputStream fileInputStream = new FileInputStream(file);

		SendObject dataInfo = new SendObject();
		dataInfo.size = file.length();

		objOutStream.writeObject(dataInfo);
		objOutStream.reset();
		objOutStream.flush();
		System.out.println(this.getClass().getSimpleName() + ": object sent ( " + dataInfo.size + " )");

		byte[] buffer = new byte[16000];
		int len = 0;

		while ((len = fileInputStream.read(buffer)) != -1)
			outStream.write(buffer, 0, len);
		outStream.flush();
		fileInputStream.close();
		System.out.println(this.getClass().getSimpleName() + ": file sent");
	}
}

class Client {
	public Client() {
		new Thread(new Runnable() {
			public void run() {
				try {
					startAndRead();
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		}).start();
	}

	private void startAndRead() throws Exception {
		Socket socket = new Socket("localhost", ServerClientDemo.PORT);
		File file = new File(ServerClientDemo.OUTPUT_File);

		InputStream inStream = socket.getInputStream();
		FileOutputStream fileOutputStream = new FileOutputStream(file);

		ObjectInputStream objInStream = new ObjectInputStream(inStream);
		long size = ((SendObject) objInStream.readObject()).size;
		System.out.println(this.getClass().getSimpleName() + ": object received ( " + size + " )");

		byte[] buffer = new byte[16000];
		int len = 0;

		System.out.println(this.getClass().getSimpleName() + ": waiting for file...");
		while ((len = inStream.read(buffer)) != -1) {
			System.out.println(this.getClass().getSimpleName() + ": chunck size of " + len + " received");
			System.out.println(this.getClass().getSimpleName() + ": still reading file...");
			fileOutputStream.write(buffer, 0, len);

		}
		fileOutputStream.flush();
		fileOutputStream.close();
		System.out.println(this.getClass().getSimpleName() + ": file received");
	}
}```
[/spoiler]


*** Edit ***

Da ich gerade ein bisschen Leerlauf habe, hab ich mir das ganze mal genauer angeschaut. Bei dem zusätzlichen Byte, das nach dem Senden des Objekts noch im Stream ansteht handelt es sich, um [ObjectStreamConstants.TC_RESET](http://docs.oracle.com/javase/1.4.2/docs/api/java/io/ObjectStreamConstants.html#TC_RESET)
Das Senden dieses Bytes rufst Du selbst durch den Aufruf der reset() am OutputStream aus.

Mein absoluter Tipp:

Streams nicht “wild” mischen. Such dir eine Stream-Art aus, und dann bleib dabei.

Wenn du Objekte und Rohdaten verschicken willst, dann nimm am besten den DataOutputStream.

Ein Objekt serialisierst du mittels eines separaten ObjektOutputStreams zu bytes. Dann schaust du wieviele Bytes da sind und schickt über den DataOutputStream die Länge als int oder long. Dann die bytes. Auf der Empfängerseite liest du zuerst die Länge und weißt dann wieviele Bytes du lesen musst. EInmal die bytes rausgelesen, scheibst du diese durch einen separaten ObjectInputStream und machst daraus wieder ein Objekt.

Danach kannst du deine File schicken. Am besten wieder die Dateilänge vorausschicken. Dann die Datei selbst. Auf der anderen Seite das gleiche wieder umgekehrt.

Ganz allgemein kannst du so ein Protokoll aufbauen:

1 byte --> Definiert die Art der Nachricht. 0x00 für Ein Objekt, 0x01 für eine Datei, 0x02 für <wasweißich>. Mit dem Datentyp “byte” kannst du so 256 Nachrichten identifizieren. Das sollte für’s erste reichen.
4 bytes als Long --> Definiert wieviele Daten bzw. bytes diese Nachrichtenart transportiert.
x bytes --> Der eigentliche Nachrichteninhalt. Siehe 1. Byte. Die Anzahl der Bytes steht in den vorangegengenen 4 Bytes.

Mein “SIMON” macht das übrigens sehr ähnlich. Erfunden hab ich das nicht selbst. Das haben viele andere schon vor mir so gemacht. Und ich kann’s nur empfehlen…

Wichtig ist nur: Bei einem Streamtyp während der Übertragung bleiben. Es mag hier und da auch funktionieren wenn man die Stream-Arten mischt… Aber das ist nicht nur aufwendig, sondern auch Fehleranfällig. Der eine oder andere Streamtyp verwendet nämlich intern selbst eine Art Protokoll und nimmt es einem Krum wenn man da zwischen rein pfuscht.

  • Alex

nur für die Länge als Long extra DataStream?
bei dir klingt ja alles so allgemein und dann so rudimentär gesendet, da könnte ein schlichter Binärstream, evtl. mit Buffer, auch schon reichen :wink:

oder wenn nur Objekte, dann ObjectStream ok?
ein byte ist doch auch ein Objekt :wink: , ist da Overhead beim Versenden in ObjectStream bekannt?

nur für die Länge als Long extra DataStream?

Nein, nur wegen des Komforts. Der DataStream hat eigentlich keinen Overhead…
Und wenn du mal selbst probiert hast einen Java-Long zu zerlegen in diesen in einen „dummen“ OutputStream als 4 einzelne Bytes zu schreiben, dann merkst du schnell dass der DataOutputStream genau dafür gemacht ist…

ist da Overhead beim Versenden in ObjectStream bekannt?

Das kommt drauf an wie ob du das gleiche Objekt schickst und wie oft du von reset() gebrauch machst…

  • Alex

Für mein recht simples Objekt (bestehend nur aus zwei long Instanzvariablen, von denen ich eigentlich nur eine benötigte) werden 56 Byte versendet.
Sicherlich wird sich das Verhältnis von 8/56 bei größeren Objekten relativieren. (Das komplexere Objekt von BlupBlup - bei dem es eigentlich auch nur um einen long geht - benötigt interessanter weise nur 2 Byte (58 gesamt) mehr.) Aber neben den Steuer Bytes und dem eigentlichen Objekt bleiben ja immer die klassenspezifische Informationen, die da mit übertragen werden müssen.

Für ein long ist es aber meiner Meinung nach definitiv einfacher z.B. DataStreams und deren writeLong() bzw. readLong() zu verwenden, als ein long in einem Objekt zu kapseln, per ObjectStream zu schreiben und zu lesen… casten muss man dann ja auch noch.

Sofern es um primitive Datentypen geht und nicht um Objekte, so macht es immer Sinn auf ObjectStreams zu verzichten. Der Overhead ist einfach zu groß. Nicht nur was die Kapazität anbelangt, sondern auch, was das serialisieren/deserialisieren betrifft. Für einen einfachen gekapselten Long mag das noch nicht ins Gewicht fallen. Aber für viele Long Variablen rechnet sich das ganz schnell.

Ich habe eine Lösung im Netz gefunden, die als Attribut ein Objekt der Klasse WrapperFile verwendet.
Mein DataInfo Objekt erhält also einfach dieses Attribut. Die Datei wird in einem byte[] gespeichert und
das DataInfo Objekt kann über writeObject() verschickt werden. Auf der anderen Seite wird
das byte[] in eine Datei eingelesen. Das funktioniert bei mir ganz gut. Trotzdem danke für eure Hinweise.

package com.shared;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.Serializable;

public class WrapperFile implements Serializable
{
	private static final long serialVersionUID = 7387996229438399989L;
	
	private File file;
	private byte[] byteRepresentation;
	
	public WrapperFile(File file)
	{
		this.file = file;
	}


	public void fileToByteArray() 
	{
		try 
		{
			this.byteRepresentation = new byte[(int) this.file.length()];
			FileInputStream fileInputStream = new FileInputStream(this.file);
			fileInputStream.read(byteRepresentation);
			fileInputStream.close();
		}
		catch (FileNotFoundException e)
		{
			e.printStackTrace();
		}
		catch (IOException e) 
		{
			e.printStackTrace();
		}
	}


	public void byteArrayToFile(File file) 
	{
		try
		{
			FileOutputStream fileOutputStream = new FileOutputStream(file);
			fileOutputStream.write(this.byteRepresentation);
			fileOutputStream.close(); 
		} 
		catch (FileNotFoundException e)
		{
	
			e.printStackTrace();
		} 
		catch (IOException e)
		{
			e.printStackTrace();
		}
	}
}

immer dieses ‚trotzdem‘ das soviel heißt wie ‚ihr konntet nicht helfen‘ :wink:

byte[] als Object zu verschicken war klare Option, direkt hier genannt in erster Antwort,
eine Extra-Klasse mit zusätzlichen Informationen ist natürlich ebenso klar Erweiterung,

generell noch eine Basisklasse Nachricht mit Enum Nachrichtentyp, damit man dann den Inhalt schnell feststellen kann,
falls nicht durch Ablauf schon klar ist, was ausgetauscht wird

this.byteRepresentation = new byte[(int) this.file.length()];

Sowas find ich immer sehr unschön weil das erfordert dass vor dem versenden die ganze Datei in den Arbeitsspeicher eingelesen werden muss. Bei großen Dateien ist sowas äußerst ärgerlich bzw. kannst du schon mal über dein Speicherlimit schiessen.