Java Client-Server (Chat) - auch auf andere Datentypen reagieren, nicht nur auf Text

mehmet_b_90

Lieutenant
Registriert
Aug. 2013
Beiträge
560
Guten Abend liebe CB-Community,

programmiere gerade ein Chat-Programm. Läuft auch soweit wunderbar. Allerdings habe ich da eine eher allgemeine Frage zu diesem Thema.

Wie reagiere ich außer auf Textnachrichten in einer Schleife solcher Client-Server-Modelle? Ich habe ja eine Schleife mit einer read-Methode die immer wartet bis Daten von der andere Seite ankommen und werden dann als Character/String weiter verarbeitet.

Wie reagiere ich aber, wenn die andere Stelle mal binäre Daten oder Objekte verschickt? Ich muss ja vorher überprüfen ob es sich um Text oder Binär handelt.

Hier zwei Abschnitte meiner Codes:

ClientThread-Klasse vom Server:

Code:
package main;

import java.io.*;
import java.net.*;
import java.text.SimpleDateFormat;
import java.util.Date;
import gui.*;

public class ClientThread implements Runnable {
	private Socket client;
	private MainGUI serverGui;
	private BufferedReader in;
	private BufferedWriter out;
	private String username;
	private String msg;
	
	public ClientThread(Socket c, MainGUI f) {
		client = c;
		serverGui = f;
	}
	
	@Override
	public void run() {
		try {
			in = new BufferedReader(new InputStreamReader(client.getInputStream()));
			out = new BufferedWriter(new OutputStreamWriter(client.getOutputStream()));
			
			username = in.readLine(); // Empfange vom Client als erstes den Benutzernamen
			MainGUI.listModel.addElement(username); // Füge Benutzername in die Teilnehmerliste hinzu
			
			serverGui.setMsg("[INFO] " + new SimpleDateFormat("HH:mm").format(new Date()) + " - " + username + " hat sich angemeldet!");
			
			while (true) {
				msg = in.readLine(); // Wartet bis eine Nachricht vom Client geschickt wird
				serverGui.setMsg(msg); // Empfangene Nachricht in das Chatfenster vom Server schreiben
				
				// Empfangene Nachricht an alle anderen Clients senden
				for (int i = 0; i < Server.clientList.size(); i++) {
					if (client != Server.clientList.get(i)) {
						Socket s = Server.clientList.get(i);
						BufferedWriter listWriter = new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
						listWriter.write(msg + "\n");
						listWriter.flush(); // Damit die Nachricht, die voerst im Puffer liegt, gleich übergeben wird
					}
				}
			}
		} catch (IOException e) {
			serverGui.setMsg("[INFO] " + new SimpleDateFormat("HH:mm").format(new Date()) + " - " + username + " hat sich abgemeldet!");
			
			// Client von der Teilnehmerliste entfernen
			for (int i = 0; i < MainGUI.listModel.getSize(); i++) {
				if (MainGUI.listModel.getElementAt(i).equals(username)) {
					MainGUI.listModel.remove(i);
					Server.clientListCounter--;
				}
			}
		}
	}
}


Client-Klasse vom Client:

Code:
package main;

import java.io.*;
import java.net.*;
import java.text.SimpleDateFormat;
import java.util.Date;
import gui.*;

public class Client implements Runnable {
	private String host;
	private int port;
	private Socket client;
	private BufferedReader in;
	private BufferedWriter out;
	private MainGUI clientGui;
	
	public Client(String h, int p, MainGUI f) {
		host = h;
		port = p;
		clientGui = f;
	}
	
	@Override
	public void run() {
		try {
			client = new Socket(host, port);
			
			clientGui.addClientOutputStream(client.getOutputStream()); // OutputStream an das Hauptfenster übergeben
			in = new BufferedReader(new InputStreamReader(client.getInputStream())); // Baue InputStream auf
			out = new BufferedWriter(new OutputStreamWriter(client.getOutputStream())); // Baue OutputStream auf
			
			clientGui.readMsg("[INFO] " + new SimpleDateFormat("HH:mm").format(new Date()) + " - Erfolgreich mit Server verbunden!");
			
			out.write(clientGui.getUsername() + "\n"); // Übermittle als erstes den Benutzernamen
			out.flush();
			
			while (true) {
				clientGui.readMsg(in.readLine()); // Wartet bis eine Nachricht vom Server empfangen wird und schreibt es in das Chatfenster vom Client
			}
		} catch (UnknownHostException e) {
			clientGui.readMsg("[INFO] " + new SimpleDateFormat("HH:mm").format(new Date()) + " - Server konnte nicht gefunden werden!");
		} catch (IOException e) {
			clientGui.readMsg("[INFO] " + new SimpleDateFormat("HH:mm").format(new Date()) + " - Kommunikationsfehler!");
		}
	}
}
 
Zuletzt bearbeitet:
Du kannst vor den binären Daten (oder was auch immer) ein Flag senden, dass deinem Client sagt, wie es die nachfolgenden Daten zu behandeln hat. Dafür dürfte ein Byte locker ausreichen.

Es wäre günstig, wenn du dir für die Kommunikation ein Protokoll ausdenkst oder ein vorhandenes adaptierst. Es ist auch ein "Transmission"-Objekt denkbar, dass die zu übertragenden Daten sowie ein Typ-Flag enthält. Objekte kann man mit ObjectInput/OutputStream verschicken und empfangen.

