Java - Server/Client simple

Shooter2k

Erfahrenes Mitglied
Hallo liebes Forum,
ich möchte einen kleinen Chat schreiben und benötige ein wenig Hilfe.

Ich habe schon ein paar Tutorials ausprobiert und komme trotzdem nicht ganz weiter.
Zum Ablauf:
1. Server starten(warten auf Verbindung) funktioniert
2. Connecten mit Client - funktioniert
3. Vom Client eine Nachricht an den Server schicken / funktioniert auch

Mein Problem:
4. Serververbindung aufrecht erhalten
5. eine weitere Nachricht an den Server schicken


Mein bisheriger Servercode:
Code:
package serverTest;
import java.io.*; 
import java.net.*;


public class Server {

	public static void main(String[] args) {
		try {
            ServerSocket skt = new ServerSocket(50666);

            Socket clientSocket = skt.accept();

            clientSocket.setKeepAlive(false);
         
            System.out.println("Connected..");

            
            BufferedReader input = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));

            String inputLine;

            while((inputLine = input.readLine()) != null)
            {
                System.out.println(inputLine);
            }

        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}

Mein Client:
Code:
package client;
import java.io.*; 
import java.net.*;

public class Client {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Client client = new Client();
	 	try {
	 	    client.test();
	 	} catch (IOException e) {
	 	    e.printStackTrace();
	 	}
	 	
	}

	void test() throws IOException {
	 	String ip = "127.0.0.1"; // localhost
	 	int port = 50666;
	 	java.net.Socket socket = new java.net.Socket(ip,port); // verbindet sich mit Server
	 	String zuSendendeNachricht = "Hello, xxxworld!\n";
	  	schreibeNachricht(socket, zuSendendeNachricht);
	  	
	 	String empfangeneNachricht = leseNachricht(socket);
	 	System.out.println(empfangeneNachricht);
	 }
	
	void schreibeNachricht(java.net.Socket socket, String nachricht) throws IOException {
	 	 PrintWriter printWriter =
	 	    new PrintWriter(
	 	 	new OutputStreamWriter(
	 		    socket.getOutputStream()));
	 	printWriter.print(nachricht);
	 	printWriter.flush();
	    }
	
	String leseNachricht(java.net.Socket socket) throws IOException {
	 	BufferedReader bufferedReader =
	 	    new BufferedReader(
	 		new InputStreamReader(
	 	  	    socket.getInputStream()));
	 	char[] buffer = new char[200];
	 	int anzahlZeichen = bufferedReader.read(buffer, 0, 200); // blockiert bis Nachricht empfangen
	 	String nachricht = new String(buffer, 0, anzahlZeichen);
	 	return nachricht;
	    }
	
}


Ich bin für jede Hilfe dankbar.
Beste grüße
 
Hi,

das Problem mit dem Verbindungsabriss hängt mit der Lebensdauer deines Socket-Objektes zusammen.
Dieses existiert nur innerhalb der Methode Test(). In dieser Methode gibt es nur ein Senden und ein Empfangen als Aktion.
Danach wird zur aufrufenden Instanz zurückgekehrt (deine Main) und das Socket-Objekt "stirbt" und somit auch deine Verbindung.

Es wäre also ne tolle Sache, wenn du dir ein Feld in einer Klasse machst, welches so ein Socket-Objekt aufnehmen kann.
Relativ simpel also einfach global in die Main rein.
Die Lesen- und Schreiben-Methoden bekommen die Instanz dann als Argument übergeben und können damit arbeiten.
Wenn du dann noch eine while-Schleife in die Main baust, dann terminiert das Programm nicht mehr und die Verbindung bricht auch nicht mehr ab, bis der teminierende Fall eintritt.

Falls du ein Beispiel haben möchtest, dann gib einfach Bescheid :)
Ansonsten hoffe ich, dass ich dir helfen konnte.

EDIT:
Bitte einen kurzen Moment...ich schreibe schnell ein Beispiel :)
 
Zuletzt bearbeitet:
Hallo,
vielen Dank für deine Hilfe.
Vom Prinzip habe ich es verstanden.

Ich bin leider noch ziemlich neu in Java und würde mich über ein kleines Beispiel von dir sehr freuen.
 
Hi,

also hier ist erstmal ein kleiner Code-Ausschnitt, der dir ein Socket-Objekt erzeugt:

Code:
public class SocketUtil {
	public static Socket createSocket(String ipAddress, int usedPort) throws UnknownHostException, IOException, IllegalArgumentException {
		if(null == ipAddress) {
			throw new IllegalArgumentException("ip-address is not valid"); // vllt. noch weiter Prüfungen einbauen
		}
		
		if(usedPort < 0) {
			throw new IllegalArgumentException("port is not supported"); // vllt. noch weitere Prüfungen einbauen
		}
		return new Socket(ipAddress, usedPort);
	}
}

