Dateitransfer über Socket

simon1389

Grünschnabel
Dateitransfer über Socket wird nicht korrekt beendet

Hallo, mein erster Post hier ;) Also nicht zu streng mit mir sein! Das richtige Forum sollte ich zumindest schonmal getroffen haben :)

Also ich bin dabei, einen Chat zu programmieren. Das läuft soweit auch alles ganz fein. Text schicken geht ganz normal, Nachrichten an bestimmte Leute im Chat geht auch.
Jetzt will ich als weiteres Feature die Möglichkeit haben eine Datei an eine bestimmte Person zu schicken.

Das mache ich so, dass ich einen speziellen Befehl ("/transfer NAME") habe. Wenn der Server sieht, dass ein Client einen solchen Befehl geschrieben hat, übermittelt der Server die IP-Adresse des Datei-Verschickenden an den der die Datei erhalten soll.
Vorher hat der Datei-Verschickende nach dem /transfer Befehl einen neuen Thread eröffnet in dem auf eine eingehende Verbindung über ServerSocket gewartet wird.
Der Datei-Erhaltende hat ja die IP des Datei-Verschickenden übermittelt bekommen und macht nun ebenfalls einen neuen Thread auf in dem er sich mittels eines Sockets mit der IP des Datei-Verschickenden verbindet. (das alles auf einem anderen Port als der eigentliche Chat läuft.. eh klar)
Nun besteht also eine extra Verbindung direkt zwischen den beiden Clients.
Jetzt wird die Datei übertragen und die Threads sollten beendet sein.
Nun zu meinem Problem: Die Datei wird zwar übertragen, allerdings wird sie (bei einem kleinen Bild was ich momentan testweise immer verschicke) nicht angezeigt. Versuche ich sie zu löschen bekomm ich eine Meldung, dass die JVM noch darauf zugreifen würde und das deshalb nicht möglich ist...
Erst wenn ich die JVM komplett beende, also den ganzen Chat abschieße ist die Datei anscheinend freigegeben und ich kann sie auch öffnen und sie wird korrekt angezeigt.

Ich teste das ganze hier auf einem Rechner und habe bei meinem /transfer Befehl noch nicht die Möglichkeit eine bestimmte Datei zu verschicken, sondern es wird einfach eine hart reinkodierte Datei verschickt und beim andern Client der auch auf meinem Rechner läuft auf einen anderen Ort an der Festplatte wieder gespeichert.

Ich hoffe ich konnte das einigermaßen gut beschreiben.

Mein Code dazu:

FileSender Klasse:

Code:
package chat;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;

public class FileSender implements Runnable{

	public FileSender() {
	}

	@Override
	public void run() {
		try {
			ServerSocket serverSocket = new ServerSocket(8766);
			Socket socket = serverSocket.accept();
			BufferedInputStream fileReader = new BufferedInputStream(new FileInputStream("E:\\Simon\\Desktop\\skigebiete-test4.png"));
			BufferedOutputStream fileWriter = new BufferedOutputStream(socket.getOutputStream());
			int read = 0;
			while ((read = fileReader.read()) != -1) {
				fileWriter.write(read);
			}	
			fileReader.close();
			fileWriter.close();
			socket.close();
			serverSocket.close();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}	
	}
}

FileReceiver Klasse:

Code:
package chat;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.Socket;
import java.net.UnknownHostException;

public class FileReceiver implements Runnable {
	
	private String fileHostIP = "";
	
	public FileReceiver(String fileHostIP) {
		this.fileHostIP = fileHostIP;
	}

