Programmierpraktikum im Grundstudium WS04/05 (Institut für Informatik, LMU München) Inhalt: Hilfen zum Verständnis von Spezifikation und Interfaces. Im Zweifelsfall ersetzen sie Regelungen in der Spezifikation. Version 1.0 vom 8.12.04. Grundprinzipien =============== Die Interfaces werden typischerweise allesamt durch Klassen implementiert, die von java.rmi.server.UnicastRemoteObject erben. Alle Remote-Methoden sollen praktisch unverzüglich ihr Ergebnis liefern (bei Rückgabetyp void bedeutet das bloß die Terminierung). Insbesondere wird nicht auf andere Handlungen der Spieler bzw. des Servers gewartet, bevor eine Methodenausführung beendet wird. (Natürlich kann in der lokalen JVM erst noch einiges anderes getan, aber eben nicht gewartet werden.) Der Server darf allerdings keinesfalls darauf vertrauen, daß sich die Clients an diese Regel halten. Server-Interfaces sagen, was der Client bekommen kann. Client-Interfaces sagen, was der Server von Client verlangt. Letztlich sind nach Verbindungsaufnahme beide Seiten einander Server - d.h. Dienste. Der Server stellt die meisten Informationen nur einmal selbst bereit, kontrolliert also den Zugriff auf seine Daten selbst und vermeidet so übermäßiges Nachfragen der Clients ("Push-Technologie"). Die Client-Seite identifiziert sich außer bei der Anmeldung nie selbst sondern dadurch, daß sie jeweils als Einzige Zugriff auf den jeweiligen Kommunikator der Server-Seite hat. Typische Abläufe ================ Anmeldung --------- Der SpielServerManager stellt eine Implementierung des Interfaces Server bereit, die in der RMI-Registry des Hosts unter dem Namen "SpielServerManager" erreichbar ist. Der SpielClientManager verschafft sich Zugang zu diesem Service (der Benutzer muß Hostname und Port für die RMI-Registry eingeben können), erzeugt eine Implementierung von ClientKommunikator und übergibt eine Referenz auf sie bei der Anmeldung mit der Methode anmelden des Interfaces Server. Dazu werden der Name des Spielers und sein Paßwort angegeben. Der Spielername wird generell zur Identifikation im Chat benutzt und auch in den Spielpartien bekannt gemacht. Das Paßwort dient ausschließlich zur Identifikation bei einer Wiederanmeldung, die z.B. nach Netzwerkversagen oder Absturz der JVM nötig sein kann. Der SpielServerManager überprüft, ob der Spielername schon vergeben wurde. Ist dies der Fall, so muß der SpielServerManager feststellen, ob der betreffende SpielClientManager als inaktiv bekannt ist (weil z.B. schon Aufrufe RemoteExceptions geworfen haben) oder ob er erst jetzt nicht ansprechbar ist (Methode lebtNoch des ClientKommunikators). Ist er nun also bekanntermaßen inaktiv, wird das Paßwort überprüft. Stimmt es nicht, so wird eine PasswortFalschException geworfen. Anderenfalls müssen der allgemeine Chat und alle laufenden Spielpartien nebst Spielchats rekonstruiert werden und der Client alle aufgelaufenen Meldungen erhalten, siehe unten unter Rekonstruktion. Ist der ClientKommunikator noch aktiv, so wird eine NameVorhandenException geworfen. Anmerkung: Es kann passieren, daß sich der Benutzer arglos unter dem schon vergebenen aber nicht mehr aktiven Namen anmeldet und sein eigenes Paßwort festlegt. Dann kann er aus FalschesPasswortException erschließen, daß diese Situation vorliegt. Wichtiger aber ist, daß ein "Wiederanmelder" korrekt informiert wird, wenn es am falschen Paßwort gelegen hat. (Alternativ hätte man eine Methode wiederAnmelden vorsehen können.) Das Ergebnis einer erfolgreichen Anmeldung ist für den SpielClientManager die Herausgabe einer Implementierung von ServerKommunikator durch den SpielServerManager. Sie muß individuell für den SpielClientManager sein, damit Aufrufe von deren Remote-Methoden eindeutig dem jeweiligen SpielClientManager zugeordnet werden können. Auswahl von Spielen ------------------- Der SpielServerManager informiert den ClientKommunikator über alle angebotenen Spielpartien (laufend oder nur vorbereitet) mit der Methode zeigeSpiele und aktualisiert diese Information mittels entferneSpiel und updateSpiel. Die Methode setzeChat dient dem Austausch der Chat-Kommunikatoren: Der SpielServerManager übergibt einen für den SpielClientManager individuellen ServerChatKommunikator und erhält einen ClientChatKommunikator. Der ServerChatKommunikator erhält dann die Meldungen eines globalen Chat-Clients und gibt sie zusammen mit der Information darüber, welcher Chat-Client es war, an einen passenden globalen Chat-Server weiter. Der ClientChatKommunikator erhält alle Meldungen vom Chat-Server. Der Chat-Server speichert Nachrichten, die er aufgrund von Netzwerkproblemen nicht zustellen konnte, um sie bei Wiederaufleben der Verbindung nachträglich zuzustellen. Daraus ergibt sich auch, daß der globale (allgemeine) Chat dieselbe Funktionsweise haben soll wie derjenige, der am Anfang des Praktikums programmiert wurde. Nur hat er jetzt einen Schutz vor Identitätsdiebstahl. Der globale Chat dient insbesondere der Anbahnung von Spielpartien. Die Methoden des ServerKommunikators: Der SpielClientManager kann sich vollständig abmelden. Eine Wiederanmeldung erlaubt dann nicht mehr den Eintritt in die schon laufenden Chats und Spielpartien. Genauer gesagt, gibt es dann keine spezifische Wiederanmeldung. Wenn der SpielClientManager einen ClientSpielEinleitungsKommunikator bereitstellt, dann kann er damit in ein vorbereitetes Spiel, das noch Mitspieler sucht, eintreten (Methode betreteSpiel) oder ein vorbereitetes oder laufendes Spiel, das beobachtbar ist, kontinuierlich beobachten (Methode beobachteSpiel). In beiden Fällen erhält der SpielClientManager einen individuellen ServerSpielEinleitungsKommunikator, der nur ihm das Verlassen des Spiels ermöglicht. Mit dem ClientSpielEinleitungsKommunikator stellt der SpielClientManager Methoden bereit, die der SpielServerManager wie folgt benutzt: Mit setzeSpielChat geschieht derselbe Austausch von ServerChatKommunikator und ClientChatKommunikator wie für den globalen Chat und bewirkt einen lokalen Chat, der auf diese Spielpartie begrenzt ist, d.h. auf die Mitspieler und (eventuell zugelassene) Beobachter. Mit zeigeSpiel übergibt der SpielServerManager das ausgewählte Objekt der Klasse Spiel. Für eine noch nicht laufende Partie wird die Information über die Spieler mit den Methoden neuerSpieler und entferneSpieler aktualisiert. zeigeSpiel wird zu diesem Zweck nicht aufgerufen! Wenn die vorgegebene Zahl der Spieler erreicht ist und der Spielleiter den Spielbeginn mit der Methode spielStarten des ServerSpielLeiterKommunikator gewünscht hat, dann wird der SpielServerManager mit der Methode start den Austausch von ServerSpielKommunikator und ClientSpielKommunikator veranlassen (hier wurde also das Konzept des ServerSpielers nochmals verfeinert, der ServerSpielKommunikator entspricht aber der Idee des ServerSpielers aus der Spezifikation 1.1). Einen solchen ServerSpielLeiterKommunikator erhält der SpielClientManager durch Aufrufen der Methode neuesSpiel von ServerKommunikator. Dabei muß der SpielClientManager eine Implementierung von ClientSpielLeiterKommunikator übergeben. Zusätzlich zu den Methoden des ClientSpielEinleitungsKommunikators muß hier noch die Methode zeigeSpielVarianten implementiert sein, die dem SpielServerManager erlaubt, alle bei ihm gespeicherten SpielVarianten-Objekte zu übergeben, damit der SpielClientManager davon eine aussuchen kann. Der ServerSpielLeiterKommunikator erlaubt dem SpielClientManager, ein Spiel-Objekt als Ausgangspunkt für eine neue Spielpartie vorzuschlagen. Dies geschieht mit der Methode setzeSpiel, wobei der SpielServerManager den Vorschlag ablehnen kann. Laut Spezifikation müssen hier auch parametrisierte zufällige Spielbretter vorgeschlagen werden. Mit der Methode spielStarten kann der SpielClientManager (der hier als Spielleiter für eine Partie auftritt) die Partie zum Starten freigeben. Wirklich gestartet wird aber durch den SpielServerManager mittels der Methode start des ClientSpielEinleitungsKommunikators. Spielen von Partien ------------------- Durch die Methode start übergibt der SpielServerManager dem SpielClientManager einen für diese Partie und diesen Spieler individuellen ServerSpielKommunikator und erhält einen ClientSpielKommunikator. Der SpielClient übermittelt dem ServerSpielKommunikator die Züge mit der Methode macheZug (oder verläßt die Spielpartie endgültig mit spielVerlassen). Der SpielServer übermittelt dem ClientSpielKommunikator die offiziell gemachten Spielzüge (Methode zeigeSpielzug), aktuelle Informationen, Warnungen, daß der SpielClient nur noch kurze Zeit für seinen Zug hat und eben den gesamten Spielverlauf, wenn der SpielServer die Partie für beendet erklärt hat (Methode spielBeendet). Wiedergabe von gespeicherten Partien ------------------------------------ Hierzu ist in den Interfaces nichts vorgegeben, da dies ausschließlich auf der Client-Seite stattfindet. Die Erklärungen zur Klasse Spielverlauf sollten hier aureichen. Abbrechen --------- Der SpielServerManager kann das geplante Abbrechen seiner Verbindung zum SpielClientManager mittels der Methode serverShutdown des Interfaces ClientKommunikator ankündigen. Der SpielServerManager kann eine Spielpartie beim ClientKommunikator abmelden mit der Methode entferneSpiel. Der SpielServerManager kann einen Chat-Teilnehmer beim ClientChatKommunikator abmelden mit der Methode teilnehmerAbgemeldet. Der SpielServerManager kann einen angemeldeten Mitspieler beim ClientSpielEinleitungsKommunikator abmelden mit der Methode entferneSpieler. Der SpielClientManager kann sich vom SpielServerManager abmelden mit der Methode abmelden von Server. Der SpielClientManager kann sich von einem in der Vorbereitung befindlichen Spiel mit der Methode spielVerlassen von ServerSpielEinleitungsKommunikator abmelden. Der SpielClientManager kann einen SpielClient von einer laufenden Partie mit der Methode spielVerlassen von ServerSpielKommunikator abmelden. Ein Abbrechen einer laufenden Spielpartie geschieht durch den Absprung eines Mitspielers nicht. Die Würmer bleiben lediglich liegen, und dieser Spieler wird übergangen. Er wird nicht mehr bei der Siegerermittlung berücksichtigt und kann insbesondere nicht mehr gewinnen. Rekonstruktion nach Verbindungswiederaufnahme --------------------------------------------- Die Methode partieWiederherstellen() des Interfaces ClientKommunikator dient dem SpielServerManager dazu, alle Chats und Spielpartien bei einem wiederangemeldeten SpielClientManager zu rekonstruieren, wenn auf der Client-Seite die entsprechenden Kommunikatoren verloren gegangen sind. Davon darf einfach ausgegangen werden, wenn die Anmeldung nicht mit dem früheren ClientKommunikator erfolgt ist. Im Einzelnen geschieht folgendes: Mit setzeChat von ClientKommunikator stellt der SpielServerManager den globalen Chat wieder her. Der zurückgegebene ClientChatKommunikator muß nicht auf diese Situation zugeschnitten sein. Der SpielServerManager muß allerdings alle Informationen neu senden, insbesondere die bisherigen Nachrichten. Für jede frühere Spielpartie ruft der SpielServerManager einmal partieWiederherstellen() auf und erhält dann einen unspezifischen (nicht speziell für den Zweck der Wiederherstellung gemachten) ClientSpielEinleitungsKommunikator. Dessen Methoden ruft der SpielServerManager auf: setzeSpielChat dient der Wiederherstellung des lokalen Chats, die wie die des globalen Chats gemacht werden kann. zeigeSpiel muß nun den aktuellen Spielzustand übergeben, worauf mit start die Kommunikation zwischen SpielClient und SpielServer wiederhergestellt wird. Dabei wird wiederum ein nicht auf die Wiederherstellung zugeschnittener ClientSpielKommunikator zurückgegeben.