CS_LEVEL_23_SOLUTION - OnlyCook/abitur-elite-code GitHub Wiki

Level 23 – Musterlösung: Das Sicherheitssystem

Lösung

public class SicherheitsServer
{
    private int port;
    private SicherheitsZentrale zentrale;
    private ServerSocket serverSocket;
 
    public SicherheitsServer(int port, SicherheitsZentrale z)
    {
        this.port = port;
        this.zentrale = z;
        this.serverSocket = new ServerSocket(port);
    }
 
    public void RunServer()
    {
        while (true)
        {
            Socket socket = serverSocket.Accept();
            new SicherheitsThread(socket, zentrale).Start();
        }
    }
}
 
public class SicherheitsThread : Thread
{
    private Socket clientSocket;
    private SicherheitsZentrale zentrale;
 
    public SicherheitsThread(Socket cs, SicherheitsZentrale z)
    {
        this.clientSocket = cs;
        this.zentrale = z;
    }
 
    public override void Run()
    {
        string befehl = clientSocket.ReadLine();
        if (befehl.StartsWith("LOGIN;"))
        {
            string[] parts = befehl.Split(';');
            bool ok = VergleicheZugangsdaten(parts[1], parts[2]);
            if (ok)
            {
                clientSocket.Write("+OK Willkommen\n");
 
                while (befehl != "QUIT")
                {
                    befehl = clientSocket.ReadLine();
                    switch (befehl)
                    {
                        case "STATUS":
                            Alarmanlage alarmanlage = zentrale.GetAlarmanlage();
                            bool aktiv = alarmanlage.GetAktiv();
                            string msg = aktiv ? "ON" : "OFF";
                            clientSocket.Write("+OK ALARM_" + msg + "\n");
                            break;
                        case "TOGGLE":
                            alarmanlage = zentrale.GetAlarmanlage();
                            alarmanlage.SetAktiv(!alarmanlage.GetAktiv());
                            string log = "Alarmanlage umgeschaltet";
                            zentrale.GetLog().GetEintraege().Add(log);
                            clientSocket.Write("+OK Umschaltung erfolgreich\n");
                            break;
                        case "QUIT":
                            clientSocket.Write("+OK Bye\n");
                            break;
                    }
                }
            }
            else
            {
                clientSocket.Write("-ERR Login fehlgeschlagen\n");
            }
        }
        clientSocket.Close();
    }
 
    private bool VergleicheZugangsdaten(string user, string pin)
    {
        BenutzerVerwaltung verwaltung = zentrale.GetVerwaltung();
        if (verwaltung.GetAdminUser() == user)
        {
            if (verwaltung.GetAdminPin() == pin)
            {
                return true;
            }
        }
        return false;
    }
}

Erklärung

Abschluss von Sektion 5

Dieses Level kombiniert alles aus der Sektion: Threads, Socket-Kommunikation, Protokollverarbeitung und Objektzugriff über mehrere Ebenen. Es gibt keine wirklich neuen Konzepte – dafür ist die Eigenständigkeit gefragt, alles sauber zusammenzusetzen.


SicherheitsServer – Kurzform beim Thread-Start

new SicherheitsThread(socket, zentrale).Start();

Statt das Objekt erst in einer Variable zu speichern und dann Start() aufzurufen, wird beides direkt in einer Zeile erledigt. Das funktioniert, weil new SicherheitsThread(...) das neue Objekt zurückgibt und man sofort eine Methode darauf aufrufen kann. Diese Variante ist kompakt und im Abitur durchaus verbreitet.


Run() – der Ablauf aus dem Sequenzdiagramm

Login-Parsing mit Split