	@Override
	public void run() {
		try {
			Socket socket = new Socket(fileHostIP, 8766);
			BufferedInputStream in = new BufferedInputStream(socket.getInputStream());
			BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream("E:\\Simon\\test.png"));
			int read = 0;
			while ((read = in.read()) != -1) {
				out.write(read);
			}
			out.close();
			in.close();	
			socket.close();
		} catch (UnknownHostException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

und hier erfolgt der aufruf in meiner hauptklasse:
das in der run methode, ist der teil wo der client benachrichtigt wird, dass er jetzt ein File bekommt und das in der action-Methode der Teil dass der client jetzt eine nachricht verschickt

Code:
public void run()
	{
		String line;
		String command[];

		try
		{
			while(true)
			{
				line = in.readLine();
				command = line.split(" ");
				if (line.contains(": /") && command[4].equals("/youGetFile")) {
					Thread fileTransfer = new Thread(new FileReceiver(command[5].split(":")[0].substring(1)));
					fileTransfer.start();
				} else if (line != null)
					outputarea.append(line+'\n' );
			}
		} catch (IOException e) { 
			e.printStackTrace();
			say("Verbindung zum Server abgebrochen"); 
		}
	}

	public boolean action(Event e, Object what)
	{
		if (e.target == inputfield)
		{
			String inp = (String) e.arg;
			String[] command = inp.split(" ");
			if(command[0].equals("/transfer")) {
				out.println(": " + inp);
				Thread fileTransfer = new Thread(new FileSender());
				fileTransfer.start();
			} else {
				out.println(": " + inp);
				inputfield.setText("");
				return true;
			}
		}
		return false;
	}


Ich hoffe ihr könnt mir weiterhelfen... habe schon einiges probiert. Auf verschiedenste Arten versucht die sockets zu schließen oder die threads zu beenden. mit diesem SO_LINGER attribute rumgespielt. half leider alles nichts...
was auch auffällt, ist dass wenn ich netstat -a eingebe in der windows konsole, die ports immer noch offen sind... was ja eigentlich nicht sein sollte. Also zumindest die für den filetransfer (8766)


Grüße

Simon
 
Zuletzt bearbeitet:
Ich hab gerade mal ein wenig mit der Thematik rumgespielt, rausgekommen ist das hier:

Java:
public class Server extends Thread {

	private ServerSocket serverSocket;

	public Server(int port) throws IOException {
		this.serverSocket = new ServerSocket(port);
	}

	@Override
	public void run() {
		try {
			System.out.println("waiting for connection");
			Socket socket = serverSocket.accept();
			System.out.println("connection accepted");

			File f = new File("input.html");
			FileInputStream input = new FileInputStream(f);
			BufferedOutputStream socketOut = new BufferedOutputStream(socket
					.getOutputStream());
			System.out.println(f.getAbsolutePath());
			int read = 0;
			while ((read = input.read()) != -1) {
				socketOut.write(read);
				System.out.println("write " + read + " to socket");
			}
			socketOut.flush();

			socket.close();
			this.serverSocket.close();
		} catch (Exception e) {
			System.err.println("sth was wrong");
			e.printStackTrace();
		}
	}

	public static void main(String[] args) throws IOException {
		Server server = new Server(8766);
		server.start();
		System.out.println("Server established");

		Client client = new Client();
		client.getFromServer("127.0.0.1", 8766);
	}
}

Und hier der Client:
Java:
public class Client {

	public void getFromServer(String host, int port)
			throws UnknownHostException, IOException {
		Socket socket = new Socket(host, port);
		BufferedInputStream fileReader = new BufferedInputStream(socket
				.getInputStream());
		FileWriter writer = new FileWriter("output.html");
		int read = 0;
		while ((read = fileReader.read()) != -1) {
			writer.write(read);
			System.out.println("read " + read + " from socket");
		}
		fileReader.close();
		writer.close();
		socket.close();
		
		while(true) {
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println("Client ist sleeping - try to open output.html");
			
		}
	}
}

Warum bei dir das File scheinbar nicht richtig geschlossen wird, ist mir auch nicht ganz klar. Mein Client läuft nach dem Transfer in einer Endlosschleife weiter, und währenddessen kann ich problemlos auf die output.html zugreifen...

Versuch vielleicht mal, deinen Socketpart in meine Klassen zu übertragen und guck, ob der Effekt dann immer noch auftritt!
 
Hey ihr beiden,

close() gehört auf jeden Fall immer in einen finally-Block, damit auch immer falle einer Exception die Verbindungen geschlossen werden.

simon, hast du dir denn schonmal mit System.out ausgeben lassen, ob auch alles wie gewünscht ausgeführt wird?
 
Hallo nochmal! :)
Vielen Dank für die Tipps!
Der Tipp von dir zeja mit dem finally Block brachte den gewünschten Effekt! :-) Vielen Dank******!!
Anscheinend wird da wirklich irgendwo eine Exception drin geschmissen und die Sockets nicht richtig geschlossen.... was mich aber sehr wundert, denn ich hatte im vorfeld eigentlich alles mit system.out alles ausgeben lassen an welche stellen er überall hinläuft, und jeweils vor und nach den wichtigen stellen mir das anzeigen lassen....

