/*
** This file is part of Filius, a network construction and simulation software.
**
** Originally created at the University of Siegen, Institute "Didactics of
** Informatics and E-Learning" by a students' project group:
** members (2006-2007):
** André Asschoff, Johannes Bade, Carsten Dittich, Thomas Gerding,
** Nadja Haßler, Ernst Johannes Klebert, Michell Weyer
** supervisors:
** Stefan Freischlad (maintainer until 2009), Peer Stechert
** Project is maintained since 2010 by Christian Eibl <filius@c.fameibl.de>
** and Stefan Freischlad
** Filius is free software: you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation, either version 2 of the License, or
** (at your option) version 3.
**
** Filius is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied
** warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
** PURPOSE. See the GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with Filius. If not, see <http://www.gnu.org/licenses/>.
*/
package filius.software.transportschicht;
import java.util.LinkedList;
import java.util.ListIterator;
import java.util.Random;
import filius.Main;
import filius.exception.SocketException;
import filius.exception.TimeOutException;
import filius.exception.VerbindungsException;
import filius.hardware.Verbindung;
import filius.software.system.InternetKnotenBetriebssystem;
import filius.software.vermittlungsschicht.IpPaket;
/**
* <p>
* Dieser Socket implementiert die Schnittstelle fuer den
* Ende-zu-Ende-Datenaustausch mit dem Transport Control Protocol. Dazu werden
* insbesondere drei Funktionen unterschieden:
* <ol>
* <li> Verbindungsaufbau </li>
* <li> Senden und </li>
* <li> Empfangen von Nachrichten </li>
* <li> Verbindungsabbau </li>
* </ol>
* Es wurden Vereinfachungen hinsichtlich Wartezeiten, Wiederholung von
* Datenuebertragungen und aehnliches vorgenommen.
* </p>
* Die vier genannten Funktionen werden jeweils durch eine Methode
* implementiert. Diese Methoden blockieren die Ausfuehrung eines Threads. Sie
* stellen die Schnittstelle zur Anwendungsschicht dar. <br />
* <p>
* Die Verwendung des TCP-Sockets funktioniert foldendermassen:
* <ol>
* <li> Der Aufruf des Konstruktors initialisiert die notwendigen Attribute fuer
* eine Verbindung. </li>
* <li> Der Verbindungsaufbau erfolgt durch Aufruf der Methode <b>verbinden()</b>.
* Diese Methode blockiert so lange, bis der Verbindungsaufbau erfolgreich
* abgeschlossen werden konnte oder eine Ausnahme ausgeloest wurde. Der
* Verbindungsaufbau erfolgt mit einem Three-Way-Handshake. </li>
* <li> Mit Aufruf der Methode <b>istVerbunden()</b> kann der Verbindungsstatus
* abgerufen werden. Diese Methode prueft, ob der Zustand des Sockets
* ESTABLISHED ist. </li>
* <li> Wenn eine Verbindung erfolgreich aufgebaut werden konnte, ist es
* moeglich Nachrichten mit der Methode <b>sende(String)</b> an den entfernten
* Socket zu verschicken. Diese Methode blockiert, bis die Nachricht erfolgreich
* uebertragen werden konnte oder eine Ausnahme ausgeloest wurde. Die
* Uebertragung erfolgt mit einem Stop-And-Wait-Algorithmus. </li>
* <li> Ebenso kann nach efolgtem Verbindungsaufbau mit Aufruf der Methode
* <b>empfangen()</b> auf eine eingehende Nachricht gewartet werden. Es wird
* ein String zurueck gegeben, wenn die Nachricht vollstaendig empfangen wurde.
* </li>
* <li> Der Verbindungsabbau wird durch Aufruf der Methode <b>schliessen()</b>
* initiiert. Allerdings erfolgt der Verbindungsabbau synchron. D. h., der
* Verbindungsabbau muss von beiden Seiten initiiert werden. Diese Methode
* blockiert so lange, bis der Verbindungsabbau beendet wurde. Allerdings gibt
* es einen Timeout, nach dem der Socket auch ohne Antwort der Gegenseite
* geschlossen wird. </li>
* <li> Beim Wechsel vom Aktions- in den Entwurfsmodus muss die Methode
* <b>beenden()</b> aufgerufen werden, damit der Socket geschlossen wird und
* alle Threads, die noch in einer Methode blockiert werden, wieder freigegeben
* werden. Der Socket ist anschliessend geschlossen. Es wird jedoch kein
* ordentlicher Verbindungsabbau durchgefuehrt. </li>
* </ol>
* </p>
*
* Der TCP-Socket befindet sich immer in einem der folgenden Zustaende:
* <ul>
* <li> 1 - CLOSED </li>
* <li> 2 - LISTEN </li>
* <li> 3 - SYN_RCVD </li>
* <li> 4 - SYN_SENT </li>
* <li> 5 - ESTABLISHED </li>
* <li> 6 - CLOSE_WAIT </li>
* <li> 7 - LAST_ACK </li>
* <li> 8 - FIN_WAIT_1 </li>
* <li> 9 - FIN_WAIT_2 </li>
* <li> 10 - CLOSING </li>
* <li> 11 - TIME_WAIT </li>
* </ul>
*
* Uebergaenge zwischen den Zustaenden werden durch folgende Ereignisse
* ausgeloest:
* <ol>
* <li> aktiv oeffnen </li>
* <li> passiv oeffnen </li>
* <li> schliessen </li>
* <li> senden </li>
* <li> timeout </li>
* <li> FIN </li>
* <li> ACK+FIN </li>
* <li> ACK </li>
* <li> SYN+ACK </li>
* </ol>
*
* <p>
* Oeffnen eines Server-Sockets: Das Kommando 'passiv oeffnen' ist das Ereignis,
* das den Uebergang vom Zustand 1 (CLOSED) zu 2 (LISTEN) ausloest. <br />
* Mit dem Empfangen eines SYN-Segments wird der Uebergang zum Zustand 3
* ausgeloest und zugleich ein SYN+ACK Segment verschickt. <br />
* Nach dem Empfang eines ACK-Segments wechselt der Server-Socket in den Zustand
* 5 (ESTABLISHED).
* </p>
*
* <p>
* Oeffnen eines Client-Sockets: Das Kommando 'aktiv oeffnen' loest den
* Uebergang zum Zustand 4 aus. Zugleich wird ein SYN-Segment gesendet. <br />
* Mit dem Empfang eines SYN+ACK-Segments geht der Socket in den Zustand 5
* (ESTABLISHED) ueber und sendet zugleich noch ein ACK-Segment.
* </p>
*
* <p>
* Initiieren des Verbindungsabbaus: Mit dem Kommando 'schliessen' wird der
* Verbindungsabbau gestartet. Zugleich wird ein FIN-Segment gesendet und der
* Socket wechselt zum Zustand 8 (FIN_WAIT_1). <br />
* Mit dem Empfang eines ACK+FIN-Segments erfolgt der Uebergang zum Zustand 11
* und zugleich wird ein ACK-Segment versendet. <br />
* Nach einem Timeout, geht der Socket in den Zustand 1 (CLOSED) ueber.
* </p>
*
* <p>
* Reaktion auf Verbindungsabbau: Erhaelt der Socket ein FIN-Segment, wechselt
* er aus Zustand 5 in Zustand 6 (CLOSE_WAIT) und sendet zugleich ein
* ACK-Segment. <br />
* Durch das Kommando 'schliessen' geht der Socket in den Zustand 7 ueber und
* sendet zugleich ein FIN-Segment <br />
* Mit dem Empfang eines ACK-Segments geht der Socket in Zustand 1 (CLOSED)
* ueber.
* </p>
*/
public class TCPSocket extends Socket implements Runnable {
/** Die Zustaende, die ein Socket einnehmen kann */
protected static final int CLOSED = 1, LISTEN = 2, SYN_RCVD = 3,
SYN_SENT = 4, ESTABLISHED = 5, CLOSE_WAIT = 6, LAST_ACK = 7,
FIN_WAIT_1 = 8, FIN_WAIT_2 = 9, CLOSING = 10, TIME_WAIT = 11;
/**
* Der aktuelle Zustand des Sockets. Zustandsuebergaenge werden in den
* Methoden verbinden(), schliessen() und hinzufuegen() ausgeloest.
*/
private int zustand = CLOSED;
/**
* Anzahl der maximalen Sendeversuche, in Fehlersituationen, d. h., dass ein
* Segment nicht bestaetigt wurde.
*/
protected static final int MAX_SENDEVERSUCHE = 1;
/** Maximum Segment Size (MSS) */
protected final static int MSS = 1460;
/** Puffer fuer eingegangene Segmente. */
private LinkedList<TcpSegment> puffer = new LinkedList<TcpSegment>();
/**
* dieses Attribut ist immer die Sequenznummer des als naechstes zu
* sendenden Segments. Die Sequenznummer wird waehrend des
* Veringudngsaufbaus erhoeht, wenn das SYN-Flag gesetzt ist und wenn die
* Verbindung hergestellt ist, wenn in dem Segment Nutzdaten verschickt
* werden.
*/
private long sequenzNummer = 0;
/**
* dieses Attribut ist immer die zuletzt versendete ACK-Sequenznummer <br />
* <b> Achtung: </b> Das Segment mit der Sequenznummer x wird mit der
* Acknowledge-Sequenznummer x+1 bestaetigt!
*/
private long ackNummer = 0;
/**
* Konstruktor ruft den Konstruktor der Oberklasse auf. Ausserdem wird das
* Attribut protokoll mit dem TCP initialisiert. <br />
* Der Konstruktor ist <b> nicht blockierend</b>.
*
* @param betriebssystem
* @param zielAdresse
* @param zielPort
* @throws VerbindungsException
*/
public TCPSocket(InternetKnotenBetriebssystem betriebssystem,
String zielAdresse, int zielPort) throws VerbindungsException {
super(betriebssystem, zielAdresse, zielPort, IpPaket.TCP);
Main.debug.println("INVOKED-2 ("+this.hashCode()+") "+getClass()+" (TCPSocket), constr: TCPSocket("+betriebssystem+","+zielAdresse+","+zielPort+")");
}
/**
* Konstruktor ruft den Konstruktor der Oberklasse auf. Ausserdem wird das
* Attribut protokoll mit dem TCP initialisiert. <br />
* Der Konstruktor ist <b> nicht blockierend</b>.
*
* @param betriebssystem
* @param zielAdresse
* @param zielPort
* @throws VerbindungsException
*/
public TCPSocket(InternetKnotenBetriebssystem betriebssystem,
int lokalerPort) throws VerbindungsException {
super(betriebssystem, lokalerPort, IpPaket.TCP);
Main.debug.println("INVOKED-2 ("+this.hashCode()+") "+getClass()+" (TCPSocket), constr: TCPSocket("+betriebssystem+","+lokalerPort+")");
}
/**
* Mit dieser Methode werden die Standardfelder des Kopfteils eines Segments
* gefuellt und dann wird es versendet. <br />
* Das heisst, dass
* <ul>
* <li> lokaler Port und </li>
* <li> entfernter Port
* </ul>
* gesetzt werden.
*/
private void sendeSegment(TcpSegment segment) {
Main.debug.println("INVOKED ("+this.hashCode()+") "+getClass()+" (TCPSocket), sendeSegment("+segment+")");
segment.setQuellPort(lokalerPort);
segment.setZielPort(zielPort);
protokoll.senden(zielIp, segment);
}
/**
* Methode zur Initialisierung des Verbindungsaufbaus und die Sequenznummer
* wird mit einer Zufallszahl initialisiert. Ausserdem wird der Inhalt des
* Puffers geloescht. Abhaengig davon, ob der Socket-Modus AKTIV oder PASSIV
* ist erfolgen verschiedene Zustandsuebergaenge. <br>
* Diese Methode <b>blockiert</b>, bis der Verbindungsaufbau erfolgreich
* abgeschlossen ist, oder eine VerbindungsException ausgeloest wurde.
* <br />
* Die Methode kann nicht zweimal gleichzeitig aufgerufen werden. Der zweite
* Aufruf wartet so lange mit der Ausfuehrung, bis der erste Aufruf komplett
* abgeschlossen und der Monitor dieser Klasse wieder frei gegeben wird.
* <ol>
* <li> passiver Modus: (Oeffnen eines Server-Sockets) Der Aufruf dieser
* Methode im PASSIV-Modus loest automatisch den Uebergang vom Zustand
* CLOSED zu LISTEN aus. <br />
* Mit dem Empfangen eines SYN-Segments wird der Uebergang zum Zustand
* SYN_RCVD ausgeloest, die Acknowledge-Sequenznummer initialisiert und ein
* SYN+ACK Segment verschickt. <br />
* Nach dem Empfang eines ACK-Segments wechselt der Server-Socket in den
* Zustand ESTABLISHED. </li>
* <li> aktiver Modus: (Oeffnen eines Client-Sockets) Der Aufruf dieser
* Methode loest den Versand eines SYN-Segments und damit den Uebergang zum
* Zustand SYN_SENT aus. <br />
* Mit dem Empfang eines SYN+ACK-Segments geht der Socket in den Zustand
* ESTABLISHED ueber, initialisiert die Acknowledge Sequenznummer und sendet
* zugleich noch ein ACK-Segment. </li>
* </ol>
*
* @throws VerbindungsException
* wenn ein Fehler auftritt bzw. ein unerwartetes Segment
* waehrend des Verbindungsaufbaus empfangen wird.
* @throws TimeOutException
* wenn eine Antwort nicht innerhalb der maximalen
* Round-Trip-Time empfangen wird.
*/
public synchronized void verbinden() throws VerbindungsException,
TimeOutException {
Main.debug.println("INVOKED ("+this.hashCode()+") "+getClass()+" (TCPSocket), verbinden()");
TcpSegment tmp, segment;
long sendezeit = Long.MAX_VALUE;
// Wenn der Socket schon einmal geoeffnet war, koennen im Puffer
// noch Segmente von der alten Verbindung vorhanden sein.
puffer.clear();
sequenzNummer = Math.abs((new Random()).nextLong())
% ((long) (Math.pow(2, 32)) - 1);
// -----------------------------------------------
// passiver Verbindungsaufbau ohne Timeout
if (modus == PASSIV) {
zustand = LISTEN;
Main.debug.println("INFO ("+this.hashCode()+"): verbinden() [passiver Modus], Socket: " + this.toString());
while (zustand == LISTEN) {
synchronized (puffer) {
if (puffer.size() < 1) {
try {
puffer.wait();
}
catch (InterruptedException e) {
}
}
else break;
}
}
for (int i = 0; i < MAX_SENDEVERSUCHE + 1 && zustand != ESTABLISHED
&& zustand != CLOSED; i++) {
synchronized (puffer) {
if (puffer.size() < 1) {
try {
puffer.wait(Verbindung.holeRTT());
}
catch (InterruptedException e) {
}
}
}
if (puffer.size() >= 1) {
sendezeit = System.currentTimeMillis();
segment = (TcpSegment) puffer.removeFirst();
if (zustand == LISTEN && segment.isSyn()) {
// Initialisierung der zunaechst zu sendenden
// ACK-Nummer anhand der empfangenen
// Sequenznummer
ackNummer = naechsteSequenznummer(segment
.getSeqNummer());
zustand = SYN_RCVD;
tmp = new TcpSegment();
tmp.setSyn(true);
tmp.setSeqNummer(sequenzNummer);
sendeAck(segment, tmp);
}
else if (zustand == SYN_RCVD && segment.isAck()) {
sequenzNummer = segment.getAckNummer();
zustand = ESTABLISHED;
}
else {
zustand = CLOSED;
throw new VerbindungsException(messages
.getString("sw_tcpsocket_msg1"));
}
}
else if (System.currentTimeMillis() - sendezeit > Verbindung
.holeRTT()) {
zustand = CLOSED;
throw new TimeOutException(messages
.getString("sw_tcpsocket_msg2"));
}
}
if (zustand == ESTABLISHED) {
try {
eintragenPort();
}
catch (SocketException e) {
e.printStackTrace(Main.debug);
}
}
}
// -----------------------------------------------
// aktiver Verbindungsaufbau mit Timeout
else {
try {
eintragenPort();
Main.debug.println("INFO ("+this.hashCode()+"): verbinden() [aktiver Modus], Socket: " + this.toString());
for (int i = 0; zustand != ESTABLISHED && i < MAX_SENDEVERSUCHE; i++) {
tmp = new TcpSegment();
tmp.setSyn(true);
tmp.setSeqNummer(sequenzNummer);
sendeSegment(tmp);
zustand = SYN_SENT;
synchronized (puffer) {
if (puffer.size() < 1) {
try {
puffer.wait(Verbindung.holeRTT());
}
catch (InterruptedException e) {
}
}
}
if (puffer.size() >= 1) {
segment = (TcpSegment) puffer.removeFirst();
if (zustand == SYN_SENT && segment.isAck()
&& segment.isSyn()) {
// Initialisierung der zunaechst zu sendenden
// ACK-Nummer anhand der empfangenen
// Sequenznummer
ackNummer = naechsteSequenznummer(segment
.getSeqNummer());
sequenzNummer = segment.getAckNummer();
zustand = ESTABLISHED;
sendeAck(segment, null);
}
else {
zustand = CLOSED;
throw new VerbindungsException(messages
.getString("sw_tcpsocket_msg3"));
}
}
else if (zustand != CLOSED) {
zustand = CLOSED;
throw new TimeOutException(messages
.getString("sw_tcpsocket_msg4"));
}
}
if (zustand != ESTABLISHED)
austragenPort();
}
catch (SocketException e1) {
e1.printStackTrace(Main.debug);
}
}
// war Verbindungsaufbau erfolgreich? -> Ausgabe auf Standardausgabe
// wenn nicht erfolgreich Uebergang zu Zustand CLOSED und ausloesen
// einer Exception
if (zustand != ESTABLISHED && zustand != CLOSED) {
zustand = CLOSED;
throw new VerbindungsException(messages
.getString("sw_tcpsocket_msg5"));
}
else {
// Main.debug.println("TCPSocket: Socket hat Verbindungsaufbau abgeschlossen.");
}
}
/**
* Diese Methode erstellt die entsprechende Anzahl von Segmenten zur
* Uebertragung einer Nachricht und gibt diese in einer Liste zurueck. Das
* letzte Segment wird mit dem Flag 'Ende' markiert.
*
* @author carsten
* @param daten -
* Datenstring, der in den Segmenten als Nutzdaten uebertragen
* werden soll
* @return - Gibt eine Liste mit den erstellten TcpSegmenten zurueck.
*/
protected LinkedList<TcpSegment> erstelleSegmente(String daten) {
Main.debug.println("INVOKED ("+this.hashCode()+") "+getClass()+" (TCPSocket), erstelleSegment("+daten+")");
LinkedList<TcpSegment> segmenteListe;
int paketeAnzahl;
TcpSegment segment;
paketeAnzahl = (int) Math.ceil((float) daten.length() / (float) MSS);
segmenteListe = new LinkedList<TcpSegment>();
for (int i = 1; i <= paketeAnzahl; i++) {
segment = new TcpSegment();
if (daten.length() < MSS) {
segment.setDaten(daten);
}
else {
segment.setDaten(daten.substring(0, MSS));
daten = daten.substring(MSS);
}
if (i == paketeAnzahl) {
segment.setPush(true);
}
segmenteListe.add(segment);
}
return segmenteListe;
}
/**
* Mit dieser Methode wird eine Nachricht auf Segmente aufgeteilt und
* versendet. Bevor das naechste Segment verschickt wird, blockiert die
* Methode bis zur Bestaetigung mit einem ACK-Segment. Es wird also ein
* Stop-and-Wait-Algorithmus umgesetzt. <br />
* Diese Methode ist <b>synchronized</b>, weil sonst die durchgaengig
* aufsteigende Sequenznummer der Segmente nicht gewaehrleistet wird.
*
* @throws VerbindungsException
* wenn beim Aufruf keine Verbindung hergestellt ist oder
* waehrend der Uebertragung die Verbindung beendet wird.
* @throws TimeOutException
* wenn eine Bestaetigung nicht rechtzeitig eintrifft
*/
public synchronized void senden(String nachricht) throws VerbindungsException,
TimeOutException {
Main.debug.println("INVOKED ("+this.hashCode()+") "+getClass()+" (TCPSocket), senden("+nachricht+")");
TcpSegment segment, antwort;
boolean bestaetigt = true;
LinkedList<TcpSegment> liste;
ListIterator<?> it;
long versendeZeitpunkt = Long.MAX_VALUE;
long rtt;
if (zustand != ESTABLISHED) { // if I understand it correctly, then the thrown exception is needed for
// properly resetting the connection! (no harmful exception, but part of the concept)
Main.debug.println("EXCEPTION: "+getClass()+" ("+this.hashCode()+"); zustand="+zustand);
zustand = CLOSED;
throw new VerbindungsException(messages.getString("sw_tcpsocket_msg6"));
}
liste = erstelleSegmente(nachricht);
// Die erstellten Segmente werden verschickt
// und auf die Bestaetigung gewartet, bevor das
// naechste Segment verschickt wird.
it = liste.listIterator();
while (it.hasNext() && zustand == ESTABLISHED) {
bestaetigt = false;
segment = (TcpSegment) it.next();
segment.setSeqNummer(sequenzNummer);
// Versand eines einzelnen Segments mit dem
// Stop-and-Wait-Algorithmus
// Wenn diese Schleife beendet ist, muss das Segment entweder
// verschickt und der Empfang bestaetigt worden sein und damit
// auch die Sequenznummer inkrementiert sein oder der Versand
// war nicht erfolgreich
for (int i = 0; i < MAX_SENDEVERSUCHE && !bestaetigt; i++) {
if (zustand != ESTABLISHED) {
// Wenn die Verbindung zwischenzeitlich unterbrochen
// wurde, wird eine Verbindungsexception ausgeloest.
zustand = CLOSED;
throw new VerbindungsException(messages
.getString("sw_tcpsocket_msg7"));
}
sendeSegment(segment);
versendeZeitpunkt = System.currentTimeMillis();
// In dieser Schleife werden alle eingehenden
// Segmente geprueft, ob sie das ACK-Segment und
// damit die Bestaetigung fuer das versendete
// Segment darstellen. Alle anderen Segmente
// werden verworfen.
do {
synchronized (puffer) {
if (puffer.size() < 1) {
try {
puffer.wait(Verbindung.holeRTT());
}
catch (InterruptedException e) {
e.printStackTrace(Main.debug);
}
}
}
synchronized (puffer) {
while (puffer.size() > 0 && !bestaetigt) {
antwort = puffer.removeFirst();
if (antwort.isAck()) {
if (antwort.getAckNummer() == naechsteSequenznummer(sequenzNummer)) {
sequenzNummer = antwort.getAckNummer();
bestaetigt = true;
}
}
}
}
rtt = System.currentTimeMillis() - versendeZeitpunkt;
}
while (!bestaetigt && (rtt < Verbindung.holeRTT())
&& zustand == ESTABLISHED);
}
if (!bestaetigt && zustand != CLOSED) {
zustand = CLOSED;
throw new TimeOutException(messages
.getString("sw_tcpsocket_msg8"));
}
}
}
/**
* Beim Aufruf dieser Methode werden die eingehenden TCP-Segmente zu einer
* Nachricht zusammen gefuegt und wenn das Ende der Nachricht erreicht ist,
* wird diese zurueck gegeben. Das Ende einer Nachricht wird hier mit dem
* Flag 'Ende' gekennzeichnet. <br />
* Diese Methode ist <b>blockierend</b>.
*
* @return gibt den zusammengesetzten empfangenen Datenstring zurueck. Wenn
* die Verbindung vor Eingang einer Nachricht geschlossen wurde,
* wird null zurueck gegeben.
* @throws VerbindungsException -
* wird geworfen, wenn beim Aufruf keine Verbindung hergestellt
* ist
* @throws TimeOutException -
* wird geworfen, wenn die entfernte Anwendung nicht mehr
* reagiert oder Verbindung unterbrochen wurde.
*/
public String empfangen() throws VerbindungsException, TimeOutException {
Main.debug.println("INVOKED ("+this.hashCode()+") "+getClass()+" (TCPSocket), empfangen()");
StringBuffer nachricht = new StringBuffer();
LinkedList<TcpSegment> segmentListe = new LinkedList<TcpSegment>();
boolean beendet = false;
TcpSegment segment;
long zeitpunkt = Long.MAX_VALUE;
// Main.debug.println(getClass().toString() + "\n\tempfangen() aufgerufen"
// + "\n\tlokaler Port: " + lokalerPort);
if (zustand != ESTABLISHED) {
zustand = CLOSED;
throw new VerbindungsException(messages
.getString("sw_tcpsocket_msg9"));
}
while (!beendet && zustand == ESTABLISHED) {
synchronized (puffer) {
if (puffer.size() < 1) {
try {
puffer.wait(Verbindung.holeRTT());
}
catch (InterruptedException e) {
}
}
}
zeitpunkt = System.currentTimeMillis();
if (zustand == ESTABLISHED && !beendet && puffer.size() >= 1) {
segment = (TcpSegment) puffer.getFirst();
// waehrend des Empfangs werden keine ACK-Segmente
// verarbeitet. Diese werden nur beim Versenden von
// Nachrichten genutzt. Daher wartet diese Methode dann
// auf das naechste eintreffende Segment.
if (segment.isAck()) {
synchronized (puffer) {
try {
puffer.wait(Verbindung.holeRTT());
}
catch (InterruptedException e) {
}
}
}
else {
synchronized (puffer) {
puffer.remove(segment);
}
// ist das Segment schon bestaetigt worden?
if (segment.getSeqNummer() < ackNummer) {
sendeAck(segment, null);
}
else if (segment.getSeqNummer() == ackNummer) {
sendeAck(segment, null);
ackNummer = naechsteSequenznummer(ackNummer);
segmentListe.add(segment);
nachricht.append(segment.getDaten());
}
if (segment.isPush())
beendet = true;
}
}
if (System.currentTimeMillis() - zeitpunkt > Verbindung.holeRTT()) {
zustand = CLOSED;
throw new TimeOutException(messages
.getString("sw_tcpsocket_msg10"));
}
}
if (zustand == ESTABLISHED)
return nachricht.toString();
else
return null;
}
/**
* Mit dieser Methode wird ein Verbindungsabbau mit dem FIN-Flag
* eingeleitet. <b>Diese Methode darf nicht blockieren, wenn sich der Socket
* im Zustand LISTEN befindet!</b> <br />
* Das Verhalten zum Schliessen des Sockets ist vom aktuellen Zustand
* abhaengig:
* <ol>
* <li> LISTEN: Der Zustand wird einfach auf CLOSED gesetzt, weiter passiert
* nichts. </li>
* <li> ESTABLISHED: Dann wird ein FIN-Segment verschickt und in den Zustand
* FIN_WAIT_1 gewechselt. <br />
* Mit dem Empfang eines ACK-Segments erfolgt der Zustandsuebergang zu
* FIN_WAIT_2 ohne eine Aktion. <br />
* Nach dem Empfang eines FIN-Segments wird ein ACK-Segment versendet und in
* den Zustand TIME_WAIT gewechselt. <br />
* und zugleich wird ein ACK-Segment versendet. <br />
* Nach einem Timeout, geht der Socket in den Zustand CLOSED ueber. <br />
* Eine weitere Moeglichkeit ist der Uebergang vom Zustand FIN_WAIT_1 zu
* TIME_WAIT bei Empfang eines FIN+ACK-Segments. Dann wird ein ACK
* zurueckgeschickt. <br />
* Die dritte Moeglichkeit ist, das im Zustand FIN_WAIT_1 ein FIN-Segment
* empfangen wird. Dann wird ein ACK-Segment verschickt und in den Zustand
* CLOSING gewechselt. Mit dem Empfang eines ACK-Segments erfolgt der
* Uebergang in den Zustand TIME_WAIT. </li>
* <li> SYN_RCVD: Verhalten wie in Zustand ESTABLISHED. </li>
* <li> CLOSE_WAIT: Wenn der entfernte Socket zuerst den Verbindungsabbau
* eingeleitet hat, befindet sich dieser Socket bereits im Zustand
* CLOSE_WAIT. Auch in diesem Fall wird ein FIN-Segment versendet. Damit
* erfolgt der Uebergang zum Zustand LAST_ACK. Wenn das FIN-Segment
* bestaetigt erfolgt der Uebergang in den Zustand CLOSED ohne weitere
* Aktionen. </li>
* </ol>
*
* Initiieren des Verbindungsabbaus: Mit dem Kommando 'schliessen' wird der
* Verbindungsabbau gestartet. Diese Methode blockiert so lange, bis der
* Endzustand CLOSED erreicht wurde! Wenn beim Datenaustausch zum
* Verbindungsabbau etwas nicht funktioniert, wird der Socket einseitig
* geschlossen
*/
public void schliessen() {
Main.debug.println("INVOKED ("+this.hashCode()+") "+getClass()+" (TCPSocket), schliessen()");
(new Thread(this)).start();
}
public void run() {
Main.debug.println("INVOKED ("+this.hashCode()+") "+getClass()+" (TCPSocket), run()");
TcpSegment tmp;
if (zustand == LISTEN) {
zustand = CLOSED;
synchronized (puffer) {
puffer.notifyAll();
}
}
else if (zustand != CLOSED) {
tmp = new TcpSegment();
tmp.setFin(true);
switch (zustand) {
case ESTABLISHED:
case SYN_RCVD:
sendeSegment(tmp);
zustand = FIN_WAIT_1;
break;
case SYN_SENT:
zustand = CLOSED;
synchronized (puffer) {
puffer.notifyAll();
}
break;
case CLOSE_WAIT:
sendeSegment(tmp);
zustand = LAST_ACK;
break;
}
for (int i = 0; zustand != CLOSED && i < 5; i++) {
synchronized (puffer) {
if (puffer.size() < 1) {
try {
puffer.wait(Verbindung.holeRTT());
}
catch (InterruptedException e) {
}
}
if (zustand == TIME_WAIT) {
puffer.clear();
zustand = CLOSED;
}
else if (puffer.size() >= 1) {
tmp = (TcpSegment) puffer.removeFirst();
switch (zustand) {
case LAST_ACK:
if (tmp.isAck())
zustand = CLOSED;
break;
case CLOSING:
if (tmp.isAck())
zustand = TIME_WAIT;
break;
case FIN_WAIT_2:
if (tmp.isFin()) {
sendeAck(tmp, null);
zustand = TIME_WAIT;
}
break;
case FIN_WAIT_1:
if (tmp.isAck() && tmp.isFin()) {
sendeAck(tmp, null);
zustand = TIME_WAIT;
}
else if (tmp.isAck()) {
zustand = FIN_WAIT_2;
}
else if (tmp.isFin()) {
sendeAck(tmp, null);
zustand = CLOSING;
}
break;
}
}
}
}
austragenPort();
if (zustand != CLOSED) {
zustand = CLOSED;
}
}
}
/**
* Methode zum Senden der Bestaetigung eines TCP-Segments. Dazu wird anhand
* des Attibuts ackNummer zunaechst geprueft, ob das Segment bestaetigt
* werden kann. Dann wird die Sequenznummer des uebergebenen Segments um
* eins erhoeht und in das Acknowledge-Feld des Kopfteils im neuen
* Acknowledge-Segment geschrieben. Es werden also
* <ul>
* <li> das ACK-Flag und </li>
* <li> die Acknowledge-Sequenznummer initialisiert </li>
* </ul>
* Zur weiteren Bearbeitung wird das zu sendende Segment an die Methode
* sendeSegment(TcpSegment) weitergegeben.
*
* @param empfangSegment
* das empfangene Segment, dessen Empfang bestaetigt werden soll.
* @param sendeSegment
* das zu sendende Segment (kann bereits vorab initialisiert
* werden), wenn dieser Parameter 'null' ist, wird ein neues
* Segment erstellt.
*/
private void sendeAck(TcpSegment empfangSegment, TcpSegment sendeSegment) {
Main.debug.println("INVOKED ("+this.hashCode()+") "+getClass()+" (TCPSocket), sendeAck("+empfangSegment+","+sendeSegment+")");
if (sendeSegment == null) {
sendeSegment = new TcpSegment();
}
sendeSegment.setAck(true);
sendeSegment.setAckNummer(naechsteSequenznummer(empfangSegment
.getSeqNummer()));
sendeSegment(sendeSegment);
}
/**
* Zum Erhoehen von Sequenznummern. D. h.: der uebergebene Paramter wird um
* eins erhoeht und es wird eine Modulo-Operation durchgefuehrt. Es treten
* keine Seiteneffekte auf!
*
* @return die naechste Sequenznummer
*/
private static long naechsteSequenznummer(long sequenzNummer) {
Main.debug.println("INVOKED (static) filius.software.transportschicht.TCPSocket, naechsteSequenznummer("+sequenzNummer+")");
sequenzNummer = (sequenzNummer + 1) % ((long) (Math.pow(2, 32)) - 1);
return sequenzNummer;
}
/**
* In dieser Methode werden die eingehenden Segmente in den Puffer
* eingefuegt. Ausserdem wird der Zustandsuebergang von ESTABLISHED nach
* CLOSE_WAIT ausgeloest und die Methode zum Schliessen des Sockets beim
* Empfang eines FIN-Segments aufgerufen.
*/
public void hinzufuegen(String startIp, int startPort, Object segment) {
Main.debug.println("INVOKED ("+this.hashCode()+") "+getClass()+" (TCPSocket), hinzufuegen("+startIp+","+startPort+","+segment+")");
TcpSegment tcpSegment = (TcpSegment) segment;
//Main.debug.println(getClass().toString()
//+ "\n\thinzufuegen() wurde aufgerufen"
//+ "\n\tAbsender-Adresse: " + startIp + ":" + startPort
//+ "\n\tFlags: " + (tcpSegment.isAck() ? "ACK " : "")
//+ (tcpSegment.isFin() ? "FIN " : "")
//+ (tcpSegment.isSyn() ? "SYN " : "")
//+ (tcpSegment.isPush() ? "PUSH " : "") + "\n\tZustand: "
//+ zustand + "\n\tDaten: " + segment.toString());
if (zustand == LISTEN) {
zielPort = startPort;
zielIp = startIp;
}
if (zustand == ESTABLISHED && tcpSegment.isFin()) {
sendeAck(tcpSegment, null);
zustand = CLOSE_WAIT;
synchronized (puffer) {
puffer.notifyAll();
}
}
else {
synchronized (puffer) {
puffer.addLast(tcpSegment);
puffer.notifyAll();
}
}
}
/**
* Diese Methode wird beim Wechsel vom Aktions- zum Entwurfsmodus
* aufgerufen, damit moeglicherweise blockierte Threads beendet werden
* koennen. Hier wird der Zustand auf CLOSED gesetzt und Threads, die auf
* den Puffer warten, aufgeweckt. Es findet kein ordentlicher
* Verbindungsaufbau statt! Der Zustand des Sockets ist anschliessend
* unbestimmt. Daher sollte dieser Socket nicht weiter verwendet werden.
* <br />
* Die Methode ist <b>nicht blockierend</b>!
*/
public void beenden() {
Main.debug.println("INVOKED ("+this.hashCode()+") "+getClass()+" (TCPSocket), beenden()");
zustand = CLOSED;
synchronized (puffer) {
puffer.notifyAll();
}
}
/**
* Zur Abfrage, ob eine Verbindung aufgebaut ist. <br />
* Diese Methode ist <b>nicht blockierend</b>.
*
* @return ob der aktuelle Zustand ESTABLISHED ist
*/
public boolean istVerbunden() {
Main.debug.println("INVOKED ("+this.hashCode()+") "+getClass()+" (TCPSocket), istVerbunden(), port: " + this.holeLokalenPort());
return (zustand == ESTABLISHED);
}
/* workaround function for more ugly exception provoking original code */
public boolean isSortOfConnected() {
Main.debug.println("INVOKED ("+this.hashCode()+") "+getClass()+" (TCPSocket), isSortOfConnected(), port: " + this.holeLokalenPort());
if (zustand>=5 && zustand<=11) {
return true;
}
else { return false; }
}
public String getStateAsString() {
switch (this.zustand) {
case CLOSED:
return "CLOSED";
case LISTEN:
return "LISTEN";
case SYN_RCVD:
return "SYN_RCVD";
case SYN_SENT:
return "SYN_SENT";
case ESTABLISHED:
return "ESTABLISHED";
case CLOSE_WAIT:
return "CLOSE_WAIT";
case LAST_ACK:
return "LAST_ACK";
case FIN_WAIT_1:
return "FIN_WAIT_1";
case FIN_WAIT_2:
return "FIN_WAIT_2";
case CLOSING:
return "CLOSING";
case TIME_WAIT:
return "TIME_WAIT";
default:
return "<unknown>";
}
}
}