Da könte man natürlich noch eine Menge Prüfungen einbauen (nur per Kommentar angedeutet). Bspw. muss die IP-Adresse in einem bestimmten Format vorhanden sein (in Zukunft noch eine Unterscheidung zw. IPv4 und IPv6 notwendig).
Das sei einführend aber nicht weiter wichtig, deshalb einfach gehalten.

Nun kann das in deinem Client folgendermaßen genutzt werden:
Code:
public static void main(String[] args) {
        Client client = new Client();
        try {
			Socket socket = SocketUtil.createSocket("127.0.0.1", 50666);
			
			while(true) {
				client.test(socket);
			}
        } catch (IOException e) {
            e.printStackTrace();
        } catch (IllegalArgumentException e1) {
			e1.printStackTrace();
		}
        
    }
 
    void test(Socket socket) throws IOException {
        String zuSendendeNachricht = "Hello, xxxworld!\n";
        schreibeNachricht(socket, zuSendendeNachricht);
        
        String empfangeneNachricht = leseNachricht(socket);
        System.out.println(empfangeneNachricht);
     }

Du erzeugst dir somit ein "globales" Socket-Objekt mithilfe des obigen Code-Ausschnittes und speicherst das in einer Variable des gleichen Types (hier einfach "socket" genannt.
Die test-Methode erwartet nun ebenfalls eine Variable vom Typ Socket und die haben wir ja nun --> also ab damit und an die Funktion übergeben:

Code:
client.test(socket) // <-- hier wird die Variable einfach an die Methode übergeben (in den Klammern)

Die Anpassung an der Methode ist oben auch schon aufgeführt:
Code:
void test(Socket socket) throws IOException { // <-- in den Klammern stehen die Variablen, die die Methode bekommen will, um arbeiten zu können

Auf der Server-Seite ist natürlich noch etwas zu ändern:
Code:
clientSocket.setKeepAlive(true); // <-- von false auf true geändert. wir wollen die Verbindung ja offen halten

Was mir jedoch beim Debuggen noch aufgefallen ist:
Der Server sendet keine Nachricht an den Client zurück. Der Client möchte aber eine haben und blockiert deshalb solange, bis er eine bekommt. Ergo müsstest du den Server nach dem Empfang der Nachricht die Möglichkeit geben eine zurück zu senden.

Ansonsten sollte das so lauffähig sein.
Bei weiteren Fragen einfach raus damit. Ich werde mal parallel noch an einer Lösung arbeiten und sie dir zur Verfügung stellen (dauert nur ein wenig :))
 
Hallo Alex,
vielen vielen Dank für deine Hilfe****** Es läuft :)

Könnte man die Test-Methode (Client), noch so umbauen, dass man eine Eingabe machen kann? Bis jetzt ist der Text ja statisch. Glaube das geht mit Scanner sc = new Scanner(System.in);

Wie würde eine Antwort vom Server denn aussehen?

Nochmals vielen Dank.
 
Hi,

freut mich, dass es läuft :)
Den Scanner kannst du natürlich für den Anfang benutzen (falls du in absehbarer Zeit einmal mit Oberflächen arbeiten willst, dann muss das natürlich irgendwie ausgelagert werden).

Theoretisch läuft so eine Kommunikation mit einem 3-Way-Handshake ab.
Zuerst sendet der Sender die Nachricht an den Empfänger. Der wiederum schickt die Bestätigung (auch Quittung genannt) für den Erhalt der Nachricht an den Sender zurück. Jetzt wird es witzig der Sender schickt dann nochmal eine Bestätigungs-Nachricht an den Empfänger, dass er seine Bestätigung erhalten hat.

So eine Bestätigung kann man natürlich auf verschiedenste Arten implementieren. Ein kurzes Beispiel:
Der Client sendet eine Nachricht an den Server und merkt sich deren Länge in Byte.
In der Quittung des Servers steht dann die Anzahl der Bytes drin, die er vom Client empfangen hat. Stimmt diese Angabe mit dem Wissen des Client überein, so geht dessen Quittung an den Server zurück.
An der Stelle könnte man wieder argumentieren, dass sich bei der Übertragung vllt. Bits gedreht haben etc., weshalb es besser wäre, sich eine Checksumme anstelle der Bytes zu merken, aber es soll ja nur ein Beispiel sein.

Das hat natürlich Auswirkungen auf beide Seiten (Client und Server), die jeweils auf Randsituationen reagieren müssen. Bspw. kommt eine Bestätigung nicht innerhalb eines bestimmten Zeitintervalls (Timeout), steht da etwas komisches in der Quittung etc. und wie reagiere ich darauf (Nachricht nochmal anfordern...)

