Sommersemester – Vorlesungen

Ich habe mich entschieden, das Portfolio ein wenig anders zu strukturieren als im letzten Semester. Anstatt nach Themen zu unterteilen unterteile ich dieses Mal nach Vorlesungen, weil ich es einfach für übersichtlicher und einfacher halte.

Vorlesung 1 – Netzwerkkommunikation

Grundlagen Netzwerke

Der wesentliche Teil der ersten Vorlesung drehte sich um Netzwerkkommunikation und Servertechnologien. Da ich durch die Arbeit schon etwas Vertraut mit dieser Thematik war, waren Begriffe wie LAN, VAN, WAN usw. für mich nichts neues. In meinem Job durfte ich schon viel mit Switchen und Servern arbeiten. Dennoch war sehr interessant zu erfahren, wie sich die Technologie von den Anfängen bis heute entwickelt hat (z.b. Terminals usw.).

Eine Switch ist im Grunde ein „schlauer“ Hub:

  • Kann senden und empfangen zeitgleich (die Leitung ist nicht „belegt“)
  • Prüft, welche Netzwerkarea relevant für die Informationen ist
  • Arbeitet auf der 2. Ebene des ISO/OSI-Modells

Router hingegen verbinden oft zwei Netzwerke miteinander. Oft werden Router im Heimnetz verwendet, um das private Netzwerk mit dem Internet zu verbinden. Er geht noch tiefer als eine Switch und weiß, welcher Rechner welche Adresse im Internet angefragt hat und kann Informationen gezielt schicken.

Sehr interessant und neu waren die Tiefseekabel, die die Kontinente miteinander durch die Weltmeere verbinden. Die Komplexität hinter eines solchen Kabels ist verblüffend, aber auch einleuchtend. Schließlich muss ein solches Kabel viel aushalten, ohne ständig repariert werden zu müssen.

Aufbau eines Tiefseekabels

Java-Applet

Der Begriff Java-Applet ist mir völlig neu. Was verbirgt sich also hinter diesem Begriff?

Java Applets sind eine Variante von Clientseitigen Technologien und ermöglichen Aktivität auf der Client Seite. Sie habe meist eine GUI und werden mit dem HTML-Tag „applet“ in den Code eingebaut.

Java Applets

Wenn also der Webbrowser auf dem Client eine HTML Seite lädt, kann auch nun ein Java Applet geladen werden. Heutzutage werden Java Applets inzwischen kaum noch verwendet, da diese als unsicher und veraltet gelten. Als Java Applets noch weit verbreitet waren, konnten über diese alle möglichen Dateien ohne Sicherheitsvorkehrungen geladen werden.

Serverseitige Technologien

Das allgemeine Thema dieses Semester sind serverseitige Technologien. Das Grundprinzip von serverseitigen Technologien beruht auf der Delegation des HTTP Requests an externe Komponenten. Es erfolgt eine Weiterleitung aufgrund der URL. Folglich erzeugen die Komponenten ein Response.

CGI – Common Gateway Interface

Was das Common Gateway Interface ist eine allgemeine Schnittstelle und zählt zu den serverseitigen Technologien. Das CGI ist ein Standard für den Datenaustausch zwischen dem Webserver und einer Software. Ziel ist die Dynamisierung von Webseiten. Es besteht die Möglichkeit, HTML Seiten dynamisch zu erzeugen.

CGI Programme können in verschiedenen Programmiersprachen geschrieben werden. Die Ausführung erfolgt jedoch mit einer geringen Geschwindigkeit. In CGI Programm dürfen nur bestimmte eingeschränkte Arten von Programmroutinen ausgeführt werden. Ähnlich wie Java Applets werden CGIs aufgrund von Sicherheits- und Performanceproblemen kaum noch verwendet, waren aber von 2000 – 2010 sehr weit verbreitet.

Das Prinzip von CGI beruht darin, bestimmte URLs auf Programmen statt auf Dokumenten abzubilden, die dann vom Webserver gestartet und beendet werden. CGIs zählen also zu den sehr einfachen Schnittstellen.

PHP

PHP ist eine serverseitige Skriptsprache zur Erstellung dynamischer Webseiten oder Webanwendungen. Hierbei wird der Quelltext wird nicht an den Webbrowser sondern an den Interpret übermittelt. PHP ist gut geeignet für Kommandozeilen-orientierte Skripte. Setzt man PHP als CGI ein, kann dies sich negativ aus die auf die Ausführungsgeschwindigkeit auswirken.

Früher stand PHP einmal für „Personal Homepage“; die offizielle Bezeichnung lautet aber heutzutage „Hyper Text Preprocessor“. PHP wird mit einem Anfangs- und einem Endtag in eine HTML Datei eingebunden.

Java Serverlets und JSP

Serverlets sind Java Klassen, deren Instanzen innerhalb eines Webservers Anfragen von Clients beantworten. Dies geschieht dynamisch. Das ist möglich, wenn eine Klasse erstellt wird, welche folgende Schnittstelle implementiert: „javax.servlet.servlet“.

Oft werden Parameter der Anfrage auch als Sitzungsdaten verwendet, um personalisierte Antworten zu erzeugen oder Daten zu speichern und zu verändern. Serverlets werden oft in Form von Java Servlet Pages (JSP) verwendet.

Von all diesen serverseitigen Technologien hatte ich zuvor noch nie etwas gehört außer einmal PHP, als wir im ersten Semester über die verschiedensten Programmiersprachen sprachen. Doch wirklich Kontakt zu PHP hatte ich noch nie.

Objekte

Im zweiten Teil der Vorlesung ging es weiter mit Objekten, deren Zustände, die Serialisierung und die Deserialisierung. Hierzu haben wir eine Aufgabe Im Rahmen eines Fantasy Spiels bearbeitet.

Veranschaulicht wurden Objekte, das Serialisieren usw. mit einem Spielstand von Figuren in einem Spiel. Die Figuren möchte ich, wenn das Spiel beendet wird, speichern und zu einem späteren Zeitpunkt dort fortfahren wo ich aufgehört habe, ohne dass ich von neuem anfangen muss.

Objekte haben einen Zustand und ein Verhalten das Verhalten wird über die Klasse in dem das Objekt steht definiert und der Zustand steckt im Objekt selbst.

Das Ziel ist es, den Zustand von Objekten zu speichern. Dazu dient die Serialisierung von Objekten. Die Serialisierung von Objekten führt also dazu, dass Objekte speicherbar sind und in eine Datei geschrieben werden können.

Zunächst wird ein „FileOutputStream“ erzeugt.  Dieser definiert einen Strom, welcher sich mit einer Datei verbindet, in welche die Objekte hinein gespeichert werden sollen. Als nächstes ist ein „ObjektOutputStream“ vonnöten. Mit dem „ObjektOutputStream“ können Objekte geschrieben werden. „ObjektOutputStreams“ können aber nicht mit einer Datei verbunden werden. Man braucht also einen weiteren Helfer. Die Einbeziehung eines weiteren Helfers bezeichnet man als Verkettung eines Stroms.

Als nächstes schreiben wir die Objekte und schließen den „ObjektOutputStream“. Mit dem „ObjektOutputStream wird auch der „FileOutputStream“ geschlossen.

Die Deserialisierung sorgt dafür, dass die Objekte in den Dateien wieder ausgelesen werden können.

Im Folgenden ist der Code für die Spielfiguren des Fantasy Spiels zu finden:

import java.io.*;

public class Spielfigur implements Serializable {
  int Staerke;
  String typ;
  String[] waffen;

