uio--WebPageMain-Module

JAVA-C3 Network-Programming Network Basics Grundlagen TCP UDP Datagram IP-Adressen Port listen DNS Domain-Name DNS A- und AAAA-Records CNAME-Record Reverse-DNS / PTR-Record TTL Windows: DNS und Host Windows: Firewalls und Ports HTTPS.SYS Nginx als Reverse-Proxy Binding Lokale Netzwerke und NET URI scheme host port path/file Query Fragment / Anchor (01) Hierarchischer URI (02) Opaque URI (03) URI mit Sonderzeichen (04) Normalisierung von URI (04) Normalisierung von URI Prüfen auf Gleichheit a.compare(b) a.equals(b) (05) Relative in absolute URIs übersetzen URL Encoding/Decoding URL-Encoding ASCII Unicode Codepage UTF-8 UTF-16 UTF-32 ISO-8859-1 UTF-32 URLDecoder StandardChartsets.UTF_8

Blocking-I/O Non-Blocking-I/O

network-java-sockets @! network-java-nio FEHLT

Client-Server mit Sockets Unabhängige Java-Programme in Kommunikation über Sockets Client Server @! com.stuelken.java.c1.networking.basics.sockets Version prüfen! @! com.stuelken.java.***.a7.e50200_clientserver_keyvalue.client KeyValueClientResilient class @! Das müssten 3 verschiedene Beispiele/Pages sein! @! Erfordert com.stuelken..ArgumentInterpreter! @! Sprungmarken für Client, Server, .. ergänzen

Overview

Lambda Expressions/Overview

Client-Server-Anwendung mit Sockets sind eine vergleichsweise rudimentäre Variante mit deren Hilfe man in Java aus Microservices bestehende Anwendungen programmieren kann. Das Primärziel besteht darin, dass sich mehrere Clients über eine Netzwerkverbindung mit einem Server verbinden und darauf warten können, falls der Server aktuell nicht online ist.

Wir zeigen in diesem Beispiel, wie man einen resilienten Client programmiert, welche bestimmte Aktionen auf dem Server durchführen kann, um auf dem Server Daten zu erzeugen, abzufragen, zu verändern, die zu löschen oder auch eine Liste aller Daten anzuzeigen.

Syntax

Client/Server | KeyValue-Pair-Server

Problemstellung

In diesem Beispiel soll eine einfache Client-Server-Anwendung realisiert werden, bei der ein Key-Value-Server grundlegende CRUD-Operationen (Create, Read, Update, Delete) sowie einen "LIST"-Befehl zur Anzeige aller gespeicherten Elemente anbietet. Der Client baut jeweils für jeden Befehl eine neue, zustandslose Socket-Verbindung zum Server auf.

Die zentrale Herausforderung besteht darin, eine robuste Kommunikation zu gewährleisten – selbst in Situationen, in denen der Server temporär nicht reagiert, beispielsweise während eines Neustarts oder bei Netzwerkproblemen. Zudem soll der Client dem Benutzer den aktuellen Verbindungsstatus (zum Beispiel durch fortlaufend ausgegebene Fortschrittsindikatoren) anzeigen und ihm die Möglichkeit bieten, den Verbindungsaufbau bei Bedarf abzubrechen.

Lösungsansatz

Zur Lösung dieses Problems implementiert der Client eine sogenannte zustandslose Verbindung: Für jeden einzelnen Befehl wird eine neue Verbindung zum Server aufgebaut. Ist der Server aktuell nicht erreichbar, versucht der Client wiederholt, die Verbindung herzustellen. Während dieser Wiederholungsphase wird dem Anwender ein Fortschrittsindikator angezeigt, der in regelmäßigen Intervallen einen Punkt (.) schreibt. Gleichzeitig erhält er den Hinweis, dass er durch Drücken der Taste x den Verbindungsaufbau abbrechen kann.

Neben der Implementierung einer robusten Verbindung wird der Befehlseingabeprozess flexibel gestaltet: Befehle können sowohl in Kurznotation (zum Beispiel «c») als auch ausgeschrieben (zum Beispiel «create») eingegeben werden.

Ein einfacher Beispielaufruf, um ein neues Key-Value-Paar zu erstellen, lautet:

// Beispiel: Der Client sendet den Befehl, um das Element B mit dem Wert Berlin anzulegen. out.println("CREATE B Berlin");

Diskussion

Das vorgestellte Beispiel demonstriert grundlegende Prinzipien der Client-Server-Programmierung mit Java. Durch die zustandslose Verbindungsmethode und die Wiederholungslogik beim Verbindungsaufbau wird sichergestellt, dass der Client auch in Situationen, in denen der Server nicht sofort erreichbar ist, nicht sofort mit einem Fehler terminiert, sondern wiederholt versucht, die Verbindung herzustellen.

