Netzwerkprogrammierung: Unterschied zwischen den Versionen
Zeile 175: | Zeile 175: | ||
==Implementierung== | ==Implementierung== | ||
<code> | <code> | ||
<b>public class GossipServer extends Server </b> | <b>public class GossipServer extends Server </b> | ||
{ | { | ||
private int port = 5555; | private int port = 5555; | ||
'''//Attribute fuer die Listen''' | '''//Attribute fuer die Listen''' |
Version vom 8. Dezember 2013, 09:40 Uhr
Allgemeines
Netzwerkprogrammierung beschäftigt sich mit der Entwicklung von verteilten Anwendungen.
Dabei kommuniziert zumeist ein Server mit mehreren Clients.
Best Practices für Netzwerk-Protokolle
siehe: Protokoll_(IF)
Client-Programmierung
Für die Client-Programmierung stehen im Zentralabitur die Schnittstellen Connection.java und Client.java zur Verfügung.
Connection.java vs. Client.java
Die Schnittstellen fürs Zentralabitur stellen zwei Klassen zur Verfügung, mit denen auf der Client-Seite gearbeitet werden kann:
- Connection.java hat die folgenden Methode, um Nachrichten vom Server zu empfangen:
public String receive()
- Client.java hat die folgenden Methode, um Nachrichten vom Server zu empfangen:
public void processMessage(String pMessage)
Die einfachere (=mit weniger Möglichkeiten) ist die Klasse Connection.java. Diese beiden Klassen werden in unterschiedlichen Szenarien benutzt:
- Connection.java wird genutzt, wenn der Server nur auf Anfragen des Clients reagiert, wie z.B. bei Mailprotokollen (SMTP oder POP3).
- Client.java wird genutzt, wenn der Server von sich aus aktiv werden kann. Das ist z.B. bei einem Chat der Fall, denn hier muss der Server Nachrichten eines anderen Client von sich aus an alle Clients weiterleiten können.
Connection: Standardvorgehen
In der Regel wird wie folgt vorgegangen:
- Verbindung herstellen:
- Man verbindet sich mit dem Server, indem man ein neues
Connection
-Objekt erzeugt. - Man empfängt die Willkommensnachricht des Servers und wertet sie ggf. aus.
- Man verbindet sich mit dem Server, indem man ein neues
- Einfache Nachrichten senden/empfangen:
- Man baut einen String
zumServer
zusammen. InzumServer
ist die Nachricht enthalten, die man zum Server schicken möchte. - Man gibt
zumServer
an die Konsole aus - zur Kontrolle! - Man schickt
zumServer
an den Server. - Man empfängt die Antwort des Servers und speichert sie in
vomServer
. - Man gibt
vomServer
an die Konsole aus - zur Kontrolle! - Jetzt kann man
vomServer
auswerten.
- Man baut einen String
- Mehrzeilige Nachrichten empfangen: Wenn eine Serverantwort mehrzeilig ist, dann wird sie in der Regel mit einem einzelnen Punkt (bzw. einer anderen Endmarke) beendet. Darauf reagiert man wie folgt:
- Man empfängt eine Zeile vom Server und speichert sie in
vomServer
. - Eine
while
-Schleife, die so lange läuft, bisvomServer
ein einzelner Punkt (bzw. die Endmarke) ist. In der Schleife passiert folgendes...- Man gibt
vomServer
an die Konsole aus - zur Kontrolle, ob die Antwort vom Server wie erwartet ist. vomServer
auswerten.- Man empfängt die nächste Zeile vom Server und speichert sie in
vomServer
.
- Man gibt
- Man empfängt eine Zeile vom Server und speichert sie in
Connection: Standardvorgehen in Java
- Verbindung herstellen:
Connection verbindung = new Connection("192.168.100.100", 4242);
//IP und Port sind nur Beispiele!String vomServer = verbindung.receive();
- Einfache Nachrichten senden/empfangen:
String zumServer = "LOGIN "+username+" "+passwort;
// das ist ein Beispiel für ein LoginSystem.out.println(zumServer);
verbindung.send(zumServer);
vomServer = verbindung.receive();
.System.out.println(vomServer);
if(vomServer.startsWith("+OK")){
//Beispiel für eine Auswertung
- Mehrzeilige Nachrichten empfangen:
vomServer = verbindung.receive();
while(vomServer.equals(".") == false){
System.out.println(vomServer);
ergebnis.append(vomServer);
// Beispiel für eine Auswertung: An eine ergebnis-Liste anhängen.vomServer = verbindung.receive();
.
Client.java: Standardvorgehen in Java
Von Client.java dagegen wird eine Unterklasse gebildet, die die Fähigkeiten von Client.java erbt. Dabei muss die Methode public void processMessage(String pMessage)
überschrieben werden, damit man auf die Nachrichten des Servers reagieren kann.
Beispiel ChatClient:
public class ChatClient extends Client{
public ChatClient(){
//Konstruktor der Klasse Client aufrufen!
//Dadurch wird die Verbindung zum (selbstprogrammierten) Chatserver hergestellt
super("192.168.100.100", 4444);
}
// Überschreiben der Methode processMessage!
public void processMessage(String pMessage){
// jetzt das Protokoll abarbeiten
if(pMessage.startsWith("neu")){
// usw.
}
// usw.
}
}
Server-Programmierung
Für die Server-Programmierung gibt es die Klasse Server.java. Genauso wie bei Client.java muss die eigene Klasse von Server.java erben, um die Fähigkeiten von Server.java zu übernehmen, z.B.:
public class ChatServer extends Server{
public ChatServer(int pPort){
// Konstruktor der Klasse Server aufrufen!
// Dadurch wird der Server erzeugt.
super(pPort);
}
Wichtig ist, dass die Klasse ChatServer dann die folgenden Methoden überschreibt, um auf Ereignisse bei den Clients angemessen reagieren zu können:
public void processMessage(String pClientIP, int pClientPort, int pMessage)
public void processNewConnection(String pClientIP, int pClientPort)
public void processClosedConnection(String pClientIP, int pClientPort)
Von sich aus kann der Server aktiv werden, indem er...
- einem Client eine Nachricht schickt:
public void send(String pClientIP, int pClientPort, String pMessage)
- allen Clients eine Nachricht schickt:
public void sendToAll(String pMessage)
- einen Client rausschmeißt:
public void closeConnection(String pClientIP, int pClientPort)
- den Server zumacht:
public void close()
Wichtige Methoden der Klasse String
siehe String
Beispiel: GossipServer
Die Techniken der Netzwerkprogrammierung werden hier am Beispiel des GossipServer gezeigt.
Die Projekte dazu kann man hier herunterladen: kaibel.de
Spezifikation
Der GossipServer soll dazu dienen, Tratsch (=Gossip) zu speichern und weiter zu verbreiten. Ob das sinnvoll, moralisch bedenklich oder sogar schädlich ist, soll hier nicht diskutiert werden. Es gelten die folgenden Anforderungen:
- Gossip schreiben: Man kann sich am GossipServer anmelden und eine neue Nachricht hinterlassen. Die wird als Text und mit Datum/Uhrzeit gespeichert. Dafür soll eine Klasse
GossipClient.java
entwickelt werden. - Gossip abonnieren: Man kann sich am GossipServer anmelden und bekommt dann jede neu eintreffende Nachricht sofort geschickt. Das soll die Klasse
GossipTicker.java
erledigen. - Gossip-Archiv lesen: Man kann alle Nachrichten von einem oder mehreren Tagen vom GossipServer abrufen. Dies erledigt die Klasse
GossipClient.java
. - Gossip speichern und die Abonnentenliste verwalten: Der
GossipServer
speichert die Nachrichten und die Abonnenten in Listen und reagiert auf die o.g. Anforderungen desGossipClient
und desGossipTicker
.
Arbeitsschritte
- Protokoll definieren
- GossipServer:
- Implementationsdiagramm
- Implementierung
- GossipClient und GossipTicker:
- Entscheidung für programmtechnische Grundlage: Client oder Connection?
- Implementationsdiagramme
- Implementierungen
Protokoll
Client sendet | Server antwortet (einem Client) |
Server an alle |
---|---|---|
Client meldet sich an | +OK Willkommen beim GossipServer | |
Gossip schreiben: NEU <nachricht> |
+OK Nachricht akzeptiert -ERR Nachricht zu lang |
an alle Abonennten: NEU <zeit>--<nachricht> --- |
Gossip abonnieren: ABO |
+OK Abo akzeptiert | --- |
Gossip-Archiv lesen: ARCHIV <zeit1> <zeit2> |
+OK GossipArchiv <zeit 1>--<nachricht 1> ... <zeit n>--<nachricht n> . -ERR Datum falsch |
--- |
QUIT | +OK Tschuess Trennt die Verbindung |
--- |
<unbekannter Befehl> | -ERR unbekannter Befehl | --- |
<Parameter fehlt> | -ERR Parameter fehlt | --- |
Implementationsdiagramm
- GossipServer erbt von Server.
- GossipServer muss die Methoden
processMessage
,processNewConnection
undprocessClosedConnection
überschreiben.processMessage
ist notwendig, um Nachrichten von Clients gemäß Protokoll verarbeiten zu können.processNewConnection
ist notwendig, um neue Clients begrüßen zu können.processClosedConnection
ist notwendig, um Clients aus der Abonnentenliste streichen zu können, wenn sie sich abmelden
- GossipServer muss Nachrichten und Abonnenten verwalten können; dafür braucht er jeweils eine Liste.
- Die privaten Methoden sind vorteilhaft, damit die Methode
processMessage
nicht völlig aufgebläht wird.
Implementierung
public class GossipServer extends Server
{
private int port = 5555;
//Attribute fuer die Listen
private ListWithViewer abonnentenListe;
private ListWithViewer nachrichtenListe;
//Konstruktor
public GossipServer()
{
den Konstruktor der Super-Klasse Server aufrufen
super(port);
System.out.println("GossipServer wird gestartet");
//die Listen als leere Listen erzeugen
abonnentenListe = new ListWithViewer();
nachrichtenListe = new ListWithViewer();
}
public void processNewConnection(String pClientIP, int pClientPort)
{
send(pClientIP, pClientPort, "+OK Willkommen beim Gossipserver");
//weiter ist hier nichts zu tun.
//denn Abonennten werden erst in die Liste geschrieben, wenn sie sich registriert haben.
}
public void processMessage(String pClientIP, int pClientPort, String pMessage)
{
if(pMessage.startsWith("NEU")){
String nachricht = pMessage.substring(4);
send(pClientIP, pClientPort, "+OK Nachricht akzeptiert");
nachrichtHinzufuegen(pClientIP, pClientPort, nachricht);
nachrichtAnAbonnentenWeiterleiten(nachricht);
return;
}
if(pMessage.equals("ABO")){
send(pClientIP, pClientPort, "+OK Abo akzeptiert");
abonnentHinzufuegen(pClientIP, pClientPort);
return;
}
if(pMessage.startsWith("ARCHIV")){
String[] splits = pMessage.split(" ");
String datumVon = splits[1];
String datumBis = splits[2];
sendeNachrichten(pClientIP, pClientPort, datumVon, datumBis);
return;
}
if(pMessage.equals("QUIT")){
send(pClientIP, pClientPort, "+OK Tschuess");
closeConnection(pClientIP, pClientPort);
return;
}
// kein Befehl trifft zu!
send(pClientIP, pClientPort, "-ERR unbekannter Befehl: "+pMessage);
}
public void processClosedConnection(String pClientIP, int pClientPort)
{
abonnentEntfernen(pClientIP, pClientPort);
}
private void nachrichtHinzufuegen(String pClientIP, int pClientPort, String pNachricht)
{
Nachricht neueNachricht = new Nachricht(pNachricht);
this.nachrichtenListe.append(neueNachricht);
}
private void sendeNachrichten(String pClientIP, int pClientPort, String pDatumVon, String pDatumBis)
{
send(pClientIP, pClientPort, "+OK GossipArchiv");
nachrichtenListe.toFirst();
while(nachrichtenListe.hasAccess()) {
Nachricht dieNachricht = (Nachricht) nachrichtenListe.getObject();
if(dieNachricht.getZeit().compareTo(pDatumVon)>= 0 && dieNachricht.getZeit().compareTo(pDatumBis) <= 0){
send(pClientIP, pClientPort, dieNachricht.getZeit()+" "+dieNachricht.getText());
}
nachrichtenListe.next()
}
this.send(pClientIP, pClientPort, ".");
}
private void abonnentHinzufuegen(String pClientIP, int pClientPort)
{
Abonnent neuerAbonnent = new Abonnent(pClientIP, pClientPort);
this.abonnentenListe.append(neuerAbonnent);
}
private void abonnentEntfernen(String pClientIP, int pClientPort)
{
abonnentenListe.toFirst();
while(abonnentenListe.hasAccess()){
Abonnent derAbonnent = (Abonnent)this.abonnentenListe.getObject();
if(pClientIP.equals(derAbonnent.getIp())&& pClientPort == derAbonnent.getPort()){
this.abonnentenListe.remove();
break;
}
abonnentenListe.next()
}
}
private void nachrichtAnAbonnentenWeiterleiten(String pMessage)
{
abonnentenListe.toFirst();
while(abonnentenListe.hasAccess()){
Abonnent derAbonnent = (Abonnent)this.abonnentenListe.getObject();
this.send(derAbonnent.getIp(),derAbonnent.getPort(), "+OK NEU "+pMessage);
abonnentenListe.next()
}
}
public static void main(String[] args) {
new GossipServer();
}
}
GossipClient / GossipTicker
Auf der Client-Seite gibt es zwei Anwendungen:
- Der GossipClient kann Nachrichten an den GossipServer schicken und mit dem Befehl ARCHIV Nachrichten vom Server abrufen.
- Der GossipTicker kann Nachrichten abonnieren; er bekommt dann jede neue Nachricht geschickt.
Entscheidung: Client.java oder Connection.java?
- Die Klasse GossipClient benutzt für die Verbindung zum Server am besten die Klasse Connection.java. Denn hier findet die Kommunikation nur in request-response-Zyklen statt. GossipClient erbt von der Klasse Client.
- Die Klasse GossipTicker benutzt für die Verbindung zum Server am besten die Klasse Client.java. Denn der GossipServer wird von sich aus aktiv und mit der Klasse Client.java kann der GossipTicker die eintreffenden Nachrichten nebenläufig verarbeiten (d.h. er kann nebenbei noch andere Dinge tun). Wenn man hier die Klasse Connection.java verwenden würde, dann wäre der GossipClient immer komplett blockiert, während er auf eine neue Nachricht vom Server wartet. GossipTicker hat ein Attribut vom Typ Connection.
Implementierung GossipClient
Hier ist nur der Teil des GossipClients implementiert, der mithilfe des Befehls ARCHIV Nachrichten vom Server abruft.
import javax.swing.JOptionPane;
public class GossipClient {
private Connection verbindung;
public GossipClient(String pIp, int pPort){
// verbindung mit pIp und pPort initialisieren
verbindung = new Connection(pIp, pPort);
String willkommen = verbindung.receive();
if(willkommen.equals("+OK Willkommen beim Gossipserver")){
nachrichtenAbrufen();
}
else{
System.err.println(willkommen);
}
// verbindung schliessen
verbindung.close();
}
public void nachrichtenAbrufen(){
String vonZeit = JOptionPane.showInputDialog("Start-Zeit: ", "2013-03-21--10:00:30");
String bisZeit = JOptionPane.showInputDialog("End-Zeit: ", "2013-04-21--11:00:30");
//TODO gemaess Protokoll an den Server schicken
String message = "ARCHIV "+vonZeit+" "+bisZeit;
verbindung.send(message);
//TODO Antwort auslesen und an ergebnis anhängen packen
ListWithViewer ergebnis = new ListWithViewer();
String antwort = verbindung.receive();
if(antwort.equals("+OK GossipArchiv")){
antwort = verbindung.receive();
while(!antwort.equals(".")){
String[] splits = antwort.split(" ");
// die Zeit der Nachricht ist in splits[0]
// der Text der Nachricht ist in splits[1]
ergebnis.append(splits[1]);
antwort = verbindung.receive();
}
return;
}
else{
System.err.println(antwort);
return;
}
}
public static void main(String[] args) {
new GossipClient("127.0.0.1", 5555);
}
}
Implementierung GossipTicker
Hier wird vorausgesetzt, dass es eine Klasse GossipTickerGUI
gibt, die über eine Methode nachrichtAnzeigen(String pText)
verfügt.
public class GossipTicker extends Client{
private GossipTickerGUI gui;
public GossipTicker(String pClientIP, int pClientPort){
super(pClientIP, pClientPort);
gui = new GossipTickerGUI();
}
public void processMessage(String pMessage) {
if(pMessage.equals("+OK Willkommen beim Gossipserver")){
this.send("ABO");
return;
}
if(pMessage.startsWith("+OK NEU")){
String text = pMessage.substring(8);
gui.nachrichtAnzeigen(text);
return;
}
}
public static void main(String[] args) {
GossipTicker gt = new GossipTicker("127.0.0.1", 5555);
}
}