string befehl = clientSocket.ReadLine();
if (befehl.StartsWith("LOGIN;"))
{
    string[] parts = befehl.Split(';');
    bool ok = VergleicheZugangsdaten(parts[1], parts[2]);

Der erste eingehende Befehl wird direkt vor der Schleife gelesen – das Sequenzdiagramm zeigt, dass der Client als allererstes eine Login-Anfrage schickt. Split(';') zerlegt "LOGIN;admin;admin123" in drei Teile: parts[0] = "LOGIN", parts[1] = "admin", parts[2] = "admin123". Benutzername und PIN werden direkt an VergleicheZugangsdaten übergeben.

Ternärer Operator und String-Zusammensetzung

bool aktiv = alarmanlage.GetAktiv();
string msg = aktiv ? "ON" : "OFF";
clientSocket.Write("+OK ALARM_" + msg + "\n");

aktiv ? "ON" : "OFF" ist der ternäre Operator – eine Kurzform für if/else, die direkt einen Wert zurückgibt. Die Syntax lautet: Bedingung ? WertWennWahr : WertWennFalsch. Hier: ist aktiv wahr, wird "ON" zugewiesen, sonst "OFF".

Der fertige String wird dann mit + zusammengesetzt: "+OK ALARM_" + "ON" ergibt "+OK ALARM_ON". Das ist völlig gleichwertig zu einem String-Interpolationsausdruck wie $"+OK ALARM_{msg}\n" – beide Schreibweisen sind korrekt.

Falls man sich jedoch mit so einem Beispiel unsicher ist, kann man auch einfach folgendes tun:

bool aktiv = alarmanlage.GetAktiv();
if (aktiv)
{
    clientSocket.Write("+OK ALARM_ON\n");
}
else
{
    clientSocket.Write("+OK ALARM_OFF\n");
}

Objektzugriff über mehrere Ebenen

zentrale.GetLog().GetEintraege().Add(log);

Hier wird keine neue Variable für jedes Zwischenergebnis angelegt – stattdessen werden die Methodenaufrufe direkt verkettet. Das funktioniert, weil jede Methode ein Objekt zurückgibt, auf dem man sofort die nächste Methode aufrufen kann:

  1. zentrale.GetLog() → liefert das ProtokollLog-Objekt der Zentrale
  2. .GetEintraege() → liefert die List<string> der Einträge in diesem Log
  3. .Add(log) → fügt den neuen Eintrag zur Liste hinzu

Man kann sich das wie eine Kette vorstellen: jeder Pfeil im Sequenzdiagramm, der tiefer ins Objektgeflecht führt, entspricht einem weiteren .-Aufruf im Code. Das Ergebnis ist kürzer als drei separate Variablendeklarationen – aber nur dann sinnvoll, wenn man das Zwischenergebnis (also GetLog() oder GetEintraege()) danach nicht nochmal braucht.

Wer es lieber wie gewohnt machen will oder nicht versteht, kann einfach folgendes nutzen oder einsehen:

ProtokollLog pLog = zentrale.GetLog();
List<string> eintraege = pLog.GetEintraege();
eintraege.Add(log);

Wie in Mathe kann man sich hier vorstellen:
pLog = zentrale.GetLog()pLog ist dasselbe wie zentrale.GetLog(),
eintraege = pLog.GetEintraege()eintraege ist dasselbe wie zentrale.GetLog().GetEintraege(),
also ist danach eintraege.Add(log) exakt gleichwertig wie zentrale.GetLog().GetEintraege().Add(log).


VergleicheZugangsdaten – verschachtelte Prüfung

private bool VergleicheZugangsdaten(string user, string pin)
{
    BenutzerVerwaltung verwaltung = zentrale.GetVerwaltung();
    if (verwaltung.GetAdminUser() == user)
    {
        if (verwaltung.GetAdminPin() == pin)
        {
            return true;
        }
    }
    return false;
}

Die Methode greift über zentrale.GetVerwaltung() auf die BenutzerVerwaltung zu und vergleicht dort Benutzername und PIN getrennt. Das abschließende return false am Ende greift immer dann, wenn einer der beiden Checks fehlschlägt – es muss also nicht explizit in einem else-Zweig stehen. Diese Schreibweise – erst alle Erfolgsbedingungen prüfen, am Ende pauschal false zurückgeben – ist sauber und lesbar.

Wer einen One-Liner haben will:

private bool VergleicheZugangsdaten(string user, string pin) => zentrale.GetVerwaltung().GetAdminUser() == user && zentrale.GetVerwaltung().GetAdminPin() == pin ? true : false;
⚠️ **GitHub.com Fallback** ⚠️