Die Implementierung erlaubt es dem Benutzer, aktiv Einfluss auf den Verbindungsaufbau zu nehmen, indem er bei längeren Wartezeiten den Vorgang abbrechen kann. Dies entspricht einem nutzerfreundlichen Ansatz, bei dem Transparenz über den Verbindungsstatus gefördert wird.

In einem weitergehenden Szenario lassen sich Teile dieses Systems in separate Microservices auslagern – beispielsweise könnte ein LogService eingerichtet werden, der Verbindungs- und Zugriffsstatistiken sammelt, oder ein Gateway-Proxy, der Anfragen an mehrere Subsysteme verteilt. Diese Architekturansätze ermöglichen später eine höhere Skalierbarkeit und Ausfallsicherheit, ohne dass die Basisfunktionalität der Client-Server- Kommunikation beeinträchtigt wird.

KeyValueClient


 
package com.stuelken.java.c3.e50200_clientserver_keyvalue.client;

import java.net.Socket;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.io.IOException;

/**
 * Ein KeyValueClient, der mittels Sockets und Java Streams interaktiv Befehle
 * von der Konsole entgegennimmt und an den KeyValueServer sendet.
 * 
 * Verfügbare Befehle: [h] help : Zeigt dieses Menü an. [c] create key value :
 * Erzeugt ein Key-Value-Paar. [r] read key : Liest den Wert für 'key'. [u]
 * update key newValue : Aktualisiert den Wert. [d] delete key : Löscht ein
 * Key-Value-Paar. [l] list : Listet alle Key-Value-Paare.
 * 
 * Ist beim Start kein Port als Parameter angegeben, wird der Standardport 8080
 * verwendet.
 * 
 * @author t2m
 * @version 1.0
 * 
 *
 */ 
public class KeyValueClient {

	/**
	 * Es ist möglich, beim Programmaufruf den Port mit übergeben zu können.
	 * 
	 * <pre>
	 * java KeyValueClient 8080
	 * </pre>
	 * 
	 * Implementierung:
	 * <ul>
	 * <li>Instanz des Clients erzeugen</li>
	 * <li>Einen vom Standardport abweichenden Port für den Zugriff auf den
	 * Hauptserver aus der Liste der Argumente ermitteln.</li>
	 * <li>Socket initialisieren</li>
	 * <li>Server für dieses Socket starten, um Verbindung mit Server
	 * aufzunehmen.</li>
	 * </ul>
	 * 
	 * @param args
	 * @throws IOException
	 */
	public static void main(String[] args) throws IOException {

		// Instanz des Clients erzeugen
		KeyValueClient client = new KeyValueClient(); 

		// Einen vom Standardport abweichenden Port für den Zugriff auf den Hauptserver
		// aus der Liste der Argumente ermitteln.
		int port = client.parsePort(args);

		// Socket initialisieren
		Socket socket = client.initializeSocket("localhost", port);

		// Server für dieses Socket starten, um Verbindung mit Server aufzunehmen.
		client.startClient(socket);
	}

	/**
	 * Parst aus den Program-Arguments den gewünschten Port. Wird kein gültiger Port
	 * übergeben, wird 8080 als Default genutzt.
	 */
	private int parsePort(String[] args) {
		int port = 8080;
		if (args.length > 0) {
			try {
				port = Integer.parseInt(args[0]);
			} catch (NumberFormatException nfe) {
				System.out.println("Ungültiger Portwert. Verwende Standardport 8080.");
				port = 8080;
			}
		}
		return port;
	}

	/**
	 * Initialisiert und gibt eine Socket-Verbindung zum angegebenen Host und Port
	 * zurück.
	 */
	private Socket initializeSocket(String host, int port) throws IOException {
		Socket socket = new Socket(host, port);
		System.out.println("Verbunden zu Server auf Port " + port);
		return socket;
	}

	/**
	 * Startet den Client, indem er einen interaktiven Konsolen-Thread startet und
	 * die Kommunikation mit dem Server abwickelt.
	 */
	private void startClient(Socket socket) throws IOException {
		BufferedReader serverIn = new BufferedReader(new InputStreamReader(socket.getInputStream()));
		PrintWriter serverOut = new PrintWriter(socket.getOutputStream(), true);

		Thread consoleThread = new Thread(() -> {
			try (BufferedReader consoleReader = new BufferedReader(new InputStreamReader(System.in))) {
				printHelp();
				String consoleLine;
				while ((consoleLine = consoleReader.readLine()) != null) {
					if (consoleLine.trim().isEmpty()) {
						continue;
					}
					String request = buildRequest(consoleLine.trim());
					if (request.isEmpty()) {
						continue;
					}
					// Senden und die Server-Antwort verarbeiten
					serverOut.println(request);
					String response = serverIn.readLine();
					System.out.println("Antwort vom Server: " + response);
				}
			} catch (IOException e) {
				System.out.println("Fehler bei der Verarbeitung der Konsoleneingabe: " + e.getMessage());
			}
		});

		consoleThread.start();
		try {
			consoleThread.join();
		} catch (InterruptedException e) {
			System.out.println("Console-Thread wurde unterbrochen: " + e.getMessage());
		}
		socket.close();
	}