  public Spielfigur(int s, String t, String[] w) {
    Staerke = s;
    typ = t;
    waffen = w;
  }

  public int getStaerke() {
    return Staerke;
  }

  public String getTyp() {
    return typ;
  }

  public String getWaffen() {
    String waffenListe = "";

    for (int i = 0; i < waffen.length; i++) {
      waffenListe += waffen[i] + " ";
    }
    return waffenListe;
  }
}
import java.io.*;

public class SpielSpeicherungTest {
	public static void main (String[] args){
		Spielfigur eins = new Spielfigur(50, "Elb", new String [] { "Bogen", "Schwert", "Staub"});
		Spielfigur zwei = new Spielfigur(200, "Troll", new String [] { "Faeuste", "Axt", "Knueppel"});
		Spielfigur drei = new Spielfigur(120, "Zauberer", new String [] { "Zaubersprueche", "Unsichtbarkeit", "Heilung"});
	
		// der Code, der hier steht, soll mit den Figuren etwas machen, was ihre Zustandswerte ändern könnte

		
		try {
			ObjectOutputStream os = new ObjectOutputStream(new FileOutputStream("Spiel.ser"));
			os.writeObject(eins);
			os.writeObject(zwei);
			os.writeObject(drei);
			os.close();
		} catch (IOException ex) {
			ex.printStackTrace();
		}
		eins = null;
		zwei = null;
		drei = null; //Wir setzen alle auf null, so dass wir auf die Objekte auf dem Heap nicht mehr zugreifen koennen

		try {
			ObjectInputStream is = new ObjectInputStream(new FileInputStream("Spiel.ser")); //Jetzt lesen wir die Datei weider ein
			Spielfigur einsWiederhergestellt = (Spielfigur) is.readObject();
			Spielfigur zweiWiederhergestellt = (Spielfigur) is.readObject();
			Spielfigur dreiWiederhergestellt = (Spielfigur) is.readObject();
			is.close();

			System.out.println("Typ 1. Figur: " + einsWiederhergestellt.getTyp()); //Hier die Ueberpruefung, ob es funktioniert hat
			System.out.println("Typ 2. Figur: " + zweiWiederhergestellt.getTyp());
			System.out.println("Typ 3. Figur: " + dreiWiederhergestellt.getTyp());

		} catch (Exception ex) {
			ex.printStackTrace();
		}
	}
}

Das war der bereits vorgegebene Code. Nun ging es darum, ein Teamobjekt zu erzeugen, welches die drei Spielfiguren referenziert.

import java.io.*;

public class Team implements Serializable {
	Spielfigur [] members = new Spielfigur[3];
		public Team (Spielfigur f1, Spielfigur f2, Spielfigur f3) {
			members[0] = f1;
			members[1] = f2;
			members[2] = f3;
		}
}
import java.io.*;

public class TeamSpeicherungTest {
	public static void main (String[] args) {
		Spielfigur eins = new Spielfigur(50, "Elb", new String [] { "Bogen", "Schwert", "Staub"});
		Spielfigur zwei = new Spielfigur(200, "Troll", new String [] { "Faeuste", "Axt", "Knueppel"});
		Spielfigur drei = new Spielfigur(120, "Zauberer", new String [] { "Zaubersprueche", "Unsichtbarkeit", "Heilung"});
	
		//erzeugt Team Objekt und übergibt referenzen
		
		Team team1 = new Team (eins, zwei, drei);
		
		try {
			ObjectOutputStream os = new ObjectOutputStream(new FileOutputStream("Spiel.ser"));
			os.writeObject(team1);
			
			os.close();
			
		} catch (IOException ex) {
			ex.printStackTrace();
			
		}
		team1 = null;

		try {
			ObjectInputStream is = new ObjectInputStream(new FileInputStream("Spiel.ser"));
			Team team1wdh = (Team) is.readObject();
			is.close();
			
			for (int i = 0; i < 3; i++) {
				int k = i+1;
				System.out.println("Figur " + k + ":");
				//System.out.println(team1wdh.members[0].getTyp());
				System.out.println("Typ: " + team1wdh.members[i].getTyp());
				System.out.println("Stärke: " + team1wdh.members[i].getStaerke());
				System.out.println("Waffen: " + team1wdh.members[i].getWaffen());
				System.out.println(" ");
				
			}

		} catch (Exception ex) {
			ex.printStackTrace();
		}
	}
}

Vorlesung 2

In der zweiten Vorlesung fuhren wir dort fort, wo wir in der ersten Vorlesung aufgehört hatten. Die sogenannten Puffer waren das Thema.

Puffer und File-Objekte

Mit Puffern kann die Effizienz von Lesen und Schreiben von Objekten wesentlich gesteigert werden. Die Klasse „java.io.file“ spielt dabei eine große Rolle. Diese Klasse repräsentiert eine Datei auf dem Datenträger, aber nicht den Inhalt. Um den Inhalt zu repräsentieren benötigen wir ein File-Objekt. Ist ein File-Objekt erstellt, kann man mit dem Puffer arbeiten.

Ein Puffer ist vergleichbar mit einem Einkaufswagen. Anstatt jedes Teil beim Einkaufen einzeln zum Auto zu bringen, kann man alle Teile in einen Einkaufswagen legen und sie gemeinsam „in einem Abwasch“ zum Auto bringen.

Um also nun einen solchen Puffer anzuwenden, ist zunächst die Javaklasse von oben zu importieren. Danach ist je nach Anwendung ein „bufferedReader“ oder ein „bufferedWriter“ zu erstellen.

Mit dem Befehl „flush()“ lässt sich der Puffer wieder leeren. Um das zu veranschaulichen, sollten wir eine Aufgabe bearbeiten. Dabei ging es darum, eine Textdatei anzulegen und diese mit einem Java Programm zu lesen und eine Kopie in dasselbe Verzeichnis zu schreiben.

Der Code ist unten zu finden:

Der Code für das Lesen und für das Schreiben einer Datei war schon gegeben. Nun ging es darum, beides in einem Code zu verbinden.

import java.io.*;

class TextKopierer {
public static void main (String[] args) {
		
		try {
			File meineDatei = new File ("C:\\Users\\Erik\\OneDrive\\Dokumente\\Theoriephase\\DHBW\\Vorlesungen\\Verteilte Systeme\\name.txt");
			FileReader fileReader = new FileReader(meineDatei);
			
			BufferedReader reader = new BufferedReader (fileReader);
			
			
			FileWriter writer = new FileWriter("C:\\Users\\Erik\\OneDrive\\Dokumente\\Theoriephase\\DHBW\\Vorlesungen\\Verteilte Systeme\\namekopie.txt");
			
			BufferedWriter bufferedwriter = new BufferedWriter(writer);
			
			String zeile = null;
			
			while ((zeile = reader.readLine()) != null) {
				bufferedwriter.write(zeile);
				bufferedwriter.newLine();
			}
			reader.close();
			bufferedwriter.flush();
			bufferedwriter.close();
			
		} catch (Exception ex) {
			ex.printStackTrace();
		}
	}

}

Sockets

Weiter ging es mit der Netzwerkprogrammierung über Sockets. Hierzu nahmen wir als Grundlage ein simples Chatprogramm, bei welchem der Server allerdings alle Clients kennen muss, während jeder einzelne Client nur direkt den Server kennen muss.

Besonders wichtig ist es hier, dass wenn ein Client eine Nachricht sendet, diese nicht direkt bei anderen Clients ankommt, sondern zuerst beim Server. Dieser leitet die Nachricht dann an alle Teilnehmer weiter.

