Threads beim Server koordinieren

Sara3112

Newbie
Registriert
Juni 2009
Beiträge
7
Hallo Leute,

ich bin neu hier und ich weiß nicht genau, ob ich hier in richtige Forum schreibe. Aber ich habe ein großes Problem, woran ich schon lange sitze und ich bräuchte dringend Hilfe. Hinzu kommt das ich Programmier-Anfängerin bin. :(

Also ich entwickle einen Grafik-Editor mit Chat-Funktion. Dieser soll die Möglichkeit bieten Zeichnungen zu erstellen und mit anderen Usern zu chatten. Dabei soll aber auch die Zeichung verschickt werden können, welche dann beim Gegenüber auf das JPanel gezeichnet wird.

Die Chat-Funktion läuft Prima. Ich hab auch schon einen Thread erstellt für die Grafikübertragung. Mein Problem ist aber, dass ich nicht weiß, wie ich beim Server sagen kann, wann er aus welchem Thread lesen soll und was er wo übertragen soll.

Ich füge mal ein paar wichtige Code-Schnipsel hinzu, um mein Problem zu verdeutlichen.

Die Klasse ChatClient.java
Code:
	public void actionPerformed(ActionEvent e){
		Object obj = e.getSource();
		String cmd = e.getActionCommand();
		
		try{
			if(obj == button){
                          ...
						login();
					}
				}
			}
			if(obj == text){
				if(login){
					out.write(text.getText());
					out.newLine();
					out.flush();
				}
			}
		}
		....
	}

	private void login() throws IOException{
		socket = new Socket (host, port);
		in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
		out = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
		
                 .....
		
		t = new Thread(this);
		t.start();
	}

	public void run(){
		try{
			while (Thread.currentThread() == t){
				final String msg = in.readLine();
				if(msg == null)
					break;
				
				doUpdate(new Runnable(){
					public void run() {
						area.append(msg + "\n"); //area ist ein JTextArea
					}
				});
			}
		}
		catch (IOException e){}
	}

Die Klasse Grafikuebertragung.java:

Code:
    // hier wird das Bild auf der Zeichenfläche in den OutputStream geschrieben
    private void bildSenden() throws IOException{
		socket = new Socket(host, port);
         	cache = new ByteArrayOutputStream(64 * 1024);
		
       	if(Zeichenflaeche.buf != null){
    		System.out.println("Bild wird gesendet...");
    		ImageIO.write(Zeichenflaeche.buf, "jpeg", cache);
    		cache.flush();
    		byte[] array = cache.toByteArray();
    		final BufferedOutputStream output = new  BufferedOutputStream(socket.getOutputStream(), 64 * 1024);
    		output.write(array);
    		output.flush();
    		System.out.println("Bild wurde gesendet...");
    		
    	}	
		t = new Thread(this);
		t.start();		
	}
	
//Hier wird das empfangene Bild aus dem InputStream gelesen und in der Variablen //empfangenesBild gespeichert
	public void run(){
		try{
			while (Thread.currentThread() == t){
				
				final BufferedImage image;
				InputStream in = socket.getInputStream();
				byte[] array = new byte[64*1024];
				in.read(array);
				image = ImageIO.read(new ByteArrayInputStream(array));
				
				
				if(image == null){
		    		System.out.println("empfangenes Bild ist null");
					break;
				}
				
				doUpdate(new Runnable(){
					public void run() {
						empfangenesBild = image;
					}
				});
			}
		}
		catch (IOException e){}
	}

Und hier die Klasse Server:

Code:
	public void startServer(){
		try{
			ServerSocket server = new ServerSocket(port);
			
			
			while (true){
				Socket client = server.accept();
				new ChatThread(client).start();
				new GrafikuebertragungThread(client).start();
			}
		}...

private class GrafikuebertragungThread extends Thread {
			
		private Socket gu;
		private BufferedOutputStream bildOutput;

		
		public void run(){

				bildOutput = new BufferedOutputStream(gu.getOutputStream(), 64*1024);
				
				bildManager.add(bildOutput); //bildManager ist ein Vector vpm Typ BufferedOutputStream
				
				BufferedImage image;
				InputStream in = gu.getInputStream();
				byte[] array = new byte[64*1024];
				in.read(array);
				while ((image = ImageIO.read( new ByteArrayInputStream(array))) != null){
					sendImage(image);
				}...

		}
		
		
		// schickt das Bild an alle Benutzer, die in der Variable "bildManager" gespeichert sind
		private void sendImage(BufferedImage image){
			synchronized(manager){
				for (BufferedOutputStream bildOutput : bildManager){
					ByteArrayOutputStream cache = new ByteArrayOutputStream(64 * 1024);
					try{
						ImageIO.write(image, "jpeg", cache);
						cache.flush();
						byte[] array = cache.toByteArray();
						bildOutput.write(array);
						bildOutput.flush();

					}...
				}
			}
		}
	}
	}

 //und dann hab ich halt noch eine interne Klasse ChatThread, die die Textnachrichten entsprechend weiterleitet