einziges problem was ich jetzt noch habe: es wird wiegesagt eine exception geschmissen (connection reset) und ich weiß nicht, wie ich das verhindern soll... anscheinend wird einem beteiligten die verbindung zu schnell geschlossen....
habe das versucht mit einem bufferedwriter vom filereceiver aus zu umgehen und dem filesender so mitzuteilen wann er den socket schließen kann, also wann der filereceiver komplett fertig ist... wenn ich das allerdings mache bekomme ich wieder das selbe problem wie vorher, obwohl ich das auch in den final block geschrieben habe ;)

naja... nichts destotrotz funktioniert es mit dem final block, auch bei großen dateien... werd ich erstmal mit der exception gut leben können ;)

also danke nochmal für die hilfe!
 
Jetzt ist mir noch eine Frage eingefallen ;)
Ich hab das mit der Datenübertragung jetzt ein bisschen getestet und bei mir lokal klappts ja wunderbar. Jetzt wollte ich das aber mit einem Freund probieren der wo anders an nem anderen PC mit ner anderen IP hockt.. und da funktionierts nicht.
Kann es sein... bzw ich wüsste nicht an was es sonst liegt, dass mein Freund bei seinem Router vorher den jeweiligen Port freigeben muss der in meinem Programm benutzt wird?
Gibt es eine Möglichkeit, eine Library oder so, mit der ich im Router einen Port freigeben kann, eventuell auch nur temporär? Sonst müsste ja jeder der eine Datenübertragung machen will, den jeweiligen Port vorher immer erst freigeben...
Was mich dann aber schon wieder wundert ist, warum das ganze dann mit den normalen Chatnachrichten funktioniert... Da werden ja auch "Daten" übertragen ;)

Wär nett wenn mir das jemand mal erklären könnte und ob ich da nicht eventuell sogar einen grundlegenden Denkfehler habe!

LG simon
 
Grundsätzlich hast du Recht.

Der Server (nur der, die Clients nicht) muss beim Router (falls er einen hat) einstellen, das der Port xyz durchgelassen werden soll und zu welchem Computer hinter dem Router das Ganze geschickt werden muss.

Bei öffentlichen Chats etc ist das für die Benutzer egal, da alle programmiermäßig nur Clients sind und der Server irgendwo beim "Chatanbieter" zentral läuft und von dem verwaltet wird.
Der hat das am Anfang einmal eingestellt und das wars.

Wenn du und dein Freund aber was übertragen, ist ja einer von euch der Server und muss das beim Router eben einstellen. Portforwarding nennt sich das.
Wie das genau ausschaut, hängt vom Routermodell ab, generell muss man den betroffenen Port und die lokale IP vom Serverrechner hinter dem Router angeben.

Wenn du das vom Programm aus steuern willst, lautet das Stichwort UPNP. Hab allerdings keine Ahnung, wie das mit Java zu bewerkstelligen ist.

Gruß
 
Zurück