Drei Dinge sind für das Verbinden, Senden und Empfangen wichtig:

  1. Wie stellt man als Client eine Verbindung zum Server her?
  2. Wie sendet man Nachrichten an einen Server?
  3. Wie empfängt man Nachrichten vom Server?

Als Client verbindet man sich mit dem Server über einen Socket. Das Senden ist dann mit den Befehl „writer.println(eineNachricht)“ möglich. Erhalten kann man die Nachrichten mit folgendem String: String S = reader.readLine().

Da bereits im zweiten Semester in der Vorlesung Kommunikationssysteme die Begriffe von TCP Server und Ports und Sockets erklärt wurden, werde ich nicht noch einmal auf diese Begriffe eingehen.

Im Folgenden ist der Code für einen Server und für einen Client zu finden welcher einen Tipp des Tages zufällig wählen soll. Um dies zum Laufen zu bringen, muss erst der Server, und dann der Client gestartet werden.

Code Server Tipp des Tages:

package Tipp;

import java.io.*;
import java.net.*;

public class TippDesTagesServer {
	
	String[] tippListe = {"Essen Sie weniger Chips und Gummibärchen.", "Holen Sie sich die eingen Jeans. Nein, Sie sehen darin NICHT dick aus.", "Mit einem Wort: unmöglich!", "Seien Sie ehrlich, nur heute. Sagen Sie Ihrem Chef, was Sie wirklich denken.", "Sie sollten wirklich mal wieder zum Friseur gehen..."};
	//Der Tipp des Tages kommt aus diesen Arrays
	
	public void los() throws IOException {
		
		ServerSocket serverSock = new ServerSocket(4242);
		//Der ServerSocket sorgt dafür, dass diese Serveranwendung auf dem Rechner, auf dem dieser Code läuft, auf Port 4242 auf Client-Anfragen >>lauscht<<.
		
		
		while(true) {
			Socket sock = serverSock.accept();
			//Die accept-Methode blockiert (sie sitzt einfach da), bis eine Anfrage hereinkommt. Dann gibt die Methode einen Socket für die Kommunikation mit dem Client zurück (auf irgendeinem anonymen Port)
				
			PrintWriter writer = new PrintWriter(sock.getOutputStream());
			String tipp;
			
			for (int i = 0; i <= 5; i++) {
				tipp = getTipp();
				writer.println(tipp);
			}
			writer.close();
			//System.out.println(tipp);
			
		//} catch(IOException ex) {
			//ex.printStackTrace();
		}
	} // los() schließen
	
	private String getTipp() {
		int zufallszahl = (int) (Math.random() * tippListe.length);
		return tippListe[zufallszahl];
		
	}
	
	public static void main(String[] args) throws IOException {
		TippDesTagesServer server = new TippDesTagesServer();
		server.los();
	}
}	

Code Client Tipp des Tages:

package Tipp;

import java.io.*;
import java.net.*;


public class TippDesTagesClient {
	
	public void los() {
		try {
			Socket s = new Socket("127.0.0.1", 4242); 
			//Stellen eine Socket-Verbindung her, was da gerade auf Port 4242 läuft, und zwar auf dem gleichen Host, auf dem auch dieser Code läuft (dem >>localhost<<)
			InputStreamReader streamReader = new InputStreamReader(s.getInputStream());
			BufferedReader reader = new BufferedReader(streamReader);
			//Verketten Sie einen BufferedReader mit einem InputStreamReader, der wiederum mit dem Eingabestrom vom Socket verkettet ist
		
			
			String tipp;
			while((tipp = reader.readLine()) != null) {
				System.out.println("Tipp des Tages: " + tipp);
			}
			
			reader.close();
			
		} catch (IOException ex) {
			ex.printStackTrace();
		}
	}
	
	public static void main (String[] args) {
		TippDesTagesClient client = new TippDesTagesClient();
		client.los();
	}
}

Mögliche Ausgabe des Clients:

Die Grundlagen dieses Codes werden später noch einmal für einen Chat Client benötigt.

Vorlesung 3

In der letzten Stunde haben wir gelernt, dass Chat und Clientanwendungen über einen Socket kommunizieren. Ein Socket ist also eine Verbindung zwischen zwei realen Maschinen. Verbunden werden beide Clients über einen sogenannten TCP-Port, welcher aus einer 16-Bit-Zahl besteht.

Ist eine Verbindung hergestellt, kann ein Client vom Socket Ein- und Ausgabeströme erhalten.

Chatanwendung

Auf der Grundlage des Wissens der letzten Stunde, haben wir uns einem Chat Client zugewandt. Dieser kann zunächst nur senden; später senden und empfangen.

Um Nachrichten von einem Server zu erhalten gibt es 3 Möglichkeiten:

  1. Den Server alle 20 Sekunden abfragen
  2. Jedes Mal, wenn der Benutzer eine Nachricht schickt, diese vom Server lesen lassen.
  3. Nachrichten lesen, sobald sie vom Server gesendet werden Punkt

Bei Möglichkeit 1 besteht das Problem darin, dass der Server nicht weiß, was wir schon gelesen haben und was nicht. Er schickt also alle 20 Sekunden den kompletten Chatverlauf. Bei Punkt 2 kann es sein, dass der Nutzer nur passiv mitliest und nicht selbst sendet. So können Nachrichten verloren gehen. Möglichkeit 3 macht hier also am meisten Sinn.

Hierzu haben wir einen Chatserver und einen Chatclient gebaut. DerCode hierzu sah wie folgt aus:

Chatserver:

import java.io.*;
import java.net.*;
import java.util.*;

public class SehrEinfacherChatServer {
	
	ArrayList<PrintWriter> clientAusgabeStröme;
	
	
	public class ClientHandler implements Runnable {
		
		BufferedReader reader;
		
		Socket sock;
		
		
		public ClientHandler(Socket clientSocket) {
			try {
				sock = clientSocket;
				InputStreamReader isReader = new InputStreamReader(sock.getInputStream());
				reader = new BufferedReader(isReader);
				
			} catch(Exception ex) {ex.printStackTrace();}
		} // Konstruktor schließen
		
		public void run() {
			String nachricht;
			
			try {
				
				while ((nachricht = reader.readLine()) != null) {
					
					System.out.println("gelesen: " + nachricht);
					esAllenWeitersagen(nachricht);
					
				} // Ende der while-Schleife
			} catch(Exception ex) {ex.printStackTrace();}
		} // run schließen
	} // innere Klasse schließen
	
	
	public static void main (String[] args) {
		new SehrEinfacherChatServer().los();
	}
	
	public void los() {
		clientAusgabeStröme = new ArrayList<PrintWriter>();
		
		try {
			ServerSocket serverSock = new ServerSocket(5000);
			
			while(true) {
				Socket clientSocket = serverSock.accept();
				PrintWriter writer = new PrintWriter(clientSocket.getOutputStream());         
				clientAusgabeStröme.add(writer);
				
				Thread t = new Thread(new ClientHandler(clientSocket));
				t.start();
				
				
				System.out.println("habe eine Verbindung");
			}
			// wenn wir hier angelangt sind, haben wir eine Verbindung
			
		}catch(Exception ex) {
			ex.printStackTrace();
		}
	}
	
