Hotelbuchungen
Kurz gesagt: Ein Projekt für Hotelbuchungen: Ein Server und viele Clients buchen, prüfen und stornieren gleichzeitig Zimmer, und wenn zwei im selben Moment dasselbe Zimmer wollen, bekommt es nur einer.
Problem
Ein Zimmer, zwei Bestätigungen
Ein kleines Hotel hat vier Zimmer. Buchungen kommen von überall: von der Website, von der Rezeption, von Reisebüros. An einem Freitagabend buchen zwei Agenturen in derselben Sekunde das letzte Doppelzimmer. Beide bekommen eine Bestätigung. Am Samstag stehen zwei Familien vor dem Zimmer.
Wir bauen ein System, in dem der Server die Zimmer und die Buchungen verwaltet und die Clients Anfragen über das Netz schicken. Ein Client kann:
- die freien Zimmer sehen;
- ein Zimmer prüfen;
- ein Zimmer auf seinen Namen buchen;
- seine eigene Buchung stornieren.
Und wenn zwei Clients gleichzeitig dasselbe Zimmer buchen, hat nur einer Erfolg. Das beweisen wir mit einem Programm, das bei jedem Lauf dieselbe Ausgabe liefert.
Wir bauen von unten nach oben: das Modell ohne Netz, die Regeln und die Fehler, das Protokoll, der Server und der Client, die gleichzeitigen Anfragen und am Ende alles in einem Programm.
Genauer, für Neugierige
node:net). Es gibt aber nur einen Thread: Node.js bedient darin alle Clients und wechselt von einem zum anderen, während sie auf das Netz warten. Einen Lock gibt es nicht. Was die Zimmer dann schützt, sehen wir gegen Ende.Die Idee
Das Zimmer
Ein Zimmer hat eine Nummer, einen Typ, einen Preis pro Nacht und einen Zustand: frei oder belegt.
class Room { private final int number; private final String type; private final int price; private boolean free = true;
public Room(int number, String type, int price) { this.number = number; this.type = type; this.price = price; }
public int getNumber() { return number; }
public String getType() { return type; }
public int getPrice() { return price; }
public boolean isFree() { return free; }
public void occupy() { free = false; }
public void release() { free = true; }
@Override public String toString() { return number + ", " + type + ", " + price + " € pro Nacht, " + (free ? "frei" : "belegt"); }}public class Room{ public int Number { get; } public string Type { get; } public int Price { get; } public bool IsFree { get; set; } = true;
public Room(int number, string type, int price) { Number = number; Type = type; Price = price; }
public void Occupy() { IsFree = false; }
public void Release() { IsFree = true; }
public override string ToString() { return Number + ", " + Type + ", " + Price + " € pro Nacht, " + (IsFree ? "frei" : "belegt"); }}class Room: def __init__(self, number, kind, price): self._number = number self._kind = kind self._price = price self._free = True
@property def number(self): return self._number
@property def kind(self): return self._kind
@property def price(self): return self._price
@property def is_free(self): return self._free
def occupy(self): self._free = False
def release(self): self._free = True
def __str__(self): state = "frei" if self._free else "belegt" return f"{self._number}, {self._kind}, {self._price} € pro Nacht, {state}"class Room { #number; #type; #price; #free = true;
constructor(number, type, price) { this.#number = number; this.#type = type; this.#price = price; }
get number() { return this.#number; }
get type() { return this.#type; }
get price() { return this.#price; }
get isFree() { return this.#free; }
occupy() { this.#free = false; }
release() { this.#free = true; }
toString() { const state = this.#free ? "frei" : "belegt"; return `${this.#number}, ${this.#type}, ${this.#price} € pro Nacht, ${state}`; }}- Nummer, Typ und Preis ändern sich nicht. Nur der Zustand ändert sich, und zwar nur über zwei Methoden mit klaren Namen:
occupyundrelease. - Das Zimmer weiß nicht, wer darin wohnt. Das Zimmer geht an jeden Client, der fragt, und der Name des Gastes soll das Hotel nicht verlassen.
Genauer, für Neugierige
# sind privat: Von außen sind sie gar nicht sichtbar. Nummer, Typ und Preis werden über einen Getter ohne Klammern gelesen (room.number), und eine Änderung von außen (room.number = 5) bricht in einer Datei mit import mit TypeError ab, weil der Getter keinen Setter hat. JavaScript hat keine Aufzählungen (Enums). Für mehr als zwei Zustände nimmt man ein eingefrorenes Objekt mit ihren Namen, wie Protocol weiter unten.Sieh es dir an
Das Hotel und die Buchung
Map „Zimmernummer → Name“:private final List<Room> rooms = new ArrayList<>();private final Map<Integer, String> guests = new HashMap<>();private readonly List<Room> rooms = new List<Room>();private readonly Dictionary<int, string> guests = new Dictionary<int, string>();def __init__(self): self._rooms = [] self._guests = {}#rooms = [];#guests = new Map();public void add(Room room) { rooms.add(room);}
public List<Room> freeRooms() { return rooms.stream().filter(Room::isFree).toList();}
public Room check(int number) { return find(number);}public void Add(Room room){ rooms.Add(room);}
public List<Room> FreeRooms(){ return rooms.Where(room => room.IsFree).ToList();}
public Room Check(int number){ return Find(number);}def add(self, room): self._rooms.append(room)
def free_rooms(self): return [room for room in self._rooms if room.is_free]
def check(self, number): return self._find(number)add(room) { this.#rooms.push(room);}
freeRooms() { return this.#rooms.filter((room) => room.isFree);}
check(number) { return this.#find(number);}#find ist privat: Sie sucht das Zimmer nach Nummer oder wirft eine Exception. So kann niemand von außen ein Zimmer nehmen und es an den Regeln vorbei belegen:
private Room find(int number) { for (Room room : rooms) { if (room.getNumber() == number) { return room; } } throw new RoomNotFoundException("Es gibt kein Zimmer " + number + ".");}private Room Find(int number){ Room room = rooms.FirstOrDefault(r => r.Number == number); if (room == null) { throw new RoomNotFoundException("Es gibt kein Zimmer " + number + "."); } return room;}def _find(self, number): for room in self._rooms: if room.number == number: return room raise RoomNotFoundError(f"Es gibt kein Zimmer {number}.")#find(number) { const room = this.#rooms.find((r) => r.number === number); if (room === undefined) { throw new RoomNotFoundError(`Es gibt kein Zimmer ${number}.`); } return room;}Die Buchung prüft den Namen, findet das Zimmer und prüft, ob es frei ist. Erst dann belegt sie es:
public void reserve(int number, String guest) { if (guest.isBlank()) { throw new HotelException("Der Name des Gastes ist Pflicht."); } Room room = find(number); if (!room.isFree()) { throw new RoomTakenException("Zimmer " + number + " ist schon belegt."); } room.occupy(); guests.put(number, guest);}public void Reserve(int number, string guest){ if (string.IsNullOrWhiteSpace(guest)) { throw new HotelException("Der Name des Gastes ist Pflicht."); } Room room = Find(number); if (!room.IsFree) { throw new RoomTakenException("Zimmer " + number + " ist schon belegt."); } room.Occupy(); guests[number] = guest;}def reserve(self, number, guest): if not guest.strip(): raise HotelError("Der Name des Gastes ist Pflicht.") room = self._find(number) if not room.is_free: raise RoomTakenError(f"Zimmer {number} ist schon belegt.") room.occupy() self._guests[number] = guestreserve(number, guest) { if (guest.trim() === "") { throw new HotelError("Der Name des Gastes ist Pflicht."); } const room = this.#find(number); if (!room.isFree) { throw new RoomTakenError(`Zimmer ${number} ist schon belegt.`); } room.occupy(); this.#guests.set(number, guest);}Hotel hotel = new Hotel();hotel.add(new Room(101, "Einzelzimmer", 60));hotel.add(new Room(102, "Doppelzimmer", 85));hotel.add(new Room(201, "Doppelzimmer", 95));hotel.add(new Room(202, "Suite", 140));
for (Room room : hotel.freeRooms()) { System.out.println(room);}hotel.reserve(102, "Anna Petrova");System.out.println("Nach der Buchung: " + hotel.check(102));System.out.println("Freie Zimmer: " + hotel.freeRooms().size());101, Einzelzimmer, 60 € pro Nacht, frei102, Doppelzimmer, 85 € pro Nacht, frei201, Doppelzimmer, 95 € pro Nacht, frei202, Suite, 140 € pro Nacht, freiNach der Buchung: 102, Doppelzimmer, 85 € pro Nacht, belegtFreie Zimmer: 3Hotel hotel = new Hotel();hotel.Add(new Room(101, "Einzelzimmer", 60));hotel.Add(new Room(102, "Doppelzimmer", 85));hotel.Add(new Room(201, "Doppelzimmer", 95));hotel.Add(new Room(202, "Suite", 140));
foreach (Room room in hotel.FreeRooms()){ Console.WriteLine(room);}hotel.Reserve(102, "Anna Petrova");Console.WriteLine("Nach der Buchung: " + hotel.Check(102));Console.WriteLine("Freie Zimmer: " + hotel.FreeRooms().Count);101, Einzelzimmer, 60 € pro Nacht, frei102, Doppelzimmer, 85 € pro Nacht, frei201, Doppelzimmer, 95 € pro Nacht, frei202, Suite, 140 € pro Nacht, freiNach der Buchung: 102, Doppelzimmer, 85 € pro Nacht, belegtFreie Zimmer: 3hotel = Hotel()hotel.add(Room(101, "Einzelzimmer", 60))hotel.add(Room(102, "Doppelzimmer", 85))hotel.add(Room(201, "Doppelzimmer", 95))hotel.add(Room(202, "Suite", 140))
for room in hotel.free_rooms(): print(room)hotel.reserve(102, "Anna Petrova")print(f"Nach der Buchung: {hotel.check(102)}")print(f"Freie Zimmer: {len(hotel.free_rooms())}")101, Einzelzimmer, 60 € pro Nacht, frei102, Doppelzimmer, 85 € pro Nacht, frei201, Doppelzimmer, 95 € pro Nacht, frei202, Suite, 140 € pro Nacht, freiNach der Buchung: 102, Doppelzimmer, 85 € pro Nacht, belegtFreie Zimmer: 3const hotel = new Hotel();hotel.add(new Room(101, "Einzelzimmer", 60));hotel.add(new Room(102, "Doppelzimmer", 85));hotel.add(new Room(201, "Doppelzimmer", 95));hotel.add(new Room(202, "Suite", 140));
for (const room of hotel.freeRooms()) { console.log(String(room));}hotel.reserve(102, "Anna Petrova");console.log(`Nach der Buchung: ${hotel.check(102)}`);console.log(`Freie Zimmer: ${hotel.freeRooms().length}`);101, Einzelzimmer, 60 € pro Nacht, frei102, Doppelzimmer, 85 € pro Nacht, frei201, Doppelzimmer, 95 € pro Nacht, frei202, Suite, 140 € pro Nacht, freiNach der Buchung: 102, Doppelzimmer, 85 € pro Nacht, belegtFreie Zimmer: 3String(room) und das Template-Literal rufen toString() auf. console.log(room) ruft es nicht auf: Es zeigt die Felder des Objekts, die privaten sind nicht sichtbar, und es gibt nur Room {} aus.Die Idee
Stornieren und die Regeln
Nicht jede Anfrage lässt sich ausführen: Das Zimmer ist belegt, es gibt kein Zimmer 305, ein Gast storniert die Buchung eines anderen. Das sind Absagen nach den Regeln, deshalb sind es eigene Exceptions mit einer gemeinsamen Basisklasse:
class HotelException extends RuntimeException { public HotelException(String message) { super(message); }}
class RoomNotFoundException extends HotelException { public RoomNotFoundException(String message) { super(message); }}
class RoomTakenException extends HotelException { public RoomTakenException(String message) { super(message); }}public class HotelException : Exception{ public HotelException(string message) : base(message) { }}
public class RoomNotFoundException : HotelException{ public RoomNotFoundException(string message) : base(message) { }}
public class RoomTakenException : HotelException{ public RoomTakenException(string message) : base(message) { }}class HotelError(Exception): pass
class RoomNotFoundError(HotelError): pass
class RoomTakenError(HotelError): passclass HotelError extends Error { constructor(message) { super(message); this.name = "HotelError"; }}
class RoomNotFoundError extends HotelError { constructor(message) { super(message); this.name = "RoomNotFoundError"; }}
class RoomTakenError extends HotelError { constructor(message) { super(message); this.name = "RoomTakenError"; }}Das Stornieren prüft, dass das Zimmer belegt ist und dass derselbe Gast es storniert:
public void cancel(int number, String guest) { Room room = find(number); if (room.isFree()) { throw new HotelException("Zimmer " + number + " ist nicht gebucht."); } if (!guests.get(number).equals(guest)) { throw new HotelException("Zimmer " + number + " ist von einem anderen Gast gebucht."); } room.release(); guests.remove(number);}public void Cancel(int number, string guest){ Room room = Find(number); if (room.IsFree) { throw new HotelException("Zimmer " + number + " ist nicht gebucht."); } if (guests[number] != guest) { throw new HotelException("Zimmer " + number + " ist von einem anderen Gast gebucht."); } room.Release(); guests.Remove(number);}def cancel(self, number, guest): room = self._find(number) if room.is_free: raise HotelError(f"Zimmer {number} ist nicht gebucht.") if self._guests[number] != guest: raise HotelError(f"Zimmer {number} ist von einem anderen Gast gebucht.") room.release() del self._guests[number]cancel(number, guest) { const room = this.#find(number); if (room.isFree) { throw new HotelError(`Zimmer ${number} ist nicht gebucht.`); } if (this.#guests.get(number) !== guest) { throw new HotelError(`Zimmer ${number} ist von einem anderen Gast gebucht.`); } room.release(); this.#guests.delete(number);}Die Projektaufgabe verlangt, das System in verschiedenen Szenarien zu prüfen: buchen, prüfen und stornieren, sowohl wenn alles gültig ist als auch wenn nicht. Jedes Szenario ist eine Zeile in der Ausgabe:
Hotel hotel = new Hotel();hotel.add(new Room(101, "Einzelzimmer", 60));hotel.add(new Room(102, "Doppelzimmer", 85));hotel.add(new Room(201, "Doppelzimmer", 95));hotel.add(new Room(202, "Suite", 140));
hotel.reserve(102, "Anna Petrova");System.out.println("1. Freies Zimmer: " + hotel.check(102));try { hotel.reserve(102, "Boris Iliev");} catch (RoomTakenException e) { System.out.println("2. Belegtes Zimmer: " + e.getMessage());}try { hotel.cancel(102, "Boris Iliev");} catch (HotelException e) { System.out.println("3. Fremde Buchung: " + e.getMessage());}hotel.cancel(102, "Anna Petrova");System.out.println("4. Stornierung: " + hotel.check(102));try { hotel.cancel(102, "Anna Petrova");} catch (HotelException e) { System.out.println("5. Zweite Stornierung: " + e.getMessage());}try { hotel.reserve(305, "Anna Petrova");} catch (RoomNotFoundException e) { System.out.println("6. Kein solches Zimmer: " + e.getMessage());}try { hotel.reserve(201, " ");} catch (HotelException e) { System.out.println("7. Ohne Namen: " + e.getMessage());}1. Freies Zimmer: 102, Doppelzimmer, 85 € pro Nacht, belegt2. Belegtes Zimmer: Zimmer 102 ist schon belegt.3. Fremde Buchung: Zimmer 102 ist von einem anderen Gast gebucht.4. Stornierung: 102, Doppelzimmer, 85 € pro Nacht, frei5. Zweite Stornierung: Zimmer 102 ist nicht gebucht.6. Kein solches Zimmer: Es gibt kein Zimmer 305.7. Ohne Namen: Der Name des Gastes ist Pflicht.Hotel hotel = new Hotel();hotel.Add(new Room(101, "Einzelzimmer", 60));hotel.Add(new Room(102, "Doppelzimmer", 85));hotel.Add(new Room(201, "Doppelzimmer", 95));hotel.Add(new Room(202, "Suite", 140));
hotel.Reserve(102, "Anna Petrova");Console.WriteLine("1. Freies Zimmer: " + hotel.Check(102));try{ hotel.Reserve(102, "Boris Iliev");}catch (RoomTakenException e){ Console.WriteLine("2. Belegtes Zimmer: " + e.Message);}try{ hotel.Cancel(102, "Boris Iliev");}catch (HotelException e){ Console.WriteLine("3. Fremde Buchung: " + e.Message);}hotel.Cancel(102, "Anna Petrova");Console.WriteLine("4. Stornierung: " + hotel.Check(102));try{ hotel.Cancel(102, "Anna Petrova");}catch (HotelException e){ Console.WriteLine("5. Zweite Stornierung: " + e.Message);}try{ hotel.Reserve(305, "Anna Petrova");}catch (RoomNotFoundException e){ Console.WriteLine("6. Kein solches Zimmer: " + e.Message);}try{ hotel.Reserve(201, " ");}catch (HotelException e){ Console.WriteLine("7. Ohne Namen: " + e.Message);}1. Freies Zimmer: 102, Doppelzimmer, 85 € pro Nacht, belegt2. Belegtes Zimmer: Zimmer 102 ist schon belegt.3. Fremde Buchung: Zimmer 102 ist von einem anderen Gast gebucht.4. Stornierung: 102, Doppelzimmer, 85 € pro Nacht, frei5. Zweite Stornierung: Zimmer 102 ist nicht gebucht.6. Kein solches Zimmer: Es gibt kein Zimmer 305.7. Ohne Namen: Der Name des Gastes ist Pflicht.hotel = Hotel()hotel.add(Room(101, "Einzelzimmer", 60))hotel.add(Room(102, "Doppelzimmer", 85))hotel.add(Room(201, "Doppelzimmer", 95))hotel.add(Room(202, "Suite", 140))
hotel.reserve(102, "Anna Petrova")print(f"1. Freies Zimmer: {hotel.check(102)}")try: hotel.reserve(102, "Boris Iliev")except RoomTakenError as e: print(f"2. Belegtes Zimmer: {e}")try: hotel.cancel(102, "Boris Iliev")except HotelError as e: print(f"3. Fremde Buchung: {e}")hotel.cancel(102, "Anna Petrova")print(f"4. Stornierung: {hotel.check(102)}")try: hotel.cancel(102, "Anna Petrova")except HotelError as e: print(f"5. Zweite Stornierung: {e}")try: hotel.reserve(305, "Anna Petrova")except RoomNotFoundError as e: print(f"6. Kein solches Zimmer: {e}")try: hotel.reserve(201, " ")except HotelError as e: print(f"7. Ohne Namen: {e}")1. Freies Zimmer: 102, Doppelzimmer, 85 € pro Nacht, belegt2. Belegtes Zimmer: Zimmer 102 ist schon belegt.3. Fremde Buchung: Zimmer 102 ist von einem anderen Gast gebucht.4. Stornierung: 102, Doppelzimmer, 85 € pro Nacht, frei5. Zweite Stornierung: Zimmer 102 ist nicht gebucht.6. Kein solches Zimmer: Es gibt kein Zimmer 305.7. Ohne Namen: Der Name des Gastes ist Pflicht.const hotel = new Hotel();hotel.add(new Room(101, "Einzelzimmer", 60));hotel.add(new Room(102, "Doppelzimmer", 85));hotel.add(new Room(201, "Doppelzimmer", 95));hotel.add(new Room(202, "Suite", 140));
hotel.reserve(102, "Anna Petrova");console.log(`1. Freies Zimmer: ${hotel.check(102)}`);try { hotel.reserve(102, "Boris Iliev");} catch (e) { if (!(e instanceof RoomTakenError)) throw e; console.log(`2. Belegtes Zimmer: ${e.message}`);}try { hotel.cancel(102, "Boris Iliev");} catch (e) { if (!(e instanceof HotelError)) throw e; console.log(`3. Fremde Buchung: ${e.message}`);}hotel.cancel(102, "Anna Petrova");console.log(`4. Stornierung: ${hotel.check(102)}`);try { hotel.cancel(102, "Anna Petrova");} catch (e) { if (!(e instanceof HotelError)) throw e; console.log(`5. Zweite Stornierung: ${e.message}`);}try { hotel.reserve(305, "Anna Petrova");} catch (e) { if (!(e instanceof RoomNotFoundError)) throw e; console.log(`6. Kein solches Zimmer: ${e.message}`);}try { hotel.reserve(201, " ");} catch (e) { if (!(e instanceof HotelError)) throw e; console.log(`7. Ohne Namen: ${e.message}`);}1. Freies Zimmer: 102, Doppelzimmer, 85 € pro Nacht, belegt2. Belegtes Zimmer: Zimmer 102 ist schon belegt.3. Fremde Buchung: Zimmer 102 ist von einem anderen Gast gebucht.4. Stornierung: 102, Doppelzimmer, 85 € pro Nacht, frei5. Zweite Stornierung: Zimmer 102 ist nicht gebucht.6. Kein solches Zimmer: Es gibt kein Zimmer 305.7. Ohne Namen: Der Name des Gastes ist Pflicht.Die konkrete Exception sagt, was das Problem ist. HotelError fängt alle Absagen auf einmal: Der Server wird nur sie fangen und in eine Antwort für den Client verwandeln.
catch hat in JavaScript keinen Typ und fängt alles. Deshalb fragt jedes catch mit instanceof, ob der Fehler seiner ist, und wirft alles andere mit throw e weiter.Genauer, für Neugierige
- Checked Exceptions gibt es nicht: Keine Methode erklärt, was sie wirft. Deshalb fängt die Rezeption sie an einer Stelle.
- Szenarien wie diese sind natürliche Tests. Mit
node:testwird jedes zu einemtest(...), undassert.throws(() => hotel.reserve(102, "Boris Iliev"), RoomTakenError)prüft die Exception.
Probier es aus
Wer ist in 201
Was gibt das Programm aus?
const hotel = new Hotel();hotel.add(new Room(101, "Einzelzimmer", 60));hotel.add(new Room(102, "Doppelzimmer", 85));hotel.add(new Room(201, "Doppelzimmer", 95));hotel.add(new Room(202, "Suite", 140));
hotel.reserve(101, "Anna Petrova");hotel.reserve(201, "Boris Iliev");hotel.cancel(101, "Anna Petrova");try { hotel.reserve(201, "Vera Koleva"); console.log("Vera ist in 201");} catch (e) { if (!(e instanceof RoomTakenError)) throw e; console.log("Absage für Vera");}console.log(hotel.freeRooms().length);Anna belegt 101 und gibt es danach wieder frei. Boris belegt 201, deshalb bekommt Vera RoomTakenError, und die Zeile „Vera ist in 201“ wird nicht ausgeführt. Belegt ist nur 201, also sind drei Zimmer frei.
Die Idee
Das Protokoll: eine Zeile „BEFEHL:Text“
Jede Nachricht ist eine Zeile BEFEHL:Text, und der Server antwortet mit einer Zeile:
| Der Client schickt | Der Server antwortet |
|---|---|
FREI |
OK: und die freien Zimmer, getrennt durch ; |
PRÜFEN:102 |
OK:102, Doppelzimmer, 85 € pro Nacht, belegt |
BUCHEN:102;Anna Petrova |
OK:Zimmer 102 gehört dir, Anna Petrova. oder FEHLER: und der Grund |
STORNIEREN:102;Anna Petrova |
OK:Die Buchung für Zimmer 102 ist storniert. oder FEHLER: und der Grund |
ENDE |
TSCHÜSS, und der Server schließt die Verbindung |
Die Wörter des Protokolls sind Konstanten an einer Stelle (Object.freeze verhindert, dass das Objekt geändert wird):
const Protocol = Object.freeze({ FREE: "FREI", CHECK: "PRÜFEN", RESERVE: "BUCHEN", CANCEL: "STORNIEREN", EXIT: "ENDE",
OK: "OK", ERROR: "FEHLER", BYE: "TSCHÜSS",});Die Rezeption übersetzt eine Zeile des Protokolls in einen Aufruf des Hotels und zurück:
class Reception { #hotel;
constructor(hotel) { this.#hotel = hotel; }
...}handle(line) { const colon = line.indexOf(":"); const command = colon < 0 ? line : line.slice(0, colon); const text = colon < 0 ? "" : line.slice(colon + 1); try { switch (command) { case Protocol.FREE: return `${Protocol.OK}:${this.#join(this.#hotel.freeRooms())}`; case Protocol.CHECK: return `${Protocol.OK}:${this.#hotel.check(this.#number(text))}`; case Protocol.RESERVE: return this.#reserve(text); case Protocol.CANCEL: return this.#cancel(text); default: throw new HotelError(`Unbekannter Befehl ${command}.`); } } catch (e) { if (!(e instanceof HotelError)) throw e; return `${Protocol.ERROR}:${e.message}`; }}#reserve(text) { const [numberText, guest] = this.#numberAndGuest(text); const number = this.#number(numberText); this.#hotel.reserve(number, guest); return `${Protocol.OK}:Zimmer ${number} gehört dir, ${guest}.`;}
#cancel(text) { const [numberText, guest] = this.#numberAndGuest(text); const number = this.#number(numberText); this.#hotel.cancel(number, guest); return `${Protocol.OK}:Die Buchung für Zimmer ${number} ist storniert.`;}
#numberAndGuest(text) { const semicolon = text.indexOf(";"); if (semicolon < 0) { throw new HotelError("Schreib eine Nummer und einen Namen, zum Beispiel „102;Anna Petrova“."); } return [text.slice(0, semicolon), text.slice(semicolon + 1)];}
#number(text) { const number = Number(text); if (text.trim() === "" || !Number.isInteger(number)) { throw new HotelError("Die Zimmernummer ist eine ganze Zahl."); } return number;}
#join(rooms) { if (rooms.length === 0) { return "Es gibt keine freien Zimmer."; } return rooms.join("; ");}catch fragt, ob der Fehler ein HotelError ist. Wenn nicht, ist das ein Fehler im Code und keine Absage nach den Regeln, und er geht weiter nach oben. Number(text) wirft nicht: Für „einhundertzwei“ liefert es NaN und für leeren Text 0. Deshalb prüft #number selbst, dass der Text nicht leer und die Zahl ganz ist.Die Rezeption merkt sich nichts über den Client: Jede Zeile trägt alles, was nötig ist, sogar den Namen des Gastes. Deshalb reicht eine Rezeption für alle Clients. Das lässt sich ohne Netz prüfen:
const hotel = new Hotel();hotel.add(new Room(101, "Einzelzimmer", 60));hotel.add(new Room(102, "Doppelzimmer", 85));hotel.add(new Room(201, "Doppelzimmer", 95));hotel.add(new Room(202, "Suite", 140));
const reception = new Reception(hotel);const lines = [ "BUCHEN:102;Anna Petrova", "PRÜFEN:102", "BUCHEN:102;Boris Iliev", "STORNIEREN:102", "PRÜFEN:einhundertzwei", "ZEIGEN",];for (const line of lines) { console.log(`> ${line}`); console.log(reception.handle(line));}> BUCHEN:102;Anna PetrovaOK:Zimmer 102 gehört dir, Anna Petrova.> PRÜFEN:102OK:102, Doppelzimmer, 85 € pro Nacht, belegt> BUCHEN:102;Boris IlievFEHLER:Zimmer 102 ist schon belegt.> STORNIEREN:102FEHLER:Schreib eine Nummer und einen Namen, zum Beispiel „102;Anna Petrova“.> PRÜFEN:einhundertzweiFEHLER:Die Zimmernummer ist eine ganze Zahl.> ZEIGENFEHLER:Unbekannter Befehl ZEIGEN.Genauer, für Neugierige
-
JavaScript hat keine Aufzählungen (Enums). Ein eingefrorenes Objekt kommt dem am nächsten: eine geschlossene Liste von Namen, die niemand ändern kann. In einer Datei mit
importbricht der VersuchProtocol.FREE = "ALLE"mitTypeErrorab, undProtocol.FREEbleibt"FREI". - Das Protokoll ist zustandslos: Jede Zeile steht für sich. Das macht es leicht zu prüfen und leicht, mehr Clients zu bedienen. Müsste der Client zuerst sagen „ich bin Anna“, bräuchte jeder Client seine eigene Rezeption, wie die Sitzung im Autohaus.
Sieh es dir an
Der Server
Einen Thread-Pool gibt es nicht. createServer ruft seine Funktion für jeden neuen Client auf, und der ClientHandler dieses Clients liest seine Zeilen mit for await. Während der Client schweigt, bedient der Thread die anderen: await hält ihn nicht an, sondern sagt „mach hier weiter, wenn du dran bist“.
class ClientHandler { #socket; #reception;
constructor(socket, reception) { this.#socket = socket; this.#reception = reception; }
async run() { try { for await (const line of createInterface({ input: this.#socket })) { if (line === Protocol.EXIT) { break; } this.#socket.write(`${this.#reception.handle(line)}\n`); } this.#socket.end(`${Protocol.BYE}\n`); } catch (e) { console.log(`Die Verbindung zum Client ist abgebrochen: ${e.message}`); } }}HotelServer startet den Server auf dem Port. listen und close passieren nicht sofort, deshalb geben start und stop ein Promise zurück und werden mit await aufgerufen:
class HotelServer { #server; #port;
constructor(port, hotel) { const reception = new Reception(hotel); this.#port = port; this.#server = createServer((socket) => new ClientHandler(socket, reception).run()); }
start() { return new Promise((resolve) => this.#server.listen(this.#port, "localhost", resolve)); }
stop() { return new Promise((resolve) => this.#server.close(resolve)); }}- Egal wie viele Clients es gibt, ein Thread bedient sie alle. Er wartet nicht auf das Netz: Er erledigt anderes, bis die Zeile ankommt.
- Alle Clients rufen dasselbe
Hotelauf. Deshalb muss das Hotel seine Zimmer schützen. Wie, gleich.
Sieh es dir an
Der Client
await auf die Antwort. Die Zeilen vom Socket werden mit demselben createInterface gelesen, mit dem wir von der Tastatur lesen: next() liefert die nächste Zeile, sobald sie ankommt. Ohne Text schickt send nur den Befehl. close() sagt ENDE und schließt die Verbindung:class HotelClient { #socket; #lines;
constructor(host, port) { this.#socket = createConnection({ host, port }); this.#lines = createInterface({ input: this.#socket })[Symbol.asyncIterator](); }
async send(command, text) { const line = text === undefined ? command : `${command}:${text}`; this.#socket.write(`${line}\n`); return (await this.#lines.next()).value; }
async close() { await this.send(Protocol.EXIT); this.#socket.end(); }}Server und Client in einem Programm:
const hotel = new Hotel();hotel.add(new Room(101, "Einzelzimmer", 60));hotel.add(new Room(102, "Doppelzimmer", 85));hotel.add(new Room(201, "Doppelzimmer", 95));hotel.add(new Room(202, "Suite", 140));const server = new HotelServer(5341, hotel);await server.start();
const client = new HotelClient("localhost", 5341);console.log(await client.send(Protocol.RESERVE, "102;Anna Petrova"));console.log(await client.send(Protocol.CHECK, "102"));console.log(await client.send(Protocol.FREE));console.log(await client.send(Protocol.CANCEL, "102;Anna Petrova"));console.log(await client.send(Protocol.CHECK, "305"));await client.close();await server.stop();OK:Zimmer 102 gehört dir, Anna Petrova.OK:102, Doppelzimmer, 85 € pro Nacht, belegtOK:101, Einzelzimmer, 60 € pro Nacht, frei; 201, Doppelzimmer, 95 € pro Nacht, frei; 202, Suite, 140 € pro Nacht, freiOK:Die Buchung für Zimmer 102 ist storniert.FEHLER:Es gibt kein Zimmer 305.Die Idee
Zwei für ein Zimmer
await), während eine andere läuft, kann Folgendes passieren:| Agentur A | Agentur B | |
|---|---|---|
| 1 | prüft: 102 ist frei | |
| 2 | prüft: 102 ist frei | |
| 3 | belegt 102 für ihren Gast | |
| 4 | belegt 102 für ihren Gast und trägt ihn ein |
Beide bekommen eine Bestätigung. In keiner einzelnen Codezeile steckt ein Fehler. Der Fehler ist, dass sich die Schritte der beiden Anfragen verschränken.
Die Lösung: Niemand darf zwischen die Prüfung und das Belegen kommen. Dann ist „prüfen und belegen“ unteilbar (atomar). In JavaScript ist unsere Buchung das schon. Node.js führt den Code in einem Thread aus, und eine synchrone Funktion läuft bis zum Ende, bevor anderer Code startet. In reserve gibt es kein await, also kann keine Anfrage dazwischenkommen. Ein Lock ist nicht nötig:
reserve(number, guest) { if (guest.trim() === "") { throw new HotelError("Der Name des Gastes ist Pflicht."); } const room = this.#find(number); if (!room.isFree) { throw new RoomTakenError(`Zimmer ${number} ist schon belegt.`); } room.occupy(); this.#guests.set(number, guest);}await mitten in der Buchung. Lägen die Zimmer in einer Datenbank, würde die Prüfung mit await auf deren Antwort warten. Während sie wartet, prüft eine andere Anfrage dasselbe Zimmer und sieht es als frei. Deshalb muss „prüfen und belegen“ ein synchroner Schritt sein oder eine einzige Anfrage an die Datenbank.reserve(number, guest) { if (guest.trim() === "") { throw new HotelError("Der Name des Gastes ist Pflicht."); } const room = this.#find(number); if (!room.isFree) { throw new RoomTakenError(`Zimmer ${number} ist schon belegt.`); } room.occupy(); this.#guests.set(number, guest);}Schritt für Schritt, in Worten
- Zimmer 102 ist frei. Anna Petrova und Boris Iliev buchen es im selben Moment.
- Zuerst mit einem await zwischen der Prüfung und dem Belegen, als ob die Zimmer in einer Datenbank wären. Annas Anfrage prüft, sieht „frei“ und wartet.
- Während Anna wartet, prüft Boris’ Anfrage: Auch sie sieht „frei“.
- Anna belegt das Zimmer und trägt ihren Namen ein.
- Boris belegt das Zimmer auch und trägt seinen Namen ein. Beide haben eine Bestätigung für dasselbe Zimmer.
- Von vorn, ohne await: reserve ist synchron und läuft bis zum Ende, bevor anderer Code startet.
- Sagen wir, Anna ist zuerst dran: Ihre Anfrage ist an der Reihe. Prüfen und Belegen sind ein Schritt, und niemand kommt dazwischen.
- Boris’ Anfrage ist angekommen und wartet: Es gibt einen Thread, und er macht eine Sache nach der anderen.
- Jetzt ist Boris an der Reihe. Jetzt sieht die Prüfung „belegt“.
- RoomTakenError. Die Rezeption schickt ihn als Fehlerzeile zurück, und Boris kann nach einem anderen Zimmer fragen.
- Das Programm aus der Lektion zählt die Erfolgreichen: 1 pro Zimmer. Wer zuerst dran war, ist bei jedem Lauf anders.
Der Beweis: Zwei Clients buchen 102 gleichzeitig, dann buchen zwanzig Agenturen 201 gleichzeitig. Die Antworten werden gesammelt, und das Programm gibt aus, wie viele Erfolg hatten:
async function reserve(port, room, guest) { try { const client = new HotelClient("localhost", port); const reply = await client.send(Protocol.RESERVE, `${room};${guest}`); await client.close(); return reply; } catch (e) { return `${Protocol.ERROR}:${e.message}`; }}
async function race(port, room, guests) { const replies = await Promise.all(guests.map((guest) => reserve(port, room, guest))); return replies.filter((reply) => reply.startsWith(Protocol.OK)).length;}const hotel = new Hotel();hotel.add(new Room(101, "Einzelzimmer", 60));hotel.add(new Room(102, "Doppelzimmer", 85));hotel.add(new Room(201, "Doppelzimmer", 95));hotel.add(new Room(202, "Suite", 140));const server = new HotelServer(5342, hotel);await server.start();
const two = ["Anna Petrova", "Boris Iliev"];console.log(`Zwei für Zimmer 102, erfolgreich: ${await race(5342, 102, two)}`);
const agencies = [];for (let i = 1; i <= 20; i++) { agencies.push(`Agentur ${i}`);}console.log(`Zwanzig für Zimmer 201, erfolgreich: ${await race(5342, 201, agencies)}`);
const desk = new HotelClient("localhost", 5342);console.log(await desk.send(Protocol.FREE));await desk.close();await server.stop();Zwei für Zimmer 102, erfolgreich: 1Zwanzig für Zimmer 201, erfolgreich: 1OK:101, Einzelzimmer, 60 € pro Nacht, frei; 202, Suite, 140 € pro Nacht, freiPromise.all startet alle Anfragen gleichzeitig und wartet auf die Antworten, und race zählt die Erfolgreichen. Es gibt einen Thread, und trotzdem verschränken sich die Anfragen: Jede wartet auf das Netz, während die anderen laufen.Wer das Zimmer bekommen hat, ist bei jedem Lauf anders, deshalb gibt das Programm es nicht aus. Wie viele Erfolg hatten, ist immer gleich: einer pro Zimmer.
Genauer, für Neugierige
-
Mit einem
awaitzwischen Prüfen und Belegen ist der Fehler nicht selten: Zwei bekommen das Zimmer bei jedem Lauf (siehe die nächste Karte). Deshalb ist jedesawaiteine Stelle, an der du anhältst und nachdenkst: Was kann eine andere Anfrage ändern, während diese wartet. -
Brauchst du wirklich ein
awaitmitten in der Buchung, stellen sich die Anfragen für ein Zimmer nacheinander an, zum Beispiel mit einer Warteschlange von Promises (Promise) oder mit einer Bibliothek für Locks. Dieses Projekt braucht das nicht. - In einem echten System liegen die Zimmer in einer Datenbank, und der Schutz passiert dort: eine Transaktion oder eine Bedingung in der Abfrage selbst (
UPDATE rooms SET free = false WHERE number = 102 AND free = true). Dann gilt er auch bei mehreren Servern, nicht nur in einem Programm.
Probier es aus
Gesperrt, aber nicht ganz
async-Methoden gemacht. Die Rezeption wartet zuerst auf die Prüfung, dann auf das Belegen. Zwei Clients gleichzeitig bekommen bei jedem Lauf dasselbe Zimmer. In welcher Zeile steckt der Fehler?Wähle die Zeile mit dem Fehler.
await this.#hotel.isFree(number) hält die Anfrage an und wartet. Genau dann prüft eine andere Anfrage dasselbe Zimmer und sieht es als frei. Prüfen und Belegen müssen ein Schritt ohne await dazwischen sein, wie in unserem reserve.Sieh es dir an
Das ganze Hotel
Die Testszenarien aus der Aufgabe, diesmal über das Netz, mit zwei gleichzeitig verbundenen Clients. Am Ende buchen zwei im selben Moment 201:
async function say(who, client, command, text) { const reply = await client.send(command, text); console.log(`${who} > ${command}:${text}`); console.log(`${who} < ${reply}`);}
const hotel = new Hotel();hotel.add(new Room(101, "Einzelzimmer", 60));hotel.add(new Room(102, "Doppelzimmer", 85));hotel.add(new Room(201, "Doppelzimmer", 95));hotel.add(new Room(202, "Suite", 140));const server = new HotelServer(5343, hotel);await server.start();
const anna = new HotelClient("localhost", 5343);const boris = new HotelClient("localhost", 5343);await say("Anna", anna, Protocol.RESERVE, "102;Anna Petrova");await say("Boris", boris, Protocol.RESERVE, "102;Boris Iliev");await say("Boris", boris, Protocol.CHECK, "102");await say("Boris", boris, Protocol.CANCEL, "102;Boris Iliev");await say("Anna", anna, Protocol.CANCEL, "102;Anna Petrova");await say("Boris", boris, Protocol.RESERVE, "102;Boris Iliev");await say("Anna", anna, Protocol.CHECK, "305");await anna.close();await boris.close();
const two = ["Anna Petrova", "Vera Koleva"];console.log(`Zwei gleichzeitig für Zimmer 201, erfolgreich: ${await race(5343, 201, two)}`);const desk = new HotelClient("localhost", 5343);console.log(await desk.send(Protocol.FREE));await desk.close();await server.stop();Anna > BUCHEN:102;Anna PetrovaAnna < OK:Zimmer 102 gehört dir, Anna Petrova.Boris > BUCHEN:102;Boris IlievBoris < FEHLER:Zimmer 102 ist schon belegt.Boris > PRÜFEN:102Boris < OK:102, Doppelzimmer, 85 € pro Nacht, belegtBoris > STORNIEREN:102;Boris IlievBoris < FEHLER:Zimmer 102 ist von einem anderen Gast gebucht.Anna > STORNIEREN:102;Anna PetrovaAnna < OK:Die Buchung für Zimmer 102 ist storniert.Boris > BUCHEN:102;Boris IlievBoris < OK:Zimmer 102 gehört dir, Boris Iliev.Anna > PRÜFEN:305Anna < FEHLER:Es gibt kein Zimmer 305.Zwei gleichzeitig für Zimmer 201, erfolgreich: 1OK:101, Einzelzimmer, 60 € pro Nacht, frei; 202, Suite, 140 € pro Nacht, freiGenauer, für Neugierige
Das ganze Programm, von Anfang bis Ende:
import { createConnection, createServer } from "node:net";import { createInterface } from "node:readline";
class Room { #number; #type; #price; #free = true;
constructor(number, type, price) { this.#number = number; this.#type = type; this.#price = price; }
get number() { return this.#number; }
get type() { return this.#type; }
get price() { return this.#price; }
get isFree() { return this.#free; }
occupy() { this.#free = false; }
release() { this.#free = true; }
toString() { const state = this.#free ? "frei" : "belegt"; return `${this.#number}, ${this.#type}, ${this.#price} € pro Nacht, ${state}`; }}
class Hotel { #rooms = []; #guests = new Map();
add(room) { this.#rooms.push(room); }
freeRooms() { return this.#rooms.filter((room) => room.isFree); }
check(number) { return this.#find(number); }
reserve(number, guest) { if (guest.trim() === "") { throw new HotelError("Der Name des Gastes ist Pflicht."); } const room = this.#find(number); if (!room.isFree) { throw new RoomTakenError(`Zimmer ${number} ist schon belegt.`); } room.occupy(); this.#guests.set(number, guest); }
cancel(number, guest) { const room = this.#find(number); if (room.isFree) { throw new HotelError(`Zimmer ${number} ist nicht gebucht.`); } if (this.#guests.get(number) !== guest) { throw new HotelError(`Zimmer ${number} ist von einem anderen Gast gebucht.`); } room.release(); this.#guests.delete(number); }
#find(number) { const room = this.#rooms.find((r) => r.number === number); if (room === undefined) { throw new RoomNotFoundError(`Es gibt kein Zimmer ${number}.`); } return room; }}
class HotelError extends Error { constructor(message) { super(message); this.name = "HotelError"; }}
class RoomNotFoundError extends HotelError { constructor(message) { super(message); this.name = "RoomNotFoundError"; }}
class RoomTakenError extends HotelError { constructor(message) { super(message); this.name = "RoomTakenError"; }}
const Protocol = Object.freeze({ FREE: "FREI", CHECK: "PRÜFEN", RESERVE: "BUCHEN", CANCEL: "STORNIEREN", EXIT: "ENDE",
OK: "OK", ERROR: "FEHLER", BYE: "TSCHÜSS",});
class Reception { #hotel;
constructor(hotel) { this.#hotel = hotel; }
handle(line) { const colon = line.indexOf(":"); const command = colon < 0 ? line : line.slice(0, colon); const text = colon < 0 ? "" : line.slice(colon + 1); try { switch (command) { case Protocol.FREE: return `${Protocol.OK}:${this.#join(this.#hotel.freeRooms())}`; case Protocol.CHECK: return `${Protocol.OK}:${this.#hotel.check(this.#number(text))}`; case Protocol.RESERVE: return this.#reserve(text); case Protocol.CANCEL: return this.#cancel(text); default: throw new HotelError(`Unbekannter Befehl ${command}.`); } } catch (e) { if (!(e instanceof HotelError)) throw e; return `${Protocol.ERROR}:${e.message}`; } }
#reserve(text) { const [numberText, guest] = this.#numberAndGuest(text); const number = this.#number(numberText); this.#hotel.reserve(number, guest); return `${Protocol.OK}:Zimmer ${number} gehört dir, ${guest}.`; }
#cancel(text) { const [numberText, guest] = this.#numberAndGuest(text); const number = this.#number(numberText); this.#hotel.cancel(number, guest); return `${Protocol.OK}:Die Buchung für Zimmer ${number} ist storniert.`; }
#numberAndGuest(text) { const semicolon = text.indexOf(";"); if (semicolon < 0) { throw new HotelError("Schreib eine Nummer und einen Namen, zum Beispiel „102;Anna Petrova“."); } return [text.slice(0, semicolon), text.slice(semicolon + 1)]; }
#number(text) { const number = Number(text); if (text.trim() === "" || !Number.isInteger(number)) { throw new HotelError("Die Zimmernummer ist eine ganze Zahl."); } return number; }
#join(rooms) { if (rooms.length === 0) { return "Es gibt keine freien Zimmer."; } return rooms.join("; "); }}
class ClientHandler { #socket; #reception;
constructor(socket, reception) { this.#socket = socket; this.#reception = reception; }
async run() { try { for await (const line of createInterface({ input: this.#socket })) { if (line === Protocol.EXIT) { break; } this.#socket.write(`${this.#reception.handle(line)}\n`); } this.#socket.end(`${Protocol.BYE}\n`); } catch (e) { console.log(`Die Verbindung zum Client ist abgebrochen: ${e.message}`); } }}
class HotelServer { #server; #port;
constructor(port, hotel) { const reception = new Reception(hotel); this.#port = port; this.#server = createServer((socket) => new ClientHandler(socket, reception).run()); }
start() { return new Promise((resolve) => this.#server.listen(this.#port, "localhost", resolve)); }
stop() { return new Promise((resolve) => this.#server.close(resolve)); }}
class HotelClient { #socket; #lines;
constructor(host, port) { this.#socket = createConnection({ host, port }); this.#lines = createInterface({ input: this.#socket })[Symbol.asyncIterator](); }
async send(command, text) { const line = text === undefined ? command : `${command}:${text}`; this.#socket.write(`${line}\n`); return (await this.#lines.next()).value; }
async close() { await this.send(Protocol.EXIT); this.#socket.end(); }}
async function say(who, client, command, text) { const reply = await client.send(command, text); console.log(`${who} > ${command}:${text}`); console.log(`${who} < ${reply}`);}
async function reserve(port, room, guest) { try { const client = new HotelClient("localhost", port); const reply = await client.send(Protocol.RESERVE, `${room};${guest}`); await client.close(); return reply; } catch (e) { return `${Protocol.ERROR}:${e.message}`; }}
async function race(port, room, guests) { const replies = await Promise.all(guests.map((guest) => reserve(port, room, guest))); return replies.filter((reply) => reply.startsWith(Protocol.OK)).length;}
const hotel = new Hotel();hotel.add(new Room(101, "Einzelzimmer", 60));hotel.add(new Room(102, "Doppelzimmer", 85));hotel.add(new Room(201, "Doppelzimmer", 95));hotel.add(new Room(202, "Suite", 140));const server = new HotelServer(5343, hotel);await server.start();
const anna = new HotelClient("localhost", 5343);const boris = new HotelClient("localhost", 5343);await say("Anna", anna, Protocol.RESERVE, "102;Anna Petrova");await say("Boris", boris, Protocol.RESERVE, "102;Boris Iliev");await say("Boris", boris, Protocol.CHECK, "102");await say("Boris", boris, Protocol.CANCEL, "102;Boris Iliev");await say("Anna", anna, Protocol.CANCEL, "102;Anna Petrova");await say("Boris", boris, Protocol.RESERVE, "102;Boris Iliev");await say("Anna", anna, Protocol.CHECK, "305");await anna.close();await boris.close();
const two = ["Anna Petrova", "Vera Koleva"];console.log(`Zwei gleichzeitig für Zimmer 201, erfolgreich: ${await race(5343, 201, two)}`);const desk = new HotelClient("localhost", 5343);console.log(await desk.send(Protocol.FREE));await desk.close();await server.stop();Übung
Mach allein weiter
Arbeite mit dem ganzen Programm aus der vorigen Karte.
1. Zimmerwechsel. Der Gast zieht von einem Zimmer in ein anderes mit VERLEGEN:102;202;Anna Petrova. Die Antwort ist OK:Anna Petrova ist in Zimmer 202. Ist das neue Zimmer belegt, bleibt der Gast im alten. Der Wechsel ist eine Methode in Hotel, synchron, ohne await darin: Niemand darf das neue Zimmer zwischen dem Freigeben des alten und dem Belegen des neuen nehmen.
OK:Zimmer 102 gehört dir, Anna Petrova.OK:Zimmer 201 gehört dir, Boris Iliev.FEHLER:Zimmer 201 ist schon belegt.OK:Anna Petrova ist in Zimmer 202.OK:101, Einzelzimmer, 60 € pro Nacht, frei; 102, Doppelzimmer, 85 € pro Nacht, freiWeitere Ideen
- Nächte und Summe. Eine Buchung gilt für eine Anzahl Nächte, und die Bestätigung nennt die Summe: „3 Nächte, 255 €“.
- Daten. Ein Zimmer wird für Tage gebucht, zum Beispiel vom 14. bis 16. Dezember. Zwei Buchungen für dasselbe Zimmer dürfen sich nicht überschneiden. Ob das Zimmer frei ist, hängt von den Daten ab.
- Meine Buchungen.
MEINE:Anna Petrovasagt, welche Zimmer zu diesem Gast gehören. - Warteliste. Ist das Zimmer belegt, trägt sich der Gast zum Warten ein. Wird es storniert, geht es an den Ersten auf der Liste.
-
Ein Client über die Tastatur. Ein zweites Programm liest Befehle von der Tastatur mit
ask()und schickt sie an den Server, der in einem anderen Terminal läuft.
Test
Teste dich
Verwandte Lektionen
Abschnitt „Verwandte Lektionen“- Baut auf: Pfeilfunktionen · Array-Methoden · Async, await und Threads · Netzwerkprogrammierung
- Aufgaben zum Thema: Projekt