	/**
	 * Baut basierend auf der Benutzereingabe den zu sendenden Request-String.
	 * 
	 * 
	 * Implementierung über switch-Konstrukt.
	 * 
	 * <ul>
	 * <li>Wir teilen den Eingabe-String an jeder Zeichenfolge von White-Space-Characters.</li>
	 * <li>Wir wandeln die Eingabe von Kurzbefehlen in Kleinbuchstaben um</li>
	 * </ul>
	 * 
	 */
	private String buildRequest(String input) {
		
		// 
		String[] tokens = input.split("\\s+");
		
		// Kurzkommando für Befehle mit 1 Zeichen.
		char command = Character.toLowerCase(tokens[0].charAt(0));
		
		String request = "";

		switch (command) {
		case 'h': // Hilfe anzeigen
			printHelp();
			break;
		case 'c': // CREATE: c key value
			if (tokens.length >= 3) {
				request = "CREATE " + tokens[1] + " " + tokens[2];
			} else {
				System.out.println("Usage: c key value");
			}
			break;
		case 'r': // READ: r key
			if (tokens.length >= 2) {
				request = "READ " + tokens[1];
			} else {
				System.out.println("Usage: r key");
			}
			break;
		case 'u': // UPDATE: u key newValue
			if (tokens.length >= 3) {
				request = "UPDATE " + tokens[1] + " " + tokens[2];
			} else {
				System.out.println("Usage: u key newValue");
			}
			break;
		case 'd': // DELETE: d key
			if (tokens.length >= 2) {
				request = "DELETE " + tokens[1];
			} else {
				System.out.println("Usage: d key");
			}
			break;
		case 'l': // LIST: l
			request = "LIST";
			break;
		default:
			System.out.println("Unbekannter Befehl. Gib 'h' für Hilfe ein.");
		}
		return request;
	}

	/**
	 * Gibt das Hilfemenü mit allen verfügbaren Befehlen aus.
	 */
	private void printHelp() {
		System.out.println("Verfügbare Befehle:");
		System.out.println("[h] help : Zeigt dieses Menü an");
		System.out.println("[c] create key value : Erzeugt ein Key-Value-Paar");
		System.out.println("[r] read key : Liest den Wert für 'key'");
		System.out.println("[u] update key newValue : Aktualisiert den Wert für 'key'");
		System.out.println("[d] delete key : Löscht das Key-Value-Paar mit 'key'");
		System.out.println("[l] list : Listet alle Key-Value-Paare");
		System.out.println("Zum Beenden: Strg+C");
	}
}



KeyValueClientResilient


 
package com.stuelken.java.tests.a7.e50200_clientserver_keyvalue.client;

import java.net.Socket;
import java.net.SocketAddress;
import java.net.InetSocketAddress;
import java.io.*;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * Ein KeyValueClient, der für jeden Befehl jeweils eine neue, zustandslose
 * Verbindung zum Server aufbaut. Dabei wird beim Verbindungsaufbau ein
 * Fortschrittsindikator ausgegeben („connecting to server [press 'x' to stop]
 * ...“). Falls der Server aktuell nicht erreichbar ist, wird immer wieder
 * versucht, die Verbindung herzustellen. Der Benutzer kann den Versuch durch
 * Drücken von „x“ abbrechen.
 * 
 * Unterstützte Befehle (Eingabe über die Konsole): h oder help : Zeigt das
 * Hilfemenü an. c oder create key value : Erzeugt ein Key-Value-Paar. r oder
 * read key : Liest den Wert für 'key'. u oder update key newValue :
 * Aktualisiert den Wert für 'key'. d oder delete key : Löscht das
 * Key-Value-Paar mit 'key'. l oder list : Listet alle Key-Value-Paare.
 * 
 * Der zu verwendende Server-Port kann über den Parameter "-serverport <port>"
 * angegeben werden. (Standard: 8080)
 * 
 * Beispielaufruf: java -jar KeyValueClientResilient.jar -serverport 9090
 * 
 * @author t2m
 */
public class KeyValueClientResilient {

	public static void main(String[] args) {
		KeyValueClientResilient client = new KeyValueClientResilient();
		int port = client.parsePort(args);
		client.startClient(port);
	}