	public void esAllenWeitersagen(String nachricht) {
		Iterator it = clientAusgabeStröme.iterator();
		while(it.hasNext()) {
			try {
				PrintWriter writer = (PrintWriter) it.next();
				writer.println(nachricht);
				writer.flush();
			} catch(Exception ex) {
				ex.printStackTrace();
			}
		} // Ende der while-Schleife

//		Statt einen Iterator und eine while-Schleife zu benutzen,
//		könnten Sie in Java 5 auch wie folgt vorgehen (da wir aus der 
//		ArrayList clientAusgabeStröme hier eine parametrisierte 
//		ArrayList<PrintWriter> clientAusgabeStröme gemacht haben):
//		
//		for(PrintWriter writer:clientAusgabeStröme) {
//			try {
//				writer.println(nachricht);
//				writer.flush();
//			} catch(Exception ex) {
//				ex.printStackTrace();
//			}		
//		} // Ende der for-Schleife
//				
		
	} // esAllenWeitersagen schließen
	
	
}

Chatclient:

import java.io.*;
import java.net.*;
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;

public class EinfacherChatClient {
	JTextArea eingehend;
	JTextField ausgehend;
	BufferedReader reader;
	PrintWriter writer;
	Socket sock;
	
	public static void main(String[] args) {
		EinfacherChatClient client = new EinfacherChatClient();
		client.los();
	}
	public void los() {
		JFrame frame = new JFrame("Lächerlich einfacher Chat-Client");
		JPanel hauptPanel = new JPanel();
		eingehend = new JTextArea(15,20);
		eingehend.setLineWrap(true);
		eingehend.setWrapStyleWord(true);
		eingehend.setEditable(false);
		JScrollPane fScroller = new JScrollPane(eingehend);
		fScroller.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
		fScroller.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
		ausgehend = new JTextField(20);
		JButton sendenButton = new JButton("Senden");
		sendenButton.addActionListener(new SendenButtonListener());
		hauptPanel.add(fScroller);
		hauptPanel.add(ausgehend);
		hauptPanel.add(sendenButton);
		netzwerkEinrichten();
		
		Thread readerThread = new Thread(new EingehendReader()); 
		//Wir starten einen neuen Thread und benutzen dabei eine neue innere Klasse als Runnable (Job)
		//für den Thread. Der Job des Threads ist es, vom Socket-Strom des Servers zu lesen und alle eingehenden Nachrichten
		//im scrollbaren Textbereich anzuzeigen
		readerThread.start();
		
		frame.getContentPane().add(BorderLayout.CENTER, hauptPanel);
		frame.setSize(400,500);
		frame.setVisible(true);
		
	} //los schließen
	
	private void netzwerkEinrichten() {
		
		try {
			sock = new Socket("127.0.0.1", 5000);
			InputStreamReader streamReader = new InputStreamReader(sock.getInputStream());
			reader = new BufferedReader(streamReader);
			writer = new PrintWriter(sock.getOutputStream());
			System.out.println("Netzwerkverbindung wurde hergestellt");	
		}	catch (IOException ex) {
			ex.printStackTrace();
		} // Wir verwenden den Socket, um an die Eingabe- und Ausgabeströme zu gelangen. Den Ausgabestrom
		// benutzen wir, bereits, um an den Server zu senden; jetzt benutzen wir auch den Eingabestrom, damit der neue "reader"
		// -Thread Nachrichten vom Server erhalten kann
		
	} //Netzwerkeinrichtung schließen
	
	
	public class SendenButtonListener implements ActionListener {
		public void actionPerformed(ActionEvent ev) {
			try {
				writer.println(ausgehend.getText());
				writer.flush();
				
			}	catch(Exception ex) {
				ex.printStackTrace();
			}
			ausgehend.setText("");
			ausgehend.requestFocus();
		} //Nichts neues. Wenn der Benutzer auf den SendenButton klickt, sendet diese Methode den Inhalt des Textfeldes an den Server
		
	} //innere Klasse SendenButtonListener schließen
	
	public class EingehendReader implements Runnable {
		public void run() {
			String nachricht;
			try {
				while ((nachricht = reader.readLine()) != null) {
					System.out.println("gelesen: " + nachricht);
					eingehend.append(nachricht + "\n");
					
				} //Ende der While-Schleife
			} catch(Exception ex) {ex.printStackTrace();}
		} //run schließen
	}//innere Klasse EingehendReader schließen
} //äußere Klasse schließen

Zunächst muss man den Server starten, anschließend den Client. Wenn der Client gestartet ist, öffnet sich ein Fenster, in welchem man schreiben kann und mit einem Klick auf den Senden-Button die Nachricht verschicken kann.

Dabei ist auch etwas in der Konsole zu erkennen:

Dieser Chatclient kann stand jetzt nur senden. Wir brauchen also eine Aufgabe, welche kontinuierlich im Hintergrund läuft und den Server auf neue Nachrichten prüft. Eine solche Aufgabe nennt man Thread. Ein Thread darf nicht die GUI beeinträchtigen. Der Benutzer sollte immer noch alte Nachrichten lesen können und durch den Chat Verlauf scrollen können.

Multithreading in Java

Die Lösung für dieses Problem beschreibt das Multithreading in Java. Bereits im zweiten Semester konnten wir die Funktionen und Vorteile von Threads und Multithreading lernen. Während beim Single Free Thread beispielsweise ein E-Mail-Programm nicht mit dem Nutzer interagieren kann, so lange es verschickt oder empfängt, würde das bei Multithreading funktionieren. Da wir in unserem Beispiel selbst auch schreiben möchten während das Chatprogramm empfängt, benötigen wir also das Multithreading.

Einen neuen Thread kann man folgendermaßen erzeugen:

Thread t = new Thread(); t.start();

Dieser Thread ist aktuell noch nicht gefüllt sprich, er hat noch keine Aufgabe. Wenn ein Thread keine Aufgabe hat, so „verschwindet“ er wieder und stirbt den „virtuellen Tod“.

Um einen Thread nun zur Ausführung zu bringen, benötigen wir zunächst die Klasse Thread. Die wichtigsten Funktionen dieser Klasse sind, den Thread zu starten, einen Thread mit einem anderen zu verbinden, und einen Thread schlafen zu legen.

Im Folgenden ist kurz erläutert wie man einen Thread zur Ausführung bringt:

  • Ein Runnable-Objekt erzeugen:
    • Runnable threadJob = new MeinRunnable(); Ein Runnable ist ein Interface. Man kann also eine Klasse schreiben, die das Interface Runnable implementiert und in der Klasse wird festgelegt, welche Arbeit der Thread erledigen muss
  • Ein Thread-Objekt erzeugen und ihm ein Runnable geben:
    • Thread meinThread = new Thread(threadJob); So erfährt das neue Thread-Objekt, welche Methode es als unterste auf seinen Stack setzen soll- die run()-Methode des Runnable-Objekts
  • Den Thread starten:
    • meinThread.start(); Es passiert erst etwas, wenn die Methode „start()“ des Threads aufgerufen wird. Wenn der neue Thread startet, nimmt er die „run()“-Methode des Runnable-Objekts und setzt sie zuunterst auf seinen Stack

Um eine Aufgabe für den Thread zu erzeugen, wird das Interface „Runnable“ implementiert. Das Runnable erhält nur eine einzige zu implementierende Methode: und zwar „public void run ()“. Hier wird der Job definiert, welcher der Thread ausführen soll. Das ist dann die Methode, die ganz unten auf den neuen Stack kommt. Nun muss man dem neuen Thread-Konstruktor die neue Runnable-Instanz übergeben. Damit sagen wir dem Thread, welche Methode er unten auf den Stack setzen soll. Aber wie bereits gesagt, erhält man erst einen neuen Ausführungsstrang wenn „start()“ auf der Thread-Instanz aufgerufen wird.