Das möchte ich jetzt aber nicht so krass ausweiten, denn wir wollen es ja simpel halten. Sollte eher als kurzer Einblick dienen.
Machen wir das also so einfach wie möglich:

Der Server ließt die Nachricht des Clients vom Stream und gibt die Nachricht aus. Genau danach sendet er eine Nachricht an den Client, die als Quittung dient (Das Senden sieht genauso aus wie beim Client). Als Nachricht kannst du dir hier entweder einen Text ausdenken, der bei beiden als Quittungstext bekannt ist, oder du benutzt die Steuerzeichen des ASCII-Codes (die ersten 32 Stück).
Der Client ließt die Nachricht, und wenn der Quittungstext der erwartete war, dann kann weiter gemacht werden.

Hier mal noch ein Link zur ASCII-Tabelle: http://www.asciitable.com/

Die Erklärung war hoffentlich verständlich ^^
Viel Spaß beim Ausprobieren.
 
Hi,
Danke für den Einblick in den Ablauf. Ich brauche allerdings nicht unbedingt eine Antwort vom Server. Mir genügt schon eine einfache Ausgabe des geschriebenen Textes in der Konsole.

Habe die Test-Methode jetzt folgendermaßen erweitert:

Code:
void test(Socket socket) throws IOException {
		
		 Scanner sc = new Scanner(System.in);
		 System.out.print("Eingabe: ");
		 String eingabe = sc.next();
		
         schreibeNachricht(socket, eingabe+"\n");
        
        /*
        String empfangeneNachricht = leseNachricht(socket);
	System.out.println(empfangeneNachricht);
         */   
        
        
     }

Jetzt habe ich zwar die Möglichkeit zur Eingabe, allerdings kann ich nur einmal eine Eingabe zum Server schicken. Danach geht's nicht nochmal. Ich möchte aber mehrere Befehle nacheinander zum Server schicken.

Könntest du mir bitte noch ein kleines Beispiel geben?

Vielen Dank
 
Zuletzt bearbeitet:
Hi,

das geht eigentlich mit einem dreckigen Trick.
Du baust einfach eine Endlos-Schleife um das Eingeben und das anschließende Senden.

Code:
void test(Socket socket) throws IOException {
        
         Scanner sc = new Scanner(System.in);
         while(true) {
              System.out.print("Eingabe: ");
              String eingabe = sc.next();
        
              schreibeNachricht(socket, eingabe+"\n");
         }
        
        /*
        String empfangeneNachricht = leseNachricht(socket);
    System.out.println(empfangeneNachricht);
         */   
        
}

Ist aber wie gesagt sehr dreckig, da du nicht mehr aus der Methode in die Main zurück kehrst und dort keine weiteren Befehle absetzen kannst. Dein Programm arbeitet dann prinzipiell nur noch in der Methode test().
Wenn das richtig gemacht werden soll, dann würde man jeweils das Senden und das Empfangen in einen separaten Thread auslagern, sodass die Funktionalität zur selben Zeit gegeben ist. Damit kann man dann auch empfangen, während man selber sendet.
Aber der obige Ausschnitt sollte erstmal reichen, um deinen Wunsch umzusetzen. Nur bitte nicht daran gewöhnen!
 
Hallo nochmal :)

Eine kleine Frage noch. Habe jetzt den Server erweitert noch einem Tutorial auf YouTube.

Code:
package UTServer;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;

public class Server {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		try {
			ServerSocket server = new ServerSocket(50666);
			System.out.println("Server gestartetX");
			
			Socket client = server.accept();
			
			// Streams
			OutputStream out = client.getOutputStream();
			PrintWriter writer = new PrintWriter(out);
			
			InputStream in = client.getInputStream();
			BufferedReader reader = new BufferedReader(new InputStreamReader(in));
			
			//------------
			
			String s = null;
			
			while((s = reader.readLine()) != null)
			{
				
				writer.write(s + "\n");
				writer.flush();
				System.out.println("Empfangen vom Client: " +s);
				
			}
			
			writer.close();
			reader.close();
			
			
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

}

Ich möchte nun einen Vergleich machen anhand des gesendeten Stings

zB. :

if(s=="asdf")
{
System.out.println("Text ist "asdf" : " +s);
}


Hier rein:
Code:
while((s = reader.readLine()) != null)
{			
writer.write(s + "\n");
writer.flush();
System.out.println("Empfangen vom Client: " +s);
}

Das sollte dann so aussehen
Code:
while((s = reader.readLine()) != null)
{			
     if(s=="asdf")
     {
             System.out.println("Text asdf ");
     }
}

Nur leider funktioniert der Vergleich nicht :-(. Ich weis nicht warum vielleicht liegt es am "\n" oder so :-(

Hast du da eine Idee?



Viele liebe Grüße
 
Zurück