	/**
	 * Liest aus den übergebenen Argumenten den gewünschten Port. Sucht nach
	 * "-serverport <port>" und liefert diesen zurück oder Standard 8080.
	 */
	private int parsePort(String[] args) {
		int port = 8080;
		for (int i = 0; i < args.length; i++) {
			if (args[i].equalsIgnoreCase("-serverport") && i + 1 < args.length) {
				try {
					port = Integer.parseInt(args[i + 1]);
				} catch (NumberFormatException e) {
					System.out.println("Ungültiger Portwert. Verwende Standardport 8080.");
					port = 8080;
				}
			}
		}
		return port;
	}

	/**
	 * Startet den interaktiven Client: - Liest Eingaben von der Konsole. - Wandelt
	 * diese in einen Request-String um (unterstützt sowohl Kurzbefehle wie "c" als
	 * auch ausgeschriebene Befehle wie "create"). - Ruft für jeden Befehl
	 * sendCommandWithProgress auf.
	 */
	private void startClient(int port) {
		try (BufferedReader consoleReader = new BufferedReader(new InputStreamReader(System.in))) {
			printHelp();
			String line;
			while ((line = consoleReader.readLine()) != null) {
				String trimmed = line.trim();
				if (trimmed.isEmpty()) {
					continue;
				}
				String request = buildRequest(trimmed);
				if (request.isEmpty()) {
					continue;
				}
				String response = sendCommandWithProgress(request, port);
				System.out.println("Server Response: " + response);
			}
		} catch (IOException e) {
			System.out.println("Fehler bei der Konsoleneingabe: " + e.getMessage());
		}
	}

	/**
	 * Wandelt die Benutzereingabe in einen Request um. Unterstützt Befehle: - "c"
	 * oder "create" - "r" oder "read" - "u" oder "update" - "d" oder "delete" - "l"
	 * oder "list" - "h" oder "help" sorgt dafür, dass das Hilfemenü angezeigt wird.
	 */
	private String buildRequest(String input) {
		String[] tokens = input.split("\\s+");
		String commandToken = tokens[0].toLowerCase();
		String request = "";
		if (commandToken.equals("h") || commandToken.equals("help")) {
			printHelp();
		} else if (commandToken.equals("c") || commandToken.equals("create")) {
			if (tokens.length >= 3)
				request = "CREATE " + tokens[1] + " " + tokens[2];
			else
				System.out.println("Usage: create key value");
		} else if (commandToken.equals("r") || commandToken.equals("read")) {
			if (tokens.length >= 2)
				request = "READ " + tokens[1];
			else
				System.out.println("Usage: read key");
		} else if (commandToken.equals("u") || commandToken.equals("update")) {
			if (tokens.length >= 3)
				request = "UPDATE " + tokens[1] + " " + tokens[2];
			else
				System.out.println("Usage: update key newValue");
		} else if (commandToken.equals("d") || commandToken.equals("delete")) {
			if (tokens.length >= 2)
				request = "DELETE " + tokens[1];
			else
				System.out.println("Usage: delete key");
		} else if (commandToken.equals("l") || commandToken.equals("list")) {
			request = "LIST";
		} else {
			System.out.println("Unbekannter Befehl. Gib 'help' für Hilfe ein.");
		}
		return request;
	}

	/**
	 * Baut für den übergebenen Request eine zustandslose Verbindung zum Server auf
	 * und versucht wiederholt, diese herzustellen – bis eine Verbindung gelingt
	 * oder der Benutzer durch Drücken von "x" den Vorgang abbricht. Währenddessen
	 * wird ein Fortschrittsindikator ausgegeben.
	 * 
	 * @param command Der Request-String (z. B. "CREATE myKey myValue")
	 * @param port Der Server-Port
	 * @return Die vom Server empfangene Antwort oder eine Fehlermeldung.
	 */
	public String sendCommandWithProgress(String command, int port) {
		AtomicBoolean success = new AtomicBoolean(false);
		AtomicBoolean cancelRequested = new AtomicBoolean(false);
		final String[] responseHolder = new String[1];

		// Verbindung wird in einer Schleife wiederholt versucht.
		Thread connectionThread = new Thread(() -> {
			while (!cancelRequested.get() && !success.get()) {
				try {
					Socket socket = new Socket();
					SocketAddress address = new InetSocketAddress("localhost", port);
					// Kurzer Timeout (z. B. 5 Sekunden) pro Verbindungsversuch
					socket.connect(address, 5000);
					success.set(true);
					PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
					BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
					out.println(command);
					responseHolder[0] = in.readLine();
					socket.close();
				} catch (IOException e) {
					// Falls Verbindung fehlschlägt, warten und dann erneut versuchen.
					try {
						Thread.sleep(2000); // 2 Sekunden warten
					} catch (InterruptedException ie) {
						// Abbruchsignal eventuell weiterreichen
						break;
					}
				}
			}
		});
		connectionThread.start();

		// Fortschrittsanzeige: Gibt jede Sekunde einen Punkt aus und prüft, ob der
		// Benutzer "x" eingibt.
		Thread progressThread = new Thread(() -> {
			System.out.print("connecting to server [press 'x' to stop] ");
			try {
				while (!success.get() && connectionThread.isAlive()) {
					if (System.in.available() > 0) {
						int input = System.in.read();
						if (input == 'x' || input == 'X') {
							cancelRequested.set(true);
							connectionThread.interrupt();
							System.out.println("\nConnection attempt cancelled by user.");
							return;
						}
					}
					System.out.print(".");
					Thread.sleep(1000);
				}
			} catch (IOException | InterruptedException ex) {
				// Bei Exception beenden
			}
		});
		progressThread.setDaemon(true);
		progressThread.start();

		try {
			connectionThread.join();
			progressThread.interrupt();
			progressThread.join();
		} catch (InterruptedException ex) {
			// Ausnahmebehandlung
		}

		if (cancelRequested.get())
			return "Aborted";
		if (success.get()) {
			System.out.println("\nconnecting to server ... SUCCESS");
			System.out.println("answer received ... SUCCESS");
			System.out.println("connection terminated.");
			return responseHolder[0];
		} else {
			System.out.println("\nconnection attempt failed.");
			return "Connection attempt failed";
		}
	}