Nach dieser Logik kann ein Thread also 3 Zustände durchlaufen. Nach dem Erzeugen ist er „neu“. Wenn er gestartet wurde ist er „lauffähig“ und wenn er zur Ausführung ausgewählt ist er „laufend“. Normalerweise wechselt ein Thread immer zwischen laufweg und laufend hin und her. Das liegt daran, dass der Thread Scheduler der Java Virtual Machine (JVM) einen Thread für eine Ausführung auswählt, ihn aber anschließend wieder pausiert, um anderen Threads auch eine Laufzeit zu geben.

Thread Scheduler

Der Thread Scheduler kann auch einen laufenden Thread aus verschiedenen Gründen in einem blockierten Zustand versetzen. Zum Beispiel, wenn ein Thread auf Daten aus einem Strom wartet oder weil er „schlafen gegangen“ ist.

Übersicht Thread Lebenszyklus:

Der Thread Scheduler ist für die gesamte Planung der Threads verantwortlich und trifft die Entscheidungen, wann welcher Thread in welchem Zustand ist. Er ist von Betriebssystem zu Betriebssystem unterschiedlich. Das heißt, dass auf 2 unterschiedlichen Betriebssystemen eine unterschiedliche Reihenfolge von Threads entstehen kann.

Scheduler sind nicht steuerbar. Es gibt keine API für den Aufruf von Methoden auf dem Scheduler. Man sollte also auf keinen Fall das korrekte Funktionieren des Programms davon abhängig machen, auf welche Art und Weise der Scheduler vorgeht. Denn so resultiert auch ein häufiger Programmierfehler: Die Multithreading-Programme werden nur auf einer Maschine getestet und dann angenommen. Es wird davon ausgegangen, dass der Scheduler immer so arbeiten wird, egal wo. Dies ist aber nicht so; wie oben schon kurz erläutert. Das bedeutet also für alle Programmierer, dass wenn plattformunabhängiger Code geschrieben werden soll, das Programm unabhängig vom Verhalten des Schedulers funktionieren muss. Die Lösung in den meisten Fällen heißt wie fast immer „schlafen“. Wenn ein laufender Thread per Code zum Schlafen gezwungen wird, so verlässt er den Zustand „laufend“, was einem anderem Thread die Chance zur Ausführung gibt. Eine Garantie gibt es hierbei sogar: Der schlafende Thread wird nicht wieder laufend, bevor seine definierte Schlafzeit abgelaufen ist.

Hier ein Beispiel:

package ThreadTestlauf;

class MeinRunnable implements Runnable {
	
	public void run() {
		los();
	}
	
	public void los() {
		try {
			Thread.sleep(2000);
		} catch(InterruptedException ex) {
			ex.printStackTrace();
		}
		tuNochMehr();
	}
	
	public void tuNochMehr() {
		System.out.println("oben auf dem Stack");
	}
}