Aber wenn man schon Objekte direkt verschickt, reicht es auch, auf der Empfängerseite mit obj.getClass() oder obj instanceOf Class zu prüfen, um welche Art Objekt es sich handelt. Und dann braucht man nur noch casten.
 
@e-Laurin: Danke für dein Vorschlag. Darauf hin habe ich diesen Thread gefunden. So könnte es also aussehen? Wie mach ich das aber in Java mit den Bytes?
 
Ja genau so was simples wie im letzten Post reicht schon für die meisten Sachen aus. Du musst dir für Java jetzt entsprechend ein Byte Array basteln und das raus schicken. BufferedWriter/Reader sind nur für Text da. Aber es gibt auch entsprechend andere. Die ganzen Java Klassen kenne ich jetzt nicht wie meine Westentasche aber mit dem hier http://docs.oracle.com/javase/7/docs/api/java/io/DataOutputStream.html und dem Pendant zum Lesen sollte es gehen. *edit* wobei du nicht mal das brauchst. Kannst direkt mit dem Input- bzw. OutputStream arbeiten.

Du kannst also z.B. zum Senden:
1. Den zu übertragenen Inhalt in ein byte[] konvertieren.
2. Ein neues byte array der Länge "1 (Nachrichtentyp) + 4 (Angabe der Länge des Inhalts) + x (Länge des byte Arrays in dem der Inhalt steht)" erstellen.
3. Das neue byte array entsprechend befüllen.
4. alles raus schicken.
 
Zuletzt bearbeitet:
Danke soweit. Ich werde mich dann mal rann machen. :)
 
Etwas CPU lastiger aber langfristig flexibler wäre die Nutzung von JSON. So könnte jetzt die "Magicnumber" als Bestimmung des Datentyps integriert werden und später vielleicht ein Dateiname wenn Dateien über den Chat verschickt werden sollten. Protokolldesign ist ein heißen Eisen und solange nicht jedes Byte teuer ist lohnt es sich etwas verschwenderischer vorzugehen und etwas zu nutzen das von vorne herein erweiterbar ist ohne mit dem bestehenden zu brechen.
Egal für was du dich entscheidest, auch eine Versionsnummer sollte Teil eines jeden Protokolls sein.

Falls JSON nicht gewünscht ist, stimme ich übrigens BlooDFreeZe zu. Der Weg der Protokollgestaltung ist der klassische Weg und der Data*Stream ist ein praktikabler Weg der Plattformunabhängigen und erprobten Nutzung dieses Schemas. Wobei mir auch hier, wie eben erwähnt, eine Protokollversionsnummer fehlt. Langfristig knallt sowas gerne mal.

BTW: Evtl. möchtest du dir mal Netty ansehen - ein Netzwerk IO-Framework das einem vielen Schmerzen ersparen kann - Netzwerk Programmierung ist alles, nur nicht trivial.
 
Danke soweit für eure Unterstützung. Ich versuche gerade das mit dem eigenen Protokoll aufzubauen. Leider scheitere ich da. Könnt ihr mir da ein kleines Beispiel zeigen? Wäre echt super! :rolleyes:
 
Mit der Angabe der Länge in ein Byte-Array zu packen. Wenn ich die Länge des Inhalts mit .length ausgebe erhalte ich ja ein int. Wie packe ich das in ein Byte-Array mit der Größe 4?

Ich habe mir jetzt mal so eine statische Methode gebaut:

Code:
public static byte[] convertData(byte[] b, byte type, byte[] size) {
		byte t = type;
		byte[] s = size;
		byte[] data = b;
		byte[] convertData = new byte[1 + s.length + data.length];
		
		convertData[0] = t; // Index 0 ist für das Type zuständig
		System.arraycopy(s, 0, convertData, 1, s.length); // Index 1 - 4 ist für die Länge der eigentlichen Daten zuständig
		System.arraycopy(data, 0, convertData,  s.length + 1, data.length); // Ab Index 5 beinhalten die eigentlichen Daten
		
		return convertData;
	}
 
Zuletzt bearbeitet:
ByteBuffer ist dein Freund. Der frisst (fast) alles, was du reinstopfst.

Code:
import java.nio.ByteBuffer;

public class TestClass {
	public static void main(String[] args) {
		byte type = 1;
		byte[] content = { 1, 2, 3, 4 };
		int length = content.length;		
		ByteBuffer msgBuilder = ByteBuffer.allocate(1 + 4 + length);
		byte[] msg;
		
		msgBuilder.put(type);
		msgBuilder.putInt(length);
		msgBuilder.put(content);
		
		msg = msgBuilder.array();
		
		System.out.println("Typ: " + type + 
				   "\nLänge: " + length +
				   "\nInhalt: " + getContentAsString(content) +
				   "\nNachricht: " + getContentAsString(msg));
	}
	
	public static String getContentAsString(byte[] array) {
		StringBuilder strBuilder = new StringBuilder(array.length);
		
		for (byte element : array) {
			strBuilder.append(element);
		}		
		
		return strBuilder.toString();
	}
}
Ausgabe:
Code:
Typ: 1
Länge: 4
Inhalt: 1234
Nachricht: 100041234
 
Danke für eure Hilfe es klappt soweit.
 

Ähnliche Themen

S
Antworten
6
Aufrufe
1.357
S
Antworten
4
Aufrufe
1.000
Antworten
4
Aufrufe
2.216
C
Zurück
Oben