Das ist alles ein bisschen viel. Aber ich weiß nicht, was gebraucht wird, um mir helfen zu können.
Wie gesagt, mein Problem ist, dass ich in der Klasse "Server" nicht weiß, wie ich ihm sagen soll, wann er welchen Thread benutzen soll.. Weil im Moment habe ich noch das Problem. dass er zum Beispiel, das Bild in einer Zeichenkette in der JTextArea ausgibt.

Ich wäre euch ewig dankbar, wenn ich mir helfen könntet. Bin schon sehr verzweifelt. :(

LG Sara
 
Normal baut man sich mehrere Threads welche jeweils in einer Endlosschleife laufen (am Ende eines Durchlaufs immer ein sleep() mit paar Millisekunden damit die Prozessorauslastung gering bleibt). In der Hauptklasse hältst du Referenzen auf alle Threads und diese Referenzen können über static-Methoden dann von anderen Threads abgerufen werden. Die Threads selber haben synchronisierte Methoden um irgendwelche Variablen (z.B. ein neues Bild) zu setzen. Bei jedem Durchlauf der Endlosschleife überprüft dann der Thread (synchronized!) ob sich Variablen geändert haben und reagiert drauf.

Für das Bild würde ich ein JPanel nehmen und dort die paintComponent()-Methode überschreiben so dass dort das Bild ausgegeben wird. Die Klasse speichert dann eben das Bild als Variable mit. Bei einem geänderten Bild aktualisierst du das Bild (synchronzed oder eine volatile Variable!) und rufst dann ein repaint() auf.
 
Oh je.. vom ersten Abschnitt habe ich noch nicht soviel verstanden. Ich hab noch nicht kapiert, wo ich welche Threads aufbaue und vorallem wie. Und meinst du mit der Hauptklasse die Server-Klasse?
Den zweiten Abschnitt habe ich schon soweit realisiert.
Ich hab nu gelesen, dass ich den Threads Namen übergeben kann. Kann ich das nicht in den Klassen "ChatClient" und "Grafikuebertragung" machen und dann beim Server den Namen abfragen und dann entweder einen ChatThread erstellen oder einen GrafikuebertragungsThread?

Danke für die Hilfe und die schnelle Amtwort.
 
Naja deine Codeschnippsel sind halt wirklich nur extrem ungenügend und zusammengeklatschst so dass ich dir schwer was ganz Genaues sagen kann.
Wie soll das mit den Zeichnungen eigtl. genau funktionieren? Einer zeichnet und alle bekommen das Bild oder alle zeichnen am selbern Bild gleichzeitig oder wie dachtest du das?

Mal ein Beispielthread, ich denke das zeigt am Besten was ich am Anfang meinte:
Code:
public Beispielthread extends Thread(){
	private int Beispielvariable;
	private volatile boolean hasChanged = false;
	public volatile boolean isRunning = true;

	public BeispielThread(){
	}

	public void stopThread(){
		isRunning = false;
	}
	
	public synchronized void changeVariable(int i){
		Beispielvariable = i;
	}

	public void run(){
		while(isRunning){
			if(hasChanged){
				hasChanged = false;
				synchronized(this){
					// Aktion mit Beispielvariable ausführen
				}
			}
			// schlafen
			try{
				Thread.sleep(100);
			} catch (Exception e){}
		}
	}
}
Meist macht sowas Sinn. Z.B. könnte man einen Thread machen, der so für die Bildaktualisierung (Darstellung neuer Nachrichten + Bilder) zuständig ist.

Für das Chatten würde ich pro Client einen Thread nehmen (du hast da glaub ich zwei?). Jeder Nachricht/Bild wird ein Zeichen vorangestellt ("m" für Nachricht, "b" für Bild) gefolgt von der Länge in Bytes (evtl. unnötig aber kann oft nützlich sein) und einem Trennzeichen (z.B. "|"). Der entgegennehmende Chatthread kann dann bei eingehenden Daten schnell bestimmen, um was es sich handelt. Das ganze wird dann also vom Chatthread gelesen und dem Bildaktualisierungsthread werden die neuen Daten (Bild oder Text) übergeben.

Du hast doch irgendne Startklasse wo die main() drin ist. Dort musst du halt ne Referenz ("public static BildThread Bildaktualisierungsthread") hinterlegen für den Bildaktualisierungsthread damit die Chatthreads den finden. Und auch die Chatthreads sollten dort (z.B. in einer ArrayList) hinterlegt sein damit man bei Bedarf Funktionen auf ihnen aufrufen kann.

Du kannst den Threads schon Namen übergeben aber das bringt eher was fürs Debuggen. Ansonsten sehe ich da wenig Sinn weil man dafür auch normale Variablen verwenden kann.
 
Wie soll das mit den Zeichnungen eigtl. genau funktionieren?
Ich hatte mir überlegt, dass im Prinzip jeder User zeichnen und versenden kann. Aber es soll halt nicht gleichzeitig gesendet werden können. Während ein anderer seine Zeichnung verschickt wird der Senden-Button bei den anderen auf disabled gesetzt.

Für das Chatten würde ich pro Client einen Thread nehmen (du hast da glaub ich zwei?)
Also für die Textübertragung habe ich einen Thread und für die Grafikübertragung. Aber wenn ich jeder Nachricht ein Zeichen voranstellen kann, dann ist das ja eventuell gar nicht nötig oder? Könntest du mir vielleicht sagen, wie ich sowas mache?

Und Danke für deinen Beispielthread. Ich werde es ausprobieren.

Tut mir leid wenn ich irgendwie blöde Fragen stelle, aber es ist das erste Mal das ich mich an der Server/Client Programmierung versuche und ich habe noch nicht soviel Ahnung davon. :(
 
Aber wenn ich jeder Nachricht ein Zeichen voranstellen kann, dann ist das ja eventuell gar nicht nötig oder? Könntest du mir vielleicht sagen, wie ich sowas mache?
Hab ich doch eigentlich? Was willst du denn noch konkret wissen? Zeichen voranstellen und danach entscheiden was es für ne Nachricht ist und bis Nachrichtenende lesen. Anschließend wieder auf Nachrichtenanfang warten. Um das Ende einer Nachricht/Bildes zuverlässig zu erkennen (ich hatte ja die Übertragung der Größe in Bytes vorgeschlagen) könntest du auch ein spezielles Trennzeichen (z.B. wiederum ein "|") verwenden. In dem Fall müsstest du Nachrichten/Bilder aber in Base64 umwandeln (vgl. http://openbook.galileocomputing.de...04_008.htm#mj9430a7cb01468b2599ae42e412071dfd ) weil du sonst Probleme kriegst wenn der User selbst das Trennzeichen verwendet.

Ich hatte mir überlegt, dass im Prinzip jeder User zeichnen und versenden kann. Aber es soll halt nicht gleichzeitig gesendet werden können. Während ein anderer seine Zeichnung verschickt wird der Senden-Button bei den anderen auf disabled gesetzt.
Das Verschicken sollte eigtl. innerhalb weniger Millisekunden gehen (oder meinst du dass die andern das Zeichnen quasi in Echtzeit verfolgen können sollen?). Man müsste eigtl. grundsätzlich vor dem Zeichnen eine Sperre beantragen.
 
Zuletzt bearbeitet:
Könnt ich nicht auch, statt Zeichen voran zustellen, ObjectStreams benutzen? Da gibt es doch sowas wie
ObjectInputStream.
Und dann könnt ich doch beim Einlesen schreiben:
Object o = ObjectInputStream.readObject();
kann ich dann nicht mit o.equals(String) bzw o.equals(BufferedImage) abfragen, ob ich ein Bild oder einen String bekomme und es dann entsprechend berabeiten bzw weiterleiten?
 
Könntest du auch machen. Aber keinesfalls mit equals sondern mit instanceOf und dann casten. Am Besten das Ganze so abstrahieren (über eine extra Funktion), dass du es später leicht ändern kannst.
 
Also könnte ich einfach schreiben:

if (o instanceOf String) {..}
else if (o instanceOf BufferedImage){}

??
 
Zuletzt bearbeitet:
Ja. Allerdings wird es wohl mit dem BufferedImage Probleme geben, denn das ist so direkt wohl nicht serialisierbar. Ich denke ImageIcon wäre daher besser geeignet (man kann aber aus nem BufferedImage ein ImageIcon erstellen über den entsprechenden Konstruktor! Und Zurückwandeln geht auch mit getImage()).
 
Zuletzt bearbeitet:
Super.. Vielen vielen Dank für deine Hilfe. Ich werde es morgen früh sofort ausprobieren und berichten, wie es läuft.
Ergänzung ()

Soo. ich habe das BufferedImage nun in ein Byte-Array geschrieben. ImageIcon wäre sicherlich auch eine gute Möglichkeit gewesen. Aber ich habe deinen geänderten Eintrag gerade erst gelesen.
Mit den ObjectStreams läuft es auch ganz gut. Aber an einer Stelle hakt's noch und ich weiß nicht wieso.

Ich lese aus dem ObjectInputStream aus und übergebe den Inhalt dem Object o :

Code:
else if (o instanceof byte[]){
	array = ObjectToByteArray(o); // mit der Methode konvertiere ich das Object in ein byte[]
	BufferedImage image;
	image = ImageIO.read( new ByteArrayInputStream(array));
}

Jetzt wird mir aber gesagt, dass image null ist. Dabei ist das byte[] array auf jedenfall nicht leer. Ist der Aufruf falsch? Laut http://mindprod.com/jgloss/imageio.html#FROMBYTES wird es aber so gemacht. Habe absolut keine Ahnung was daran falsch ist. :mad:
 
Ahh.. habs.. meine Methode ObjectToByteArray, war ein wenig überflüssig und da lag auch der Fehler. Einfacher gehts mit
Code:
array = byte[]o;

aber warum einfach, wenns auch kompliziert geht ??? ^^
 
Zurück
Oben