class ThreadTestLauf {
	public static void main (String[] args) {
		Runnable threadJob = new MeinRunnable();
		Thread meinThread = new Thread(threadJob);
		meinThread.start();
		
		try {
			Thread.sleep(2000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println("zurück in main");
	}

}

Durch Ausprobieren und Auslesen der Konsole kann man nun die Schlafzeiten definieren.

Der Befehl des Schlafenlegens löst eine „interupted exception“ aus. Also müssen alle Sleep() Aufrufe mit einem „try and catch“-Block umfasst werden. Dies ist auch unten im Code zu sehen.

Der Code oben führt zu folgender Ausgabe:

Diese Ausgabe wird erst nach 2 Sekunden, also nach der Sleep-Zeit ausgegeben.

Vorlesung 4

Zu Beginn der vierten Vorlesungen haben wir uns die Erzeugung von 2 verschiedenen Threads angeschaut und die Methode aus der letzten Vorlesung angewandt, bei welcher man einen Thread zum Schlafen zwingt.

Dazu haben wir eine Aufgabe mit zwei Threads bearbeitet. Wenn wir keinen Sleep einbauen, so blockiert der Alpha Thread den Beta Thread. Der Code und das Konsolen Ergebnis sehen wie folgt aus:

Code:

public class ZweiThreads implements Runnable {
	
	public static void main (String [] args) {
		ZweiThreads aufgabe = new ZweiThreads(); //Machen sie eine Instanz von Runnable
		Thread alpha = new Thread(aufgabe);
		Thread beta = new Thread(aufgabe); //Machen Sie zwei Threads mit dem gleichen Runnable (also dem gleichen Job - in ein paar Seiten sagen wie noch mehr zum Thema "zwei Threads mit nur einem Runnable"
		alpha.setName("Alpha-Thread");
		beta.setName("Beta-Thread"); //Threads einen Namen geben
		alpha.start();
		 /*try {
		  	Thread.sleep(2000);
		  } catch (InterruptedException ex) {ex.printStackTrace();}*/
		//nur oberer Sleep für nacheinander
		
		beta.start(); //Threads starten
				
		
	}
	
	public void run() {
		for (int i = 0; i < 25; i++) {
			String threadName = Thread.currentThread().getName();
			System.out.println("Jetzt läuft der " + threadName);
			//Jeder Thread durchläuft diese Schleife und gibt bei jedem Durchgang seinen Namen aus
			
			/*try {
				Thread.sleep(3000);
			} catch (InterruptedException ex) {ex.printStackTrace();}*/
		} //Sleeps für Alpha und Beta eingebaut, beide Sleeps für abwechselnd
	}
	

}

Konsole:

Um also zu erreichen, das erst der Alpha Thread vollständig ausgeführt wird und dann erst der Beta Thread, ist es nötig, einen Sleep Befehl mit dem „try and catch“-Block einzubauen. So kommt es zu einer regelmäßigen Ausgabe:

Code:

public class ZweiThreads implements Runnable {
	
	public static void main (String [] args) {
		ZweiThreads aufgabe = new ZweiThreads(); //Machen sie eine Instanz von Runnable
		Thread alpha = new Thread(aufgabe);
		Thread beta = new Thread(aufgabe); //Machen Sie zwei Threads mit dem gleichen Runnable (also dem gleichen Job - in ein paar Seiten sagen wie noch mehr zum Thema "zwei Threads mit nur einem Runnable"
		alpha.setName("Alpha-Thread");
		beta.setName("Beta-Thread"); //Threads einen Namen geben
		alpha.start();
		try {
		  	Thread.sleep(2000);
		  } catch (InterruptedException ex) {ex.printStackTrace();}
		//nur oberer Sleep für nacheinander
		
		beta.start(); //Threads starten
				
		
	}
	
	public void run() {
		for (int i = 0; i < 25; i++) {
			String threadName = Thread.currentThread().getName();
			System.out.println("Jetzt läuft der " + threadName);
			//Jeder Thread durchläuft diese Schleife und gibt bei jedem Durchgang seinen Namen aus
			
			/*try {
				Thread.sleep(3000);
			} catch (InterruptedException ex) {ex.printStackTrace();}*/
		} //Sleeps für Alpha und Beta eingebaut, beide Sleeps für abwechselnd
	}
	

}

Konsole:

In einem weiteren Schritt haben wir die Sleeps so eingebaut, dass Alpha und Beta Thread abwechselnd laufen.

Code:

public class ZweiThreads implements Runnable {
	
	public static void main (String [] args) {
		ZweiThreads aufgabe = new ZweiThreads(); //Machen sie eine Instanz von Runnable
		Thread alpha = new Thread(aufgabe);
		Thread beta = new Thread(aufgabe); //Machen Sie zwei Threads mit dem gleichen Runnable (also dem gleichen Job - in ein paar Seiten sagen wie noch mehr zum Thema "zwei Threads mit nur einem Runnable"
		alpha.setName("Alpha-Thread");
		beta.setName("Beta-Thread"); //Threads einen Namen geben
		alpha.start();
		try {
		  	Thread.sleep(2000);
		  } catch (InterruptedException ex) {ex.printStackTrace();}
		//nur oberer Sleep für nacheinander
		
		beta.start(); //Threads starten
				
		
	}
	
	public void run() {
		for (int i = 0; i < 25; i++) {
			String threadName = Thread.currentThread().getName();
			System.out.println("Jetzt läuft der " + threadName);
			//Jeder Thread durchläuft diese Schleife und gibt bei jedem Durchgang seinen Namen aus
			
			try {
				Thread.sleep(3000);
			} catch (InterruptedException ex) {ex.printStackTrace();}
		} //Sleeps für Alpha und Beta eingebaut, beide Sleeps für abwechselnd
	}
	

}

Konsole:

Aber es gibt auch Probleme bei solchen Threads. Wenn 2 oder mehrere Thread Zugriff auf die Daten desselben Objektes haben, also wenn zwei Methoden auf zwei Stacks dasselbe Objekt aufrufen, wissen sie nicht, dass der andere Thread auch auf dieses Objekt zugreift. Ein Thread, der gerade nicht läuft, ist praktisch bewusstlos. Wenn er wieder läuft weiß er nicht, dass er angehalten wurde.

Veranschaulicht wurde uns dies anhand eines Beispiels:

Zwei Eheleute heben zeitgleich von ihrem gemeinsamen Konto Geld ab, ohne zu wissen, dass der andere auch Geld abhebt. Das Geld auf dem Konto reicht für eine Auszahlung, aber nicht für beide. Da zum Zeitpunkt der Anfrage für beide Auszahlungen das Konto gedeckt ist, werden auch beide Auszahlungen erlaubt. Das Konto wäre demnach anschließend im Minus.

Man muss also dafür sorgen, dass ein Thread, sobald er einmal läuft, nicht durch einen anderen Thread unterbrochen wird. Mit anderen Worten ist dafür zu sorgen, dass ein Thread der einen Kontostand geprüft hat, seine Abhebung zu Ende führen kann, nachdem er wieder aufgewacht ist, bevor ein anderer Thread den Kontostand prüfen kann. Mit dem Schlüsselwort „synchronised“ können Methoden so modifiziert werden, dass immer nur ein Thread auf einmal auf sie zugreifen kann.

Thread Dead Lock

Zwei Threads können sich aber auch gegenseitig blockieren. Das ist die Negativseite der Synchronisierung. Das Problem nennt sich „Thread Dead Lock“ und dabei handelt es sich um eine gegenseitige Blockierung von Threads, zu der es kommt, wenn man zwei Threads hat, die beide jeweils einen Schlüssel haben, den der andere unbedingt haben möchte. Beides Threads warten also auf den anderen und machen nichts. Ein Transaktionsmanagement-System kann Deadlocks unter bestimmten Umständen auflösen. Es kann eventuell erkennen, dass bestimmte Transaktionen zu lange benötigen oder nicht beendet werden. Hier ist der Vorteil gegenüber Java, dass der Anwendungsserver ein Transaktionsrollback durchführen kann, d.h. dass das System wieder genau in den Status zum Zeitpunkt vor der Transaktion versetzt werden kann. Im Vergleich dazu merkt Java noch nicht mal genau, dass ein Deadlock aufgetreten ist, was bedeutet, dass vorsichtig und sorgfältig programmiert werden muss.

Vorlesung 5

Erzeuger- und Verbraucherproblem

Zu Beginn der Vorlesung sind wir auf das Erzeuger- und Verbraucher-Problem eingegangen.

Ein Erzeuger schreibt einen Wert, welcher dann von einem Verbraucher abgeholt wird.

Nach dem Erzeugen des Wertes soll der Erzeuger schlafen, damit der Verbraucher den Wert „abholen“ kann. Danach schläft der Verbraucher und der Erzeuger schickt den nächsten Wert. Das ist aber vom Betriebssystem und vom Scheduler abhängig. Der Verbraucher kann aber schon wieder vor dem „putten“ des neuen Wertes vom Erzeuger aufwachen und sich den vorherigen Wert noch einmal holen. Auf der anderen Seite kann es sein, dass der Erzeuger wach wird, bevor der Verbraucher „gegettet“ hat und so schon den nächsten Wert „puttet“. So geht der vorherige Wert verloren und der Verbraucher „gettet“ nur den letzten „geputteten“ Wert.

Für dieses Problem gibt es verschiedene Möglichkeiten. Benutzt werden aber hierfür Methoden, die die Threads veranlassen, zu warten bis:

  • bei public final void join()
    • …der Thread, für den join ausgeführt wird, beendet ist. Wenn die Threads immer abwechselnd laufen sollen, ist dies nicht geeignet.
  • Bei public final void wait()
    • …ein anderer Thread die notify- oder notifyAll-Methode für das aktuelle Objekt ausführt.

Es gibt aber noch weitere mögliche Methoden. Diese sind allerdings die gängigsten.

Es kann im Code auf eine sog. Flag (oder Semaphor) gesetzt werden. Diese sitzt „im Wert“ und ist boolean. Wenn der Wert leer ist, kann auch nichts abgeholt werden. Wenn der Wert gefüllt ist, geht auch die Flag auf true und schickt dem Verbraucher eine Benachrichtigung (notify) zum Abholen.

Chat Client mit Entertaste und Name – Erweiterung

Danach haben wir uns wieder mit dem Chat Client aus der vorherigen Vorlesung befasst. Die Aufgabe bestand darin, den Chat Client so zu erweitern, dass auch mit der Enter-Taste eine Nachricht abgesendet werden kann und nicht nur mit der Benutzung der Maus.

Wenn das funktioniert hat, sollten wir uns damit beschäftigen, die Funktion einzubauen, den eigenen Namen vor die ausgehende Nachricht schreiben zu lassen. Ich brauchte ein wenig Hilfe aus dem Internet und von Kommilitonen. Schließlich hat es doch funktioniert.

Am Ende der Aufgabe sah dann der Chat Client wie folgt aus und gab folgende Ausgabe (im Code sind Erweiterungen mit Kommentaren beschrieben):

Dazu war folgender Code nötig:

Server:

package VerbessertChat;

import java.io.*;
import java.net.*;
import java.util.*;

public class VerbesserterChatServer {
	ArrayList clientAusgabeStröme;
	public class ClientHandler implements Runnable {
		BufferedReader reader;
		Socket sock;
		public ClientHandler(Socket clientSocket) {
			try {
				sock = clientSocket;
				InputStreamReader isReader = new InputStreamReader(sock.getInputStream());
				reader = new BufferedReader(isReader);
			} catch(Exception ex) {ex.printStackTrace();}
			}
		public void run() {
			String nachricht;
			try {
				while((nachricht = reader.readLine()) != null) {
					System.out.println("gelesen" + nachricht);
					esAllenWeitersagen(nachricht);
				}
			} catch(Exception ex) {ex.printStackTrace();}
		}
	}
	
	public static void main (String[] args) {
		new VerbesserterChatServer().los();
	}
	
	public void los() {
		clientAusgabeStröme = new ArrayList();
		
		try {
			ServerSocket serverSock = new ServerSocket(5000);
			
			while(true) {
				Socket clientSocket = serverSock.accept();
				PrintWriter writer = new PrintWriter(clientSocket.getOutputStream());
				clientAusgabeStröme.add(writer);
				Thread t = new Thread(new ClientHandler(clientSocket));
				t.start();
				System.out.println("habe eine Verbindung");
			}
		} catch(Exception ex) {
			ex.printStackTrace();
		}
	}
	
	public void esAllenWeitersagen(String nachricht) {
		Iterator it = clientAusgabeStröme.iterator();
		
		while(it.hasNext()) {
			try {
				PrintWriter writer = (PrintWriter) it.next();
				writer.println(nachricht);
				writer.flush();
			} catch(Exception ex) {
				ex.printStackTrace();
			}
		}
	}
}

Client (hier waren die meisten Erweiterungen nötig):

package VerbessertChat;

import java.io.*;
import java.net.*;
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;

public class VerbesserterChatClient {
	
	JTextArea eingehend;
	JTextField ausgehend;
	BufferedReader reader;
	PrintWriter writer;
	Socket sock;
	
	public static void main(String[] args) {
		VerbesserterChatClient client = new VerbesserterChatClient();
		client.los();
	}
	
	public void los() {
		JFrame frame = new JFrame("Verbesserter Chat-Client");
		JPanel hauptPanel = new JPanel();
		eingehend = new JTextArea(15,20);
		eingehend.setLineWrap(true);
		eingehend.setWrapStyleWord(true);
		eingehend.setEditable(false);
		JScrollPane fScroller = new JScrollPane(eingehend);
		fScroller.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
		fScroller.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
		ausgehend = new JTextField(20);
		ausgehend.addKeyListener(new SendenKeyListener()); //hier den KeyListener einbauen
		JButton sendenButton = new JButton("Senden");
		sendenButton.addActionListener(new SendenButtonListener());
		hauptPanel.add(fScroller);
		hauptPanel.add(ausgehend);
		hauptPanel.add(sendenButton);
		netzwerkEinrichten();
		
		Thread readerThread = new Thread(new EingehendReader()); 
		//Wir starten einen neuen Thread und benutzen dabei eine neue innere Klasse als Runnable (Job)
		//für den Thread. Der Job des Threads ist es, vom Socket-Strom des Servers zu lesen und alle eingehenden Nachrichten
		//im scrollbaren Textbereich anzuzeigen
		readerThread.start();
		
		frame.getContentPane().add(BorderLayout.CENTER, hauptPanel);
		frame.setSize(400,500);
		frame.setVisible(true);
		
	} //los schließen
	
	private void netzwerkEinrichten() {
		
		try {
			sock = new Socket("127.0.0.1", 5000);
			InputStreamReader streamReader = new InputStreamReader(sock.getInputStream());
			reader = new BufferedReader(streamReader);
			writer = new PrintWriter(sock.getOutputStream());
			System.out.println("Netzwerkverbindung wurde hergestellt");	
		}	catch (IOException ex) {
			ex.printStackTrace();
		} // Wir verwenden den Socket, um an die Eingabe- und Ausgabeströme zu gelangen. Den Ausgabestrom
		// benutzen wir, bereits, um an den Server zu senden; jetzt benutzen wir auch den Eingabestrom, damit der neue "reader"
		// -Thread Nachrichten vom Server erhalten kann
		
	} //Netzwerkeinrichtung schließen
	
	
	public class SendenButtonListener implements ActionListener {
		public void actionPerformed(ActionEvent ev) {
			try {
				writer.println(ausgehend.getText());
				writer.flush();
				
			}	catch(Exception ex) {
				ex.printStackTrace();
			}
			ausgehend.setText("");
			ausgehend.requestFocus();
		} //Nichts neues. Wenn der Benutzer auf den SendenButton klickt, sendet diese Methode den Inhalt des Textfeldes an den Server
		
	} //innere Klasse SendenButtonListener schließen
	
	public class EingehendReader implements Runnable {
		public void run() {
			String nachricht;
			try {
				while ((nachricht = reader.readLine()) != null) {
					System.out.println("gelesen: " + nachricht);
					eingehend.append(nachricht + "\n");
					
				} //Ende der While-Schleife
			} catch(Exception ex) {ex.printStackTrace();}
		} //run schließen
	}//innere Klasse EingehendReader schließen
	
	public class SendenKeyListener implements KeyListener { //hier kommt das mit der Enter-Taste
		
		public void keyTyped(KeyEvent e) {}
		
		public void keyPressed(KeyEvent e) {
			if (e.getKeyCode() == KeyEvent.VK_ENTER) { //hier wird geschaut, ob Enter gedrückt wurde
				try {
					writer.println("Erik: " + ausgehend.getText()); //hier wird mein Name vor die Nachricht geschrieben
					writer.flush();
				} catch(Exception ex) {ex.printStackTrace();}
				
				ausgehend.setText("");
			}
		}
		
		@Override
		public void keyReleased(KeyEvent e) { //hier wird der KeyListener zurückgesetzt
		}
	}
} //äußere Klasse schließen

Zum ersten Mal seitdem ich im Studium mit Programmieren in Kontakt gekommen war, hatte ich das Gefühl, etwas sinnvolles programmiert zu haben.

PHP

Im nächsten Teil beschäftigten wir uns näher mit PHP. Zwar hatten wir PHP in der ersten Vorlesung schon als serverseitige Technologie kennengelernt; in der Vorlesung sind wir aber noch einmal näher auf PHP eingegangen.

Dazu haben wir uns ein HTML Formular zum Ausfüllen angeschaut. Mit einem Knopf konnte man das Formular bestätigen und ein Mail-Programm öffnen. Nun stellte sich die Frage was das Problem sein könnte, wenn nur mit HTML gearbeitet wird.

Es stellte sich heraus, dass das Mail-Programm zwar geöffnet wird, der Inhalt der Mail aber kryptisch ist.

Ziel ist es, Die Mail vom Server schicken zu lassen, und nicht von dem Mail Programm.

Hierzu muss im HTML-Code der Seite ein Form-Tag geöffnet werden welcher wie folgt aussieht:

<form action = "meldung.php" method = "Post">

Klickt der Benutzer nun auf das Feld „senden“, so wird auf dem Server das Skript „meldung.php“ ausgeführt und die Formulardaten verarbeitet.

Für das Beispiel mit der Hundeentführung wurde ein lokaler Server auf dem Computer installiert. Aufgrund der Beschränkungen des Firmenlaptops konnte ich diesen Teil leider nicht mitmachen.

PHP wird im HTML-Code eingebettet. Der PHP-Code wird zum Server geschickt und auf dem Server verarbeitet. Das vom PHP Erzeugte wird dann an die Stelle im HTML Code gesetzt, an der zuvor der PHP-Code stand. Dieser „neu zusammengesetzte“ Code wird nun an den Webserver geschickt. PHP läuft also nur auf dem Server.

Hier ein paar wichtige PHP-Programmiervorgaben:

  • PHP-Code muss immer von „<?php“ und „?>“ eingeschlossen werden
  • Alle PHP-Anweisungen müssen mit einem Semikolon enden
  • Falls eine Webseite PHP-Code nutzt, muss die Dateinnamenserweiterung „.php“ genutzt werden
  • Namen von PHP-Variablen beginnen immer mit „$“-Zeichen

PHP Variablen

Da Variablen Container zur Speicherung von Daten sind ist ein eindeutiger Name wichtig. Im Folgenden ist zu entnehmen, wie eine Variable aussehen muss:

  • Das erste Zeichen muss ein „$“-Zeichen sein
  • Der Name muss mind. 1 Zeichen lang sein
  • Als erstes Zeichen nach dem „$“-Zeichen ist entweder ein Unterstrich oder ein Buchstabe erlaubt, die darauffolgenden Zeichen können Buchstaben, Unterstriche oder und Ziffern sein
  • Leer und Sonderzeichen sind für den Namen nicht erlaubt
  • Am besten sollte man nur Kleinbuchstaben nutzen
  • Für einen aus mehreren Wörtern bestehenden Variablennamen kann man die Wörter immer mit Unterstrichen trennen

Formatieren von Klartext

Für das Formatieren von Texten kann man Zeilenumbruchszeichen verwenden, und zwar mit der Escape-Sequenz „\n“. Für jede Stelle im Text, an dem ein \n steht, wird in die nächste Zeile gesprungen und der Text geht dort weiter. In PHP beginnen Escape-Zeichen immer mit einem Backslash (\). Eine Escape-Sequenz ist eine Zeichenfolge, die genutzt wird, um ein Zeichen in einen String einzubetten. PHP kennt aber nur einen sehr beschränkten Satz von definierten Escape-Sequenzen.

Sonstige PHP Funktionen

PHP Variablen und Arrays:
  • Variablennamen müssen mit einem „$“-Zeichen eingeleitet werden
  • Haben keinen fest definierten Typ, sondern müssen dynamisch typisiert werden
  • „$arrayname = array(wert1, wert2,…)“ definiert ein neues Array
  • „$arrayname[0]“ greift auf den ersten Arraywert zu
Operatoren

Des Weitern unterstützt PHP auch die häufig genutzten Vergleichsoperatoren, welche auch in Java zu finden sind ( <,>, !=, =, &). Um zu überprüfen, ob ein Nutzer ein bestimmtes Feld ausgelassen hat oder nicht, werden die Befehle „isset()“ oder „empty()“ verwendet. Bei Schleifen verhält sich PHP ähnlich wie Java, denn es werden die „while-“ und die „for-Schleifen“ unterstützt:

while(Bedingung) {Code} oder for($counter=0;$counter<obergrenze;$counter++){Code}
Verzweigungen

Ebenfalls unterstützt PHP „switch case-“ und „if-Verzweigungen“:

if(Bedinugung) {code} else if {} else {}
Klassen

Eine Klasse kann in PHP ebenfalls schnell instanziiert werden:

$objekt = new Klassenname();
Stringfunktionen

PHP bietet auch viele Stringfunktionen wie zum Beispiel „strtok(str,token)“ oder „trim(str)“ und macht das Auslesen von Dateien sehr einfach:

$zeilen = file(„dateiname“);

Strings werden durch den Punktoperator aneinandergehängt.

z.b.:

$msg = $name (variable) . ' ist ' . $wann . //………… usw.

Die $_POST Variable

$_POST ist eine spezielle, sog. Superglobale variable, die an jedem Punkt des PHP Codes verfügbar ist. Sie ist vordefiniert. Sie enthält alle in das Formular eingetragene Werte.

Es heißt $_POST, weil; wie oben schon beschrieben; die Method im Form-Tag „post“ ist. Stünde im Form-Tag als Method „get“, so hieße es auch $_GET. Die „GET“-Methode ist aber eine andere.

Das $_POST zieht aus dem HTML Dokument also die Formulardaten. Dabei sucht es z.b. nach dem Teil „$_POST Name =“wielange““ wenn es „$_POST[´wielange´];“ heißt.

Die POST Variable schreibt direkt auf den Server, während die GET Variable auf die URL Einfluss hat.

Mir war nicht direkt klar, was nun der Unterschied zwischen POST und GET war, deshalb habe ich mich im Internet noch einmal dazu schlau gemacht. Im Folgenden habe ich eine Tabelle mit den Unterschieden aufbereitet:

GETPOST
– GET Parameter sind in er URL eigebunden
– GET wird hauptsächlich für das Abrufen von Dokumenten verwendet
– da GET Parameter in der URL sind, haben sie eine maximale Länge
– verändern den Server nicht
– POST Parameter sind im Body eingebunden
– POST wird für das Updaten von Dokeumenten verwendet
– POST Parameter haben keine maximale Länge
– verändern den Server
Quelle: https://www.youtube.com/watch?v=UObINRj2EGY

Anschließend war mir der Unterschied klar.

MySQL und PHP

Ein weiterer Vorteil von PHP ist, dass es viele Funktionen bereitstellt, die die Benutzung einer MySQL-Datenbank vereinfachen. Ab Version 5 bietet PHP sogar eine Klasse „mysqli“ an, mit der man mit MySQL-Datenbanken interagieren kann. Dafür muss man Verbindungsobjekt erstellen und danach eine Datenbank auswählen.

Ziel der restlichen Vorlesung war es nun, den PHP Code auf eine SQL Datenbank zu schreiben.

Dazu benötigen wir:

  • Den Ort des Datenbankservers (IP Adresse)
  • DB-Benutzername
  • DB-Passwort

Man kann auch wählen, wie man auf den Datenbankserver zugreifen möchte. In der Vorlesung haben wir die „phpMyAdmin“-Variante gewählt.

Zunächst muss man eine Datenbank auf dem Datenbankserver erzeugen und darin eine Tabelle anlegen, in welche die Daten geschrieben werden. Um dann dem Datenbankserver zu sagen, in welche Tabelle er schreiben soll, wird der Befehl „use“ verwendet.

Wenn alles eingerichtet ist, sorgt ein Skript dafür, dass eine INSERT-Anweisung in PHP Teil eingefügt wird.

Um dann anschließend sich mit der Datenbank zu verbinden, muss eine Referenz vom Webserver zum Datenbankserver herstellen. Diese Referenz wird auch „handle“ genannt. Für die Referenz brauchen wir die Informationen von oben:

  • Den Ort des Datenbankservers (IP Adresse)
  • DB-Benutzername
  • DB-Passwort

Plus natürlich den Namen der gerade erzeugten Datenbank.

Ich fand sehr spannend, was man alles mit „so wenigen“ Mitteln machen kann. Dennoch ist das noch sehr theoretisch, weshalb ich den Gefallen an der Sache noch finden muss.

Fazit

Nachdem ich schon nach dem Wintersemester einen tiefen Einblick in die Möglichkeiten der Webprogrammierung bekommen hatte. Sind wir in diesem Semester noch tiefer in die Materie eingetaucht.

Obwohl mir Programmieren generell nicht unbedingt gefällt, da es meist zu theoretisch ist, konnte ich in diesem Semester meinen Spaß daran finden. Natürlich sind das alles keine großen Codes und Programme. Aber sie waren wesentlich „greifbarer“ als die Vorherigen.

Vor allem das Programmieren des Chat Servers und Clients hat mir gefallen, da dieser einen praktischen Zweck hat.

Schwer fiel mir die stumpfe Theorie. Oft begriff ich erst die Theorie, wenn es schon um die Anwendung dieser in den Aufgaben ging.

Die Auswahl des Schwerpunktes ist mir in diesem Semester auch unerwartet schwer gefallen. Zwar konnte ich nach Rücksprache mit anderen Kommilitonen herausfinden, was diese als Schwerpunkt gewählt haben, doch mir half das nicht wirklich weiter. Schließlich entschloss ich mich dafür, mir eine PHP Funktion näher anzuschauen.