	/**
	 * Zeigt das Hilfemenü mit allen verfügbaren Befehlen auf der Konsole an.
	 */
	private void printHelp() {
		System.out.println("Verfügbare Befehle:");
		System.out.println("[h] oder [help] : Zeigt dieses Menü an");
		System.out.println("[c] oder [create] key value : Erzeugt ein Key-Value-Paar");
		System.out.println("[r] oder [read] key : Liest den Wert für 'key'");
		System.out.println("[u] oder [update] key newValue : Aktualisiert den Wert für 'key'");
		System.out.println("[d] oder [delete] key : Löscht das Key-Value-Paar für 'key'");
		System.out.println("[l] oder [list] : Listet alle Key-Value-Paare");
		System.out.println("Zum Beenden: Strg+C");
	}
}



Output des Clients

Der nachfolgende Code zeigt ein Beispiel, wie man mit dem Client Datensätze erzeugen kann.

Es werden alle Funktionen einmal ausprobiert, dh. für B wird von Berlin auf Brandenburg geändert, dann H für Hannhover ergänzt und zuletzt B wieder entfernt.


 
Verfügbare Befehle:
[h] oder [help] : Zeigt dieses Menü an
[c] oder [create] key value : Erzeugt ein Key-Value-Paar
[r] oder [read] key : Liest den Wert für 'key'
[u] oder [update] key newValue : Aktualisiert den Wert für 'key'
[d] oder [delete] key : Löscht das Key-Value-Paar für 'key'
[l] oder [list] : Listet alle Key-Value-Paare
Zum Beenden: Strg+C
c B
Usage: create key value
c B Berlin
connecting to server [press 'x' to stop] .
connecting to server ... SUCCESS
answer received ... SUCCESS
connection terminated.
Server Response: Element erstellt
r B 
connecting to server [press 'x' to stop] .
connecting to server ... SUCCESS
answer received ... SUCCESS
connection terminated.
Server Response: Wert: Berlin
u B Brandenburg
connecting to server [press 'x' to stop] .
connecting to server ... SUCCESS
answer received ... SUCCESS
connection terminated.
Server Response: Element aktualisiert
r B
connecting to server [press 'x' to stop] .
connecting to server ... SUCCESS
answer received ... SUCCESS
connection terminated.
Server Response: Wert: Brandenburg
l
connecting to server [press 'x' to stop] .
connecting to server ... SUCCESS
answer received ... SUCCESS
connection terminated.
Server Response: {"elements": [{"key": "B", "value": "Brandenburg"}]}
c H Hannover
connecting to server [press 'x' to stop] .
connecting to server ... SUCCESS
answer received ... SUCCESS
connection terminated.
Server Response: Element erstellt
l
connecting to server [press 'x' to stop] .
connecting to server ... SUCCESS
answer received ... SUCCESS
connection terminated.
Server Response: {"elements": [{"key": "B", "value": "Brandenburg"},{"key": "H", "value": "Hannover"}]}
d B
connecting to server [press 'x' to stop] .
connecting to server ... SUCCESS
answer received ... SUCCESS
connection terminated.
Server Response: Element geloescht
l
connecting to server [press 'x' to stop] .
connecting to server ... SUCCESS
answer received ... SUCCESS
connection terminated.
Server Response: {"elements": [{"key": "H", "value": "Hannover"}]}



