Tutorials/Netzwerk: Unterschied zwischen den Versionen
(→Dedizierte Methode: Die Klasse {{Doc|NetzwerkVerbindung}}) |
K (→Dedizierte Methode: Die Klasse {{Doc|NetzwerkVerbindung}}) |
||
Zeile 226: | Zeile 226: | ||
Dieses Modul ist sicherlich eines der komplexeren der Engine. Ein Blick auf konkrete Projekte kann hier sicherlich helfen. Hier findest du: | Dieses Modul ist sicherlich eines der komplexeren der Engine. Ein Blick auf konkrete Projekte kann hier sicherlich helfen. Hier findest du: | ||
* [https://github.com/engine-alpha/beispiel-netzwerk-simple/archive/master.zip Ein einfaches Projekt (mit Broadcast-Server)] | * [https://github.com/engine-alpha/beispiel-netzwerk-simple/archive/master.zip Ein einfaches Projekt (mit Broadcast-Server)] | ||
− | * Ein komplexeres Projekt (mit dediziertem Server) | + | * [https://github.com/engine-alpha/beispiel-netzwerk-complex/archive/master.zip Ein komplexeres Projekt (mit dediziertem Server)] |
Beide Projekte demonstrieren recht anschaulich die wichtigsten Arbeitsschritte bei der Netzwerkkommunikation mit der Engine. | Beide Projekte demonstrieren recht anschaulich die wichtigsten Arbeitsschritte bei der Netzwerkkommunikation mit der Engine. |
Version vom 1. November 2014, 04:47 Uhr
Achtung!!!
Dieses Tutorial baut auf einigen Grundprinzipien der Kommunikation zwischen Computern und anderem Grundwissen auf. Verwendet werden:
- Server-Client-Modell
- Schicht 3 & 4 Kommunikation: IP & Port
- Grundlegendes Threading in Java (nur für dedizierte Server)
Inhaltsverzeichnis
Ziel des Tutorials
Nach diesem Tutorial kannst du die Netzwerkfunktionen der Engine benutzen:
- Server und Clients für Computer-zu-Computer-Kommunikation aufbauen.
- Informationen zwischen Computern austauschen.
Grundprinzip: Server und Client
Netzwerkverbindungen zwischen Computern werden im Allgemeinen als Server-Client-Verbindungen bezeichnet. Das Grundprinzip funktioniert so:
- Ein Server bietet seine Dienste beliebig vielen Clients an.
- Ein Client kann einen Server kontaktieren, um seine Dienste in Anspruch zu nehmen.
Entsprechend gibt es in der Engine Alpha die Klassen Server
und Client
. Server und Client können beide sowohl Nachrichten empfangen als auch verschicken.
Einen Server erstellen
Ein Server kann einfach mit dem Konstruktor aufgebaut werden:
public Server(int port)
Der Port ist eine Kennzahl, mit der verschiedene Internetdienste sich unterscheiden können, z.B. hat HTTP Port 80. Du kannst für deinen Server einen beliebigen Port wählen, allerdings sollte deine Portnummer größer als 1024 sein, da bis dahin die Nummern für bekannte Dienste reserviert sind (sog. Well Known Ports).
Damit ist der Server auch schon erstellt und bereit, Clients zu empfangen.
Einen Client erstellen
Nachdem ein Server begonnen hat zu warten, macht es erst Sinn, den Client zu starten. Der Client kann mit diesem Konstruktor aufgebaut werden:
public Client(String ipAdresse, int port)
Der Parameter ipAdresse
beschreibt die IP-Adresse des Servers, mit dem sich der Client verbinden soll. Sie wird als String übergeben, z.B. "198.162.0.2", oder "123.56.23.1".
Der Port funktioniert wie bereits in der Server-Sektion beschrieben. Damit sich der Client auch wirklich mit dem Server verbindet, muss er nicht nur die IP-Adresse des Servers angeben sondern auch die Portnummer, auf welcher der Server lauscht.
Nachrichten verschicken und empfangen
Die grundlegenden Methoden
Hat sich ein Client mit dem Server verbunden, können beide Seiten Nachrichten austauschen. Hierfür haben beide Klassen dieselben Methoden:
Sende-Methode | Funktion (Sende-Methode) | -> | Zugehörige Empfange-Methode | Funktion (Empfange-Methode) |
---|---|---|---|---|
public void sendeString (String string) |
Sendet den übergebenen Wert des entsprechenden Datentyps an den Kommunikationspartner. | empfangeString (String string) |
Wird automatisch aufgerufen, wenn der Kommunikationspartner, einen Wert des entsprechenden Datentyps sendet. Der Wert wird im Parameter übergeben. | |
public void sendeInt (int i) |
empfangeInt (int i) | |||
public void sendeByte (byte b) |
empfangeByte (byte b) | |||
public void sendeDouble (double d) |
empfangeDouble (double d) | |||
public void sendeChar (char c) |
empfangeChar (char c) | |||
public void sendeBoolean (boolean b) |
empfangeBoolean (boolean b) | |||
public void verbindungSchließen() |
Informiert den Kommunikationspartner, dass die Kommunikation ab sofort eingestellt wird (und schließt dann direkt die Verbindung). | public void verbindungBeendet() |
Wird aufgerufen, wenn der Kommunikationspartner die Verbindung beendet (anschließend wird die Verbindung auch geschlossen). |
Senden & Empfangen: Client
Senden und Empfangen als Client funktioniert, indem man eine eigene Klasse (z.B. MeinClient
) von Client
ableitet. Alle Sende&Empfange-Methoden sind bereits im Client enthalten. Die Sende-Methoden kann man also direkt ausführen (da sie von Client vererbt werden), die Empfange-Methoden, auf die man reagieren will, kann man einfach überschreiben. Konkret funktioniert dein Client also zum Beispiel so:
import ea.*;
public class MyClient
extends Client {
//...
public MyClient(String ipAdresse) {
//Super-Konstruktor aufrufen. Portnummer ist identisch mit der des Servers.
super(ipAdresse, 12345);
//weiteres ...
}
//Ich will auf String-Sendungen reagieren. Deshalb überschreibe ich die
//entsprechende Empfange-Methode!
@Override
public void empfangeString(String string) {
if(string.equals("versionsnummer-angeben")) {
//Ich kann die sende-Methode einfach aufrufen
sendeInt(3);
}
}
//...
}
Senden & Empfangen: Server
Senden und empfangen ist beim Server komplizierter. Ein Client hat immer nur eine Verbindung zu einem Server, daher ist klar, wohin er Daten sendet und woher er sie empfängt. Ein Server hingegen kann mehrere Verbindungen zu mehreren Clients gleichzeitig haben. Daher muss man sich bei jeder zu sendenden Nachricht fragen "Wohin damit?" und bei jeder empfangenen Nachricht "Woher kommt die?".
Hierzu gibt es zwei Möglichkeiten in der Engine:
- Broadcast-Methode: Jede zu sendende Nachricht wird an jeden Client geschickt. Bei empfangenen Nachrichten wird nicht unterschieden, von welchem Client sie kommen.
- Dedizierte Methode: Jede Verbindung zu jedem Client, die der Server hat, wird einzeln behandelt. Sie hat jeweils eigene Sende/Empfange-Methoden nur für diesen einen Client.
Broadcast-Methode
Diese Methode ist die einfachere. Senden funktioniert hierbei genau wie beim Client
. Die Klasse Server
hat ebenfalls alle Senden-Methoden. Ein solcher Aufruf sorgt dafür, dass die zu übermittelnde Nachricht direkt an alle Clients übermittelt wird.
Empfangen ist ein klein wenig umständlicher. Hierfür musst du das Interface Empfaenger
implementieren, welches einfach nur alle 'Empfange-Methoden beinhaltet. Dieses kannst du anschließend als globalen Empfänger beim Server anmelden. Hierfür gibt es in der Klasse Server
die Methode:
public void globalenEmpfaengerSetzen (Empfaenger e)
Nach Aufruf dieser Methode wird für jede Nachricht, die der Server empfängt - ganz egal von welchem Client - die entsprechende Empfangen-Methode des übergebenen Empfängers ausgeführt. Konkret kann das zum Beispiel so aussehen:
public class MeineBroadcastServerApp
implements Empfaenger {
//Der eigentliche Server
private Server server;
// ...
public MeineBroadcastServerApp() {
//Beliebige Portnummer (>1024); der zugehörige Client muss
//Allerdings dieselbe Portnummer haben!
server = new Server(54321);
//Melde dieses Objekt als globalen Empfänger für den Server an.
server.globalenEmpfaengerSetzen(this);
}
/* --- Empfaenger-Methoden --- */
@Override
public void empfangeString (String string) {
server.sendeString("Info an alle: Ich habe gerade \" " + string + " \" übersendet bekommen.");
}
@Override
public void verbindungBeendet () {
server.sendeString("Info an alle: Jemand hat gerade seine Verbindung zu mir getrennt");
}
// Info: Diese Methoden müssen implementiert werden.
// Sie bleiben leer, da sie hier nicht verwendet werden.
@Override
public void empfangeInt (int i) {
}
@Override
public void empfangeByte (byte b) {
}
@Override
public void empfangeDouble (double d) {
}
@Override
public void empfangeChar (char c) {
}
@Override
public void empfangeBoolean (boolean b) {
}
}
Dedizierte Methode: Die Klasse NetzwerkVerbindung
Möchtest du, dass dein Server jeden Client einzeln behandelt, musst du anders vorgehen.
Für jede Verbindung eines Servers zu einem Client gibt es eine Instanz der Klasse NetzwerkVerbindung
. Diese enthält alle Sende-Methoden. Für Empfange-Methoden hingegen nimmst du - wie beim Broadcast-Verfahren - ein Empfaenger
-Objekt. Melde es an der NetzwerkVerbindung
an mit:
public void empfaengerHinzufuegen(Empfaenger e)
Der Server
gibt seine Netzwerkverbindung über eine Methode heraus:
public NetzwerkVerbindung naechsteVerbindungAusgeben ()
Die Methode naechsteVerbindungAusgeben()
gibt die nächste NetzwerkVerbindung
. Solange es noch Clients gibt, deren Netzwerverbindungen nicht durch diese Methode ausgegeben wurden, wird die älteste dieser Verbindungen zurückgegeben (First Come First Served - Verfahren). Wird diese Methode aufgerufen, wenn es keine neuen Verbindungen mehr gibt, bleibt der Thread, in dem diese Methode aufgerufen wurde eingefrohren (im Wartezustand), bis sich ein neuer Client am Server anmeldet.
Dieses Modul ist sicherlich eines der komplexeren der Engine. Ein Blick auf konkrete Projekte kann hier sicherlich helfen. Hier findest du:
Beide Projekte demonstrieren recht anschaulich die wichtigsten Arbeitsschritte bei der Netzwerkkommunikation mit der Engine.
Sonstiges zu den Netzwerkfunktionen
Firewall
Es kann passieren, dass deine Firewall den Internetzugang deines Spiels blockiert. Wenn du keine Verbindung zustande bekommst, könnte es an so einer Blockade liegen. Füge dann eine Ausnahme in deiner Firewall für dein Spiel hinzu.
Automatisches Ressourcen aufräumen
Normalerweise entsteht beim Schließen von Verbindungen zwischen Computern (sog. Streams) schnell "Müll": Wenn man vergisst, nach Abbrechen der Verbindung auf einer Seite, dasselbe auch auf der anderen Seite zu tun, können schnell Fehler passieren. Die Netzwerkfunktion der Engine stellt sicher, dass solche Situationen nicht passieren können. Wenn du selbst eine Netzwerkkommunikation (außerhalb der Engine) implementierst, solltest du darauf achten, alle Streams zu schließen, die du geöffnet hast.