KeyValueElement


 
package com.stuelken.java.c3.e50200_clientserver_keyvalue.model;

/**
 * Diese Klasse dient dazu, Daten in einem Objekt speichern zu können.
 * 
 * @author t2m
 */
public class KeyValueElement {

	/**
	 * Damit das Objekt seinen eigenen bevorzugen Key/Schlüssel kennt, wurde diese
	 * key-Eigenschaft geschaffen. Der key-Wert könnte z. B. "A27" oder "yolo"
	 * heißen.
	 */
	private String key;

	/**
	 * Der Wert für ein Key-Value-Paar wird als String definiert und kann
	 * beispielsweise "Fischfutter" oder "public static void main" heißen.
	 */
	private String value;

	/**
	 * Konstruktor mit Parametern
	 * 
	 * @param key Der Schlüssel unter welchem dieses Schlüssel-Wert-Paar gern
	 * abgelegt werden möchte.
	 * @param value Der Wert als String wie beispielsweise "Fischfutter" oder
	 * "public static void main" als Zeichenfolge.
	 */
	public KeyValueElement(String key, String value) {
		this.key = key;
		this.value = value;
	}

	/**
	 * Getter für den Schlüssel
	 * 
	 * @return
	 */
	public String getKey() {
		return key;
	}

	/**
	 * Getter für den Wert
	 * 
	 * @return
	 */
	public String getValue() {
		return value;
	}

	/**
	 * Setter für die Option, den Wert für dieses Schlüssel-Wert-Paar verändern zu
	 * können.
	 * 
	 * @param value
	 */
	public void setValue(String value) {
		this.value = value;
	}

}

Prinzip für XML Syntax

Auch wenn wir jetzt kein XML verwenden: So oder so ähnlich könnte der XML Code aussehen, wenn man die Datenstruktur im KeyValue-Storage in XML serialisieren wollte.

Der String-Wert in Value sollte aber faktisch in CDATA Blöcke eingefasst werden, um Sonderzeichen-Problematiken entschärfen zu können.


 
<KeyValueDoc>
 <Storage>
 <Element key="BeispielKey">
 <Value>BeispielWert</Value>
 </Element>
 <!-- weitere Elemente -->
 </Storage>
</KeyValueDoc>

KeyValueStorage


 
package com.stuelken.java.tests.a7.e50200_clientserver_keyvalue.model;

//KeyValueStorage.java – in com.stuelken.java.a7.epsilon.model
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * Diese Klasse bietet einen Speicher für {@link KeyValueElement} Objekte mit
 * einem einfachen CRUD Interface für Operationen wie CREATE, R
 * 
 * @author t2m
 * @version 1.0
 *
 */
public class KeyValueStorage {

	/**
	 * Eine LinkedHashMap über welche {@link KeyValueElement} Instanzen in
	 * Verbindung mit einem String als Schlüssel gespeichert werden, um diese
	 * Elemente über diesen Schlüssel später wie lesen, ändern oder löschen zu
	 * können.
	 */
	private Map<String, KeyValueElement> storage = new LinkedHashMap<>();

	/**
	 * Anlegen eines neuen {@link KeyValueElement} Eintrags unter einem Schlüssel
	 * key im Speicher.
	 * 
	 * @param key Ein String wie beispielsweise "A21" oder oder "Zulu".
	 * @param value Ein {@link KeyValueElement} Objekt.
	 */
	public void create(String key, String value) {
		storage.put(key, new KeyValueElement(key, value));
	}

	/**
	 * Lesen des Wertes für einen Key sofern der Wert besteht.
	 * 
	 * @param key Der Schlüssel für den Wert
	 * @return
	 */
	public String read(String key) {
		KeyValueElement element = storage.get(key);
		return (element != null) ? element.getValue() : null;
	}

	/**
	 * Aktualisieren des Wertes für einen bestimmten Schlüssel.
	 * 
	 * @param key Der Schlüssel für den Wert
	 * @param newValue Der neue Wert für den Schlüssel.
	 */
	public void update(String key, String newValue) {
		KeyValueElement element = storage.get(key);
		if (element != null) {
			element.setValue(newValue);
		}
	}

	/**
	 * Entfernen eines Eintrags.
	 * 
	 * @param key Der Schlüssel für den zu entfernenden Eintrag
	 */
	public void delete(String key) {
		storage.remove(key);
	}

	/**
	 * Eine Methode mit welcher eine Liste aller Elemente hier als Map geliefert
	 * wird.
	 * 
	 * @return
	 */
	public Map<String, KeyValueElement> list() {
		return storage;
	}
}

KeyValueElement


 
package com.stuelken.java.tests.a7.e50200_clientserver_keyvalue.model;

/**
 * Diese Klasse dient dazu, Daten in einem Objekt speichern zu können.
 * 
 * @author t2m
 */
public class KeyValueElement {

	/**
	 * Damit das Objekt seinen eigenen bevorzugen Key/Schlüssel kennt, wurde diese
	 * key-Eigenschaft geschaffen. Der key-Wert könnte z. B. "A27" oder "yolo"
	 * heißen.
	 */
	private String key;

	/**
	 * Der Wert für ein Key-Value-Paar wird als String definiert und kann
	 * beispielsweise "Fischfutter" oder "public static void main" heißen.
	 */
	private String value;

	/**
	 * Konstruktor mit Parametern
	 * 
	 * @param key Der Schlüssel unter welchem dieses Schlüssel-Wert-Paar gern
	 * abgelegt werden möchte.
	 * @param value Der Wert als String wie beispielsweise "Fischfutter" oder
	 * "public static void main" als Zeichenfolge.
	 */
	public KeyValueElement(String key, String value) {
		this.key = key;
		this.value = value;
	}

	/**
	 * Getter für den Schlüssel
	 * 
	 * @return
	 */
	public String getKey() {
		return key;
	}

	/**
	 * Getter für den Wert
	 * 
	 * @return
	 */
	public String getValue() {
		return value;
	}

	/**
	 * Setter für die Option, den Wert für dieses Schlüssel-Wert-Paar verändern zu
	 * können.
	 * 
	 * @param value
	 */
	public void setValue(String value) {
		this.value = value;
	}

}



AdminCommandHandler

AdminCommandHandler empfängt administrative Befehle von der Konsole, um den Server dynamisch zu steuern (halt, resume, restart, shutdown).


 
package com.stuelken.java.tests.a7.e50200_clientserver_keyvalue.server;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

/**
 * AdminCommandHandler empfängt administrative Befehle von der Konsole, um den
 * Server dynamisch zu steuern (halt, resume, restart, shutdown).
 * 
 * Wir 
 * 
 * Unterstuetzte Befehle:
 * 
 * <ul>
 * <li><strong>halt</strong> - Stoppt die Annahme neuer Verbindungen.</li>
 * <li><strong>resume</strong> - Erlaubt wieder die Annahme neuer
 * Verbindungen.</li>
 * <li><strong>restart [port]</strong> - Startet den Server neu, optional mit
 * neuem Port.</li>
 * <li><strong>shutdown</strong> oder <strong>end</strong> - Beendet den
 * Server.</li>
 * <li><strong>help</strong> - Zeigt die Admin-Hilfe an.</li>
 * </ul>
 * 
 * @author t2m
 * @version 1.0
 * 
 * @see KeyValueServer
 * 
 */
class AdminCommandHandler implements Runnable {
	private final KeyValueServer server;

	/**
	 * Konstruktor.
	 * 
	 * @param server Die Serverinstanz, die durch administrative Befehle gesteuert
	 * wird.
	 */
	public AdminCommandHandler(KeyValueServer server) {
		this.server = server;
	}

	/**
	 * Liest Befehle von der Konsole und f�hrt die entsprechenden Aktionen am Server
	 * aus.
	 */
	@Override
	public void run() {
		try (BufferedReader consoleReader = new BufferedReader(new InputStreamReader(System.in))) {
			printAdminHelp();
			String commandLine;
			while ((commandLine = consoleReader.readLine()) != null) {
				String[] parts = commandLine.trim().split("\\s+");
				if (parts.length == 0) {
					continue;
				}
				String cmd = parts[0].toLowerCase();
				if (cmd.equals("halt")) {
					server.haltAccepting();
				} else if (cmd.equals("resume")) {
					server.resumeAccepting();
				} else if (cmd.equals("restart")) {
					int newPort = -1;
					if (parts.length > 1) {
						try {
							newPort = Integer.parseInt(parts[1]);
						} catch (NumberFormatException e) {
							System.out.println("Ungueltiger Port, Neustart erfolgt mit dem aktuellen Port.");
						}
					}
					server.restart(newPort);
				} else if (cmd.equals("shutdown") || cmd.equals("end")) {
					server.shutdown();
					break;
				} else if (cmd.equals("help")) {
					printAdminHelp();
				} else {
					System.out.println("Unbekannter Befehl. Gib 'help' fuer Admin-Hilfe ein.");
				}
			}
		} catch (IOException e) {
			System.out.println("Fehler im AdminCommandHandler: " + e.getMessage());
		}
	}

	/**
	 * Zeigt die verfuegbaren administrativen Befehle an.
	 */
	private void printAdminHelp() {
		System.out.println("Admin-Befehle:");
		System.out.println(" halt - Stoppt die Annahme neuer Verbindungen.");
		System.out.println(" resume - Erlaubt wieder die Annahme neuer Verbindungen.");
		System.out.println(" restart [port] - Startet den Server neu, optional mit neuem Port.");
		System.out.println(" shutdown/end - Beendet den Server.");
		System.out.println(" help - Zeigt diese Admin-Hilfe an.");
	}
}




KeyValueRequestHandler


 
package com.stuelken.java.tests.a7.e50200_clientserver_keyvalue.server;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Map;

import com.stuelken.java.tests.a7.e50200_clientserver_keyvalue.model.KeyValueElement;
import com.stuelken.java.tests.a7.e50200_clientserver_keyvalue.model.KeyValueStorage;

/**
 * KeyValueRequestHandler verarbeitet einzelne Client-Anfragen.
 * <p>
 * Die erwarteten Request-Formate sind:
 * 
 * <pre>
 * CREATE key value
 * READ key
 * UPDATE key newValue
 * DELETE key
 * LIST
 * </pre>
 * </p>
 * 
 * @see KeyValueServer
 */
public class KeyValueRequestHandler implements Runnable {
	private final Socket socket;
	private final KeyValueStorage storage;

	/**
	 * Konstruktor.
	 * 
	 * @param socket Das Socket, ueber das der Client verbunden ist.
	 * @param storage Die Storage-Instanz, die die Key-Value-Elemente verwaltet.
	 */
	public KeyValueRequestHandler(Socket socket, KeyValueStorage storage) {
		this.socket = socket;
		this.storage = storage;
	}

	/**
	 * Liest den Request vom Client, verarbeitet ihn und sendet die Antwort zurueck.
	 */
	@Override
	public void run() {
		try (BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
				PrintWriter out = new PrintWriter(socket.getOutputStream(), true)) {

			String request = in.readLine();
			String response = processRequest(request);
			out.println(response);
		} catch (IOException e) {
			System.out.println("Fehler in KeyValueRequestHandler: " + e.getMessage());
		} finally {
			try {
				socket.close();
			} catch (IOException e) {
				// Fehler beim Schliessen ignorieren.
			}
		}
	}

	/**
	 * Verarbeitet den vom Client uebermittelten Request-String.
	 * 
	 * Unterstuetzte Befehle:
	 * 
	 * <pre>
	 * CREATE key value
	 * READ key
	 * UPDATE key newValue
	 * DELETE key
	 * LIST
	 * </pre>
	 * 
	 * @param request Die Anfrage des Clients.
	 * @return Die Antwort, die an den Client zurueckgesendet wird.
	 */
	private String processRequest(String request) {
		String[] parts = request.split(" ");
		if (parts.length < 1) {
			return "Ungueltige Anfrage";
		}
		String command = parts[0].toUpperCase();
		if (command.equals("CREATE")) {
			if (parts.length >= 3) {
				storage.create(parts[1], parts[2]);
				return "Element erstellt";
			}
			return "Usage: CREATE key value";
		} else if (command.equals("READ")) {
			if (parts.length >= 2) {
				String value = storage.read(parts[1]);
				return "Wert: " + value;
			}
			return "Usage: READ key";
		} else if (command.equals("UPDATE")) {
			if (parts.length >= 3) {
				storage.update(parts[1], parts[2]);
				return "Element aktualisiert";
			}
			return "Usage: UPDATE key newValue";
		} else if (command.equals("DELETE")) {
			if (parts.length >= 2) {
				storage.delete(parts[1]);
				return "Element geloescht";
			}
			return "Usage: DELETE key";
		} else if (command.equals("LIST")) {
			Map<String, KeyValueElement> map = storage.list();
			return this.convertMapToJson(map);
		}
		return "Unbekannter Befehl";
	}

	/**
	 * Wandelt die gegebene Map, die KeyValue-Elemente enth�lt, in einen JSON-String
	 * um. Das Ergebnis hat die Form:
	 * 
	 * <pre>
	 * {"elements": [{"key": "key1", "value": "value1"}, {"key": "key2", "value": "value2"}]}
	 * </pre>
	 * 
	 * @param map Die Map mit den KeyValue-Elementen.
	 * @return Ein JSON-String, der die Elemente repr�sentiert.
	 */
	private String convertMapToJson(Map<String, KeyValueElement> map) {
		StringBuilder json = new StringBuilder();
		json.append("{\"elements\": [");
		boolean first = true;
		for (Map.Entry<String, KeyValueElement> entry : map.entrySet()) {
			if (!first) {
				json.append(",");
			}
			json.append("{\"key\": \"").append(entry.getKey()).append("\", ");
			json.append("\"value\": \"").append(entry.getValue().getValue()).append("\"}");
			first = false;
		}
		json.append("]}");
		return json.toString();
	}

}


UI ORGANIZED.

UIO3 Es ist einfacher als Du denkst.

Stelle noch heute Deine Anfrage.

uio--WebPageFooter-Module