K APITEL 3 Transportschicht 3.1 Dienste und Prinzipien auf der Transportschicht Zwischen der Anwendungs- und der Vermittlungsschicht angesiedelt, bildet die Transportschicht das Kernstück der geschichteten Netzwerkarchitektur. Sie erfüllt die wichtige Rolle der direkten Bereitstellung von Kommunikationsdiensten für die Anwendungsprozesse, die auf verschiedenen Hosts laufen. In diesem Kapitel beschreiben wir die Dienste, die von einem Protokoll der Transportschicht bereitgestellt werden können, und die verschiedenen, der Bereitstellung dieser Dienste zugrunde liegenden Prinzipien. Ferner wird beschrieben, wie diese Dienste in bestehenden Protokollen implementiert sind. Wie bisher, liegt die besondere Betonung auf den Internet-Protokollen, d. h. den TCP- und UDP-Protokollen auf der Transportschicht. In den ersten beiden Kapiteln haben wir die Rolle der Transportschicht und die von ihr bereitgestellten Dienste kurz angesprochen. Hier folgt nun eine kurze Übersicht dessen, was Sie über die Transportschicht bisher erfahren haben. Ein Protokoll der Transportschicht bietet eine logische Kommunikation zwischen Anwendungsprozessen, die auf unterschiedlichen Hosts laufen. Unter logischer Kommunikation verstehen wir, dass die kommunizierenden Anwendungsprozesse zwar nicht physisch miteinander verbunden sind (tatsächlich können sie sich an entgegengesetzten Stellen der Erde befinden und über zahlreiche Router und viele verschiedene Verbindungsleitungen verbunden sein), aus Sicht der Anwendung aber physikalisch verbunden erscheinen. Anwendungsprozesse verwenden die von der Transportschicht bereitgestellte logische Kommunikation, um Nachrichten miteinander auszutauschen, ohne sich um die Details der physikalischen Infrastruktur, über die diese Nachrichten fließen, kümmern zu müssen. Abbildung 3.1 stellt das Konzept der logischen Kommunikation dar. Wie aus Abbildung 3.1 deutlich wird, werden Protokolle auf der Transportschicht in den Endsystemen und nicht in Netzwerk-Routern implementiert. Netzwerk-Router agieren lediglich in den Feldern der 3-PDUs der Vermittlungsschicht; sie agieren nicht als Felder der Transportschicht. Auf der sendenden Seite konvertiert die Transportschicht die Nachrichten, die von einem sendenden Anwendungsprozess ankommen, in 4-PDUs (d. h. in Protokolldateneinheiten der Transportschicht). Dies wird (möglicherweise) dadurch bewerkstelligt, dass die Nachrichten einer Anwendung in kleinere Stücke aufgeteilt und jedem Stück ein Header der Transportschicht angehängt wird, um 4-PDUs zu erzeugen. Die Transportschicht gibt die 4-PDUs dann an die Vermittlungsschicht weiter, wo jede 4-PDU in einer 3-PDU verkapselt wird. Auf der empfangenden Seite Kapitel 3 – Transportschicht 176 Abbildung 3.1 Die Transportschicht bietet eine logische und keine physikalische Kommunikation zwischen Anwendungen. Anwendung Transport Vermittl. Sicherung Bitübertr. Lokaler ISP Vermittl. Sicherung Vermittl. Bitübertr. Sicherung Bitübertr. Vermittl. gis Lo Sicherung ch Bitübertr. Vermittl. er Sicherung En -E -zu de Bitübertr. ra e-T nd Vermittl. rt po ns Sicherung Bitübertr. Anwendung Transport Basisstation Firmennetzwerk Vermittl. Sicherung Bitübertr. erhält die Transportschicht die 4-PDUs von der Vermittlungsschicht, entfernt den Transport-Header von den 4-PDUs, setzt die Nachrichten wieder zusammen und gibt sie an einen empfangenden Anwendungsprozess weiter. Ein Computernetzwerk kann Netzwerkanwendungen mehr als ein Protokoll auf der Transportschicht zur Verfügung stellen. Das Internet hat beispielsweise zwei Protokolle: TCP und UDP. Jedes dieser Protokolle bietet andere Transportschichtdienste für die nutzende Anwendung. Alle Protokolle der Transportschicht bieten einer Anwendung einen Multiplex/ Demultiplex-Dienst. Dieser Dienst wird ausführlich im nächsten Abschnitt beschrieben. Wie in Abschnitt 2.1 erwähnt, kann ein Transportprotokoll abgesehen vom Multiplex/Demultiplex-Dienst auch weitere Dienste, z. B. zuverlässigen Datentransfer und Zusicherungen über Bandbreiten und Verzögerungen, bereitstellen. 3.1 Dienste und Prinzipien auf der Transportschicht 3.1.1 Beziehung zwischen der Transport- und der Vermittlungsschicht Die Transportschicht liegt unmittelbar oberhalb der Vermittlungsschicht im Protokollstack. Während ein Protokoll der Transportschicht eine logische Kommunikation zwischen Prozessen bietet, die auf unterschiedlichen Hosts laufen, stellt ein Protokoll der Vermittlungsschicht eine logische Kommunikation zwischen Hosts bereit. Dieser Unterschied ist subtil, aber sehr wichtig. Wir wollen ihn im Folgenden anhand einer Haushaltsanalogie erklären. Man stelle sich zwei Häuser vor, von denen eines an der Ostküste und das andere an der Westküste (der USA) steht; in jedem der beiden Häuser wohnen ein Dutzend Kinder. Die Kids in dem Haushalt an der Ostküste sind Cousins der Kids im Haushalt an der Westküste. Die Kids der beiden Haushalte haben Spaß, sich gegenseitig zu schreiben; jedes Kind schreibt jedem Cousin jede Woche, wobei jeder Brief über die konventionelle gelbe Post in einem getrennten Umschlag befördert wird. Folglich verschickt jeder Haushalt pro Woche 144 Briefe an den anderen Haushalt. (Diese Kids würden viel Geld sparen, wenn sie E-Mail hätten!) In jedem Haushalt ist eines der Kinder – Ann im Haus an der Westküste und Bill in dem Haus an der Ostküste – für die Sammlung und die Verteilung der Post zuständig. Ann besucht jede Woche ihre Brüder und Schwestern, sammelt die Post ein und gibt sie dem Briefträger bei seiner täglichen Runde. Wenn Briefe im Haus an der Westküste ankommen, hat Ann auch die Aufgabe, die Post an ihre Brüder und Schwestern zu verteilen. An der Ostküste führt Bill diese Aufgabe aus. In diesem Beispiel bietet der Postdienst eine logische Kommunikation zwischen den beiden Häusern: Er befördert Post von Haus zu Haus und nicht von Person zu Person. Andererseits bieten Ann und Bill eine logische Kommunikation unter den Cousins: Sie sammeln die Post von ihren Geschwistern ein und verteilen ankommende Post an sie. Aus Sicht der Cousins sind Ann und Bill der Postdienst, obwohl die beiden nur ein Teil (der Endsystemteil) des Ende-zu-Ende-Transportprozesses sind. Dieses Haushaltsbeispiel dient als Analogie, um den Zusammenhang zwischen der Transport- und der Vermittlungsschicht zu erklären: Hosts (auch Endsysteme genannt) = Häuser Prozesse = Cousins Anwendungsnachrichten = Briefe in Umschlägen Protokoll der Netzwerkschicht = Postdienst (mit Briefträgern) Protokoll der Transportschicht = Ann und Bill Wir fahren mit dieser Analogie fort und stellen fest, dass Ann und Bill ihre gesamte Arbeit innerhalb ihres jeweiligen Heims verrichten; sie haben z. B. nichts mit dem Sortieren von Post in einem Postamt oder der Beförderung von Post von einer Postverteilerstelle zu einer anderen zu tun. Ähnlich leben die Protokolle der Transportschicht in den Endsystemen. Innerhalb eines Endsystems verschiebt ein Transportprotokoll Nachrichten von Anwendungsprozessen zur Netzwerkperipherie (d. h. zur Vermittlungsschicht) und umgekehrt; es hat aber keinen Einfluss darauf, wie die Nachrichten innerhalb des Netzwerkkerns verschoben werden. Wie aus Abbildung 3.1 deutlich wird, wirken die dazwischen liegenden Router weder auf Informationen ein, die auf der Transportschicht möglicherweise an die Anwendungsnachrichten angehängt werden, noch erkennen sie solche. 177 178 Kapitel 3 – Transportschicht Wir greifen wieder unsere Familiensaga auf und nehmen an, dass zwei andere Cousins, sagen wir Susan und Harvey, Ann und Bill ablösen, wenn diese in den Ferien sind. Zum Leidwesen der beiden Familien sammeln und verteilen Susan und Harvey die Post nicht genau auf die gleiche Weise wie Ann und Bill. Susan und Harvey sind kleinere Kinder und so passiert es schon mal, dass sie Post weniger regelmäßig abholen und verteilen und auch mal einige Briefe verlieren (die manchmal vom Haushund zerfetzt werden). Susan und Harvey bieten also nicht die gleichen Dienste (d. h. das gleiche Dienstmodell) wie Ann und Bill. Vergleichbar damit kann ein Computernetzwerk mehrere Transportprotokolle zur Verfügung stellen, die sich hinsichtlich ihres Dienstmodells für Anwendungen unterscheiden. Die möglichen Dienste, die Ann und Bill bereitstellen können, sind deutlich auf die möglichen Dienste eingeschränkt, die der Postdienst bietet. Wenn der Postdienst beispielsweise keine Höchstgrenze festlegt, wie lange die Zustellung eines Briefs zwischen den beiden Häuser dauern darf (z. B. drei Tage), besteht für Ann und Bill keine Möglichkeit, eine maximale Verzögerung für die Postzustellung zwischen zwei Cousins zuzusichern. Ähnlich werden die Dienste, die ein Transportprotokoll bereitstellen kann, oft durch das Dienstmodell des zugrunde liegenden Protokolls auf der Vermittlungsschicht beschränkt. Wenn das Protokoll auf der Vermittlungsschicht keine Zusicherungen über Verzögerung oder Bandbreite für 4-PDUs, die zwischen zwei Hosts versendet werden, machen kann, dann kann das Protokoll auf der Transportschicht keine Zusicherungen über Verzögerung oder Bandbreite für die zwischen Prozessen ausgetauschten Nachrichten geben. Dennoch können bestimmte Dienste von einem Transportprotokoll geboten werden, auch wenn das zugrunde liegende Netzwerkprotokoll auf der Vermittlungsschicht keinen entsprechenden Dienst bietet. Wie wir in diesem Kapitel noch sehen werden, kann ein Transportprotokoll einer Anwendung z. B. zuverlässigen Datentransferdienst bieten, auch wenn das zugrunde liegende Netzwerkprotokoll unzuverlässig ist, d. h. auch wenn das Netzwerkprotokoll Pakete verliert, verstümmelt und dupliziert. Als weiteres Beispiel (das wir umfassender in Kapitel 7 in Zusammenhang mit Netzwerksicherheit behandeln) kann ein Transportprotokoll Verschlüsselung anwenden, um zuzusichern, dass Nachrichten nicht von Eindringlingen gelesen werden, auch wenn die Vermittlungsschicht keine Vertraulichkeit von 4-PDUs zusichern kann. 3.1.2 Übersicht über die Transportschicht im Internet Wir erinnern uns, dass das Internet, und allgemeiner ein TCP/IP-Netzwerk, der Anwendungsschicht zwei unterschiedliche Protokolle auf der Transportschicht zur Verfügung stellt. Eines dieser Protokolle ist UDP (User Datagram Protocol), das Anwendungen einen unzuverlässigen, verbindungslosen Dienst bereitstellt. Das zweite ist TCP (Transmission Control Protocol), das Anwendungen einen zuverlässigen, verbindungsorientierten Dienst bietet. Im Design einer Netzwerkanwendung muss der Anwendungsentwickler eines dieser beiden Transportprotokolle spezifizieren. Wir haben in den Abschnitten 2.6 und 2.7 gesehen, dass der Anwendungsentwickler entweder UDP oder TCP wählt, wenn er Sockets erstellt. Um die Terminologie zu vereinfachen, sprechen wir in Zusammenhang mit dem Internet von einem Segment, um eine 4-PDU zu bezeichnen. In der Internet-Literatur (z. B. in RFCs) wird die PDU in Zusammenhang mit TCP oft als »Segment« und in Zusammenhang mit UDP als Datagramm bezeichnet. In der gleichen Literatur findet 3.1 Dienste und Prinzipien auf der Transportschicht man aber auch den Begriff »Datagramm« für PDUs der Vermittlungsschicht! Wir sind der Ansicht, dass in einem Fachbuch über die Grundlagen im Vernetzungsbereich wie diesem keine Verwirrung mit Begriffen gestiftet werden soll, und verwenden daher den Begriff »Segment« für TCP- und UDP-PDUs, während wir den Begriff »Datagramm« nur für PDUs der Vermittlungsschicht verwenden. Bevor wir mit unserer kurzen Einführung von UDP und TCP fortfahren, sind an dieser Stelle ein paar Worte über die Vermittlungsschicht des Internets nützlich. (Die Vermittlungsschicht ist Thema von Kapitel 4.) Das Protokoll der Internet-Vermittlungsschicht ist IP (Internet Protocol). IP bietet eine logische Kommunikation zwischen Hosts. Das IP-Dienstmodell ist der Best-Effort-Dienst. Das bedeutet, dass IP Segmente zwischen kommunizierenden Hosts nach »bestem Bemühen« überträgt, allerdings keine Zusicherungen macht. Insbesondere gibt es keine Zusicherung über die Ankunft von Segmenten, über deren Ankunft in der richtigen Reihenfolge und über die Integrität der Daten in den Segmenten. Folglich bietet IP einen unzuverlässigen Dienst. Jeder Host hat eine IP-Adresse. IP-Adressierung wird ausführlich in Kapitel 4 behandelt; vorläufig genügt es zu wissen, dass jeder Host eine eindeutige IP-Adresse hat. Nachdem wir einen Blick auf das IP-Dienstmodell geworfen haben, fassen wir das Dienstmodell von UDP und TCP zusammen. Die grundlegende Verantwortung von UDP und TCP ist im Wesentlichen die Erweiterung des IP-Übertragungsdienstes zwischen zwei Endsystemen auf einen Übertragungsdienst zwischen zwei Prozessen, die auf zwei Endsystemen laufen. Diese Erweiterung der Host-zu-Host- auf die Prozesszu-Prozess-Übertragung wird als Anwendungsmultiplexen und -demultiplexen bezeichnet (wird im nächsten Abschnitt beschrieben). UDP und TCP bieten auch Integritätsprüfung dadurch, dass Fehlererkennungsfelder in die Header einbezogen werden. Diese beiden minimalen Transportschichtdienste – Prozess-zu-ProzessDatenübertragung und Fehlerprüfung – sind die einzigen Dienste, die UDP bereitstellt! Wie IP ist UDP ein unzuverlässiger Dienst; es gibt keine Zusicherung, dass die von einem Prozess gesendeten Daten beim Zielprozess intakt ankommen. UDP wird ausführlich in Abschnitt 3.3 behandelt. TCP dagegen bietet Anwendungen mehrere zusätzliche Dienste. Vor allem bietet es zuverlässigen Datentransfer. Mit Hilfe von Flusskontrolle, Sequenznummern, Bestätigungen (ACKs) und Timern (Techniken, die in diesem Kapitel ausführlich behandelt werden) gewährleistet TCP, dass die Daten vom sendenden zum empfangenden Prozess korrekt und in der richtigen Reihenfolge übertragen werden. TCP konvertiert folglich den unzuverlässigen Dienst von IP zwischen Endsystemen in einen zuverlässigen Datentransportdienst zwischen Prozessen. TCP nutzt auch Überlastkontrolle. Dabei handelt es sich nicht so sehr um einen Dienst für die aufrufende Anwendung, als vielmehr um einen für das Internet insgesamt, also einen Dienst zum allgemeinen Nutzen. Grob gesagt, hindert die TCP-Überlastkontrolle eine TCPVerbindung daran, die Verbindungsleitungen und Switches zwischen kommunizierenden Hosts mit übermäßigem Verkehrsvolumen zu überschwemmen. Im Prinzip können TCP-Verbindungen, die ein überlastetes Netzwerk überqueren, die Bandbreite der betreffenden Verbindungsleitung gemeinsam nutzen. Dies wird dadurch bewerkstelligt, dass die Rate, in der die sendende Seite Verkehr in das Netzwerk einspeisen kann, reguliert wird. Im Gegensatz dazu wird UDP-Verkehr nicht reguliert. Eine Anwendung, die UDP-Transport nutzt, kann in jeder beliebigen Rate senden, solange sie will. 179 180 Kapitel 3 – Transportschicht Ein Protokoll, das zuverlässigen Datentransfer und Überlastkontrolle bietet, ist natürlich komplex. Wir müssen die Prinzipien von zuverlässigem Datentransfer und Überlastkontrolle in mehreren Abschnitten beschreiben und zusätzliche Abschnitte sind notwendig, um das TCP-Protokoll selbst abzudecken. Diese Themen werden in den Abschnitten 3.4 bis 3.8 behandelt. In diesem Kapitel wechseln wir zwischen einem bestimmten Basisprinzip und dem TCP-Protokoll. Beispielsweise behandeln wir zuerst den zuverlässigen Datentransfer im Allgemeinen und anschließend die Art, wie TCP im Besonderen zuverlässigen Datentransfer bereitstellt. Ebenso behandeln wir Überlastkontrolle zuerst im allgemeinen Umfeld und anschließend spezifisch in TCP. Bevor wir uns diesen Themen zuwenden, betrachten wir im nächsten Abschnitt zunächst das Multiplexen und Demultiplexen von Anwendungen. 3.2 Multiplexen und Demultiplexen von Anwendungen Dieser Abschnitt befasst sich mit dem Multiplexen und Demultiplexen von Netzwerkanwendungen. Um eine konkrete Beschreibung sicherzustellen, befassen wir uns mit diesem grundlegenden Transportschichtdienst in Zusammenhang mit dem Internet. Wir betonen allerdings, dass für alle Computernetzwerke ein Multiplex/Demultiplex-Dienst erforderlich ist. Der Multiplex/Demultiplex-Dienst zählt zwar nicht zu den interessantesten Diensten, die ein Protokoll der Transportschicht bereitstellen kann, er ist jedoch absolut wichtig. Um dies zu verstehen, bedenke man die Tatsache, dass IP Daten zwischen zwei Endsystemen überträgt, wobei jedes Endsystem mit einer eindeutigen IPAdresse identifiziert wird. IP überträgt Daten nicht zwischen den Anwendungsprozessen, die auf diesen Endsystemen laufen. Die Erweiterung der Host-zu-Host- auf die Prozess-zu-Prozess-Übertragung wird durch Anwendungsmultiplexen und -demultiplexen erreicht. Auf dem Zielhost empfängt die Transportschicht Segmente (d. h. PDUs der Transportschicht) von der unmittelbar darunter liegenden Vermittlungsschicht. Die Transportschicht ist für die Übertragung der Daten in diesen Segmenten an den entsprechenden Anwendungsprozess, der auf dem Host läuft, zuständig. Wir betrachten ein Beispiel. Angenommen, Sie sitzen vor Ihrem Computer und laden Web-Seiten herunter, während Sie eine FTP-Sitzung und zwei Telnet-Sitzungen ausführen. Das heißt, es laufen momentan vier Netzwerkanwendungsprozesse (zwei Telnet-Prozesse, ein FTP-Prozess und ein HTTP-Prozess). Wenn die Transportschicht in Ihrem Computer Daten von der darunter liegenden Vermittlungsschicht empfängt, muss sie die empfangenen Daten an einen dieser vier Prozesse weiterleiten. Wie geht sie dabei vor? Jedes Transportschichtsegment umfasst eine Reihe von Feldern, die den Prozess bestimmen, an den die Daten des Segments zu übertragen sind. Am empfangenden Ende kann die Transportschicht dann diese Felder prüfen, um den empfangenden Prozess zu ermitteln und das Segment an diesen Prozess weiterzuleiten. Die Aufgabe der Übertragung der in einem Transportschichtsegment enthaltenen Daten an den richtigen Anwendungsprozess nennt man Demultiplexen. Die Aufgabe des Einsammelns von Daten im Quellhost aus verschiedenen Anwendungsprozessen, die Vervollständigung der Daten mit Header-Informationen (die später beim Demultiplexen benutzt werden), um Segmente zu bilden, und die Weiterleitung der Segmente an die Vermittlungsschicht wird als Multiplexen bezeichnet. Multiplexen und Demultiplexen sind in Abbildung 3.2 dargestellt. 3.2 Multiplexen und Demultiplexen von Anwendungen 181 Abbildung 3.2 Multiplexen und Demultiplexen Empfänger Daten auf der Anwendungsschicht P1 SegmentM Header Anwendung Segment Ht M Hn Segment P3 P4 M M' Anwendung P2 Transport M' Vermittl. Anwendung Transport Transport Vermittl. Vermittl. Um Demultiplexen besser zu verstehen, betrachten wir wieder unsere Haushaltssaga aus dem vorherigen Abschnitt. Jedes der Kids wird anhand seines Namens unterschieden. Wenn Bill einen Stapel Post vom Briefträger erhält, führt er eine Demultiplexoperation durch, indem er feststellt, an wen die Briefe adressiert sind, und die Post dann an seine Brüder und Schwestern verteilt. Ann führt eine Multiplexoperation durch, wenn sie Briefe von ihren Geschwistern einsammelt und die angesammelte Post dem Briefträger übergibt. UDP und TCP führen die Demultiplex- und Multiplexaufgaben dadurch aus, dass sie zwei spezielle Felder in die Segment-Header einbeziehen: das Feld Portnummer Quelle und das Feld Portnummer Ziel. Diese beiden Felder sind in Abbildung 3.3 dargestellt. Zusammen identifizieren die beiden Felder eindeutig einen Anwendungsprozess, der auf dem Zielhost läuft. (UDP- und TCP-Segmente haben noch weitere Felder, die in den nächsten Abschnitten dieses Kapitels beschrieben werden.) Abbildung 3.3 Felder für die Portnummer von Quelle und Ziel in einem Segment der Transportschicht 32 Bit Portnummer Quelle Portnummer Ziel Weitere Header-Felder Anwendungsdaten (Nachricht) 182 Kapitel 3 – Transportschicht Das Konzept von Portnummern wurde in den Abschnitten 2.6 und 2.7 in Zusammenhang mit der Anwendungsentwicklung und der Socket-Programmierung kurz vorgestellt. Die Portnummer ist eine 16-Bit-Nummer von 0 bis 65535. Die Portnummern im Bereich von 0 bis 1023 werden als wohl bekannte (well-known) Portnummern bezeichnet und sind eingeschränkt, was bedeutet, dass sie für wohl bekannte Anwendungsprotokolle wie HTTP und FTP reserviert sind. HTTP benutzt Portnummer 80 und FTP Portnummer 21. RFC 1700 enthält eine Liste aller wohl bekannten Portnummern. Wenn wir eine neue Anwendung (z. B. eine der Anwendungen in den Abschnitten 2.6 bis 2.8) entwickeln, müssen wir der Anwendung eine Portnummer zuweisen. Angesichts der Tatsache, dass jeder Anwendungstyp, der auf einem Endsystem läuft, eine eindeutige Portnummer hat, stellt sich die Frage, warum das Transportschichtsegment Felder für zwei Portnummern – eines für die Portnummer der Quelle und eines für die des Ziels – beinhaltet? Die Antwort ist einfach: Ein Endsystem kann zwei Prozesse des gleichen Typs gleichzeitig ausführen; deswegen genügt die Zielportnummer einer Anwendung nicht immer, um einen bestimmten Prozess zu identifizieren. Dies ist z. B. der Fall, wenn ein Web-Server für jede verarbeitete Anfrage einen neuen HTTP-Prozess startet. Jedes Mal, wenn dieser Web-Server mehr als eine Anfrage bedient (was keinesfalls ungewöhnlich ist), führt der Server mehr als einen Prozess mit Portnummer 80 aus. Um den Prozess, an den Daten gerichtet sind, eindeutig zu identifizieren, ist deshalb eine zweite Portnummer erforderlich. Wie wird diese zweite Portnummer erzeugt? Welche Portnummer wird im Feld »Portnummer Quelle« eines Segments angegeben? Welche wird im Feld »Portnummer Ziel« eines Segments angegeben? Um diese Fragen zu beantworten, rufen wir uns aus Abschnitt 2.1 wieder ins Gedächtnis, dass Netzwerkanwendungen rund um das Client/Server-Modell organisiert sind. Normalerweise ist der Host, der die Anwendung einleitet, der Client, und der andere Host ist der Server. Wir betrachten ein spezifisches Beispiel. Angenommen, die Anwendung hat Portnummer 23 (diejenige für Telnet). Ein Transportschichtsegment verlässt den Client (d. h. den Host, der die Telnet-Sitzung gestartet hat) in Richtung Server. Wie lautet bei diesem Segment die Portnummer für die Quelle und das Ziel? Die Zielportnummer dieses Segments ist die der Anwendung, nämlich 23. Für die Quellportnummer benutzt der Client eine Nummer, die noch keinem anderen Hostprozess zugewiesen wurde. (Dies erfolgt automatisch durch die Transportschichtsoftware, die auf dem Client läuft, und ist für den Anwendungsentwickler transparent.) Es sei gegeben, dass der Client Portnummer x wählt. Jedes Segment, das dieser Prozess an den Telnet-Server sendet, enthält als Quellportnummer x und als Zielportnummer 23. Wenn das Segment beim Server ankommt, ermöglichen es die beiden Portnummern im Segment dem Serverhost, die Daten des Segments an den richtigen Anwendungsprozess weiterzuleiten. Die Zielportnummer 23 identifiziert einen Telnet-Prozess und die Quellportnummer x den spezifischen Telnet-Prozess. Die Situation ist umgekehrt bei den Segmenten, die vom Server zum Client fließen. Die Quellportnummer ist jetzt die Anwendungsportnummer, also 23, und die Zielportnummer ist jetzt x (die gleiche x, die als Quellportnummer für die Segmente, die vom Client zum Server gesendet wurden, benutzt wird). Wenn ein Segment beim Client ankommt, ermöglichen es die Quell- und Zielportnummern im Segment dem Clienthost, die Daten des Segments an den richtigen Anwendungsprozess weiterzugeben, der durch das Portnummernpaar identifiziert wird (siehe Abbildung 3.4). Sie fragen sich jetzt vielleicht, was passiert, wenn zwei verschiedene Clients eine Sitzung zu einem Server aufbauen und jeder der beiden Clients die gleiche Quellport- 3.2 Multiplexen und Demultiplexen von Anwendungen Abbildung 3.4 Verwendung einer Quell- und Zielportnummer in einer Client/ServerAnwendung Quellport: x Host A Zielport: 23 Server B Quellport: 23 Zielport: x nummer x wählt? Dies kann auf einem stark frequentierten WWW-Server, der viele Web-Clients gleichzeitig bedient, tatsächlich passieren. Wie kann der Server die Segmente demultiplexen, wenn die beiden Sitzungen genau das gleiche Portnummernpaar haben? Die Antwort auf diese Frage lautet, dass der Server auch die IP-Adressen in den IP-Datagrammen, in denen diese Segmente befördert werden, heranzieht. (IPDatagramme und Adressierung werden ausführlich in Kapitel 4 behandelt.) Diese Situation ist in Abbildung 3.5 dargestellt, bei der Host C zwei HTTP-Sitzungen zu Server B und Host A eine HTTP-Sitzung zu B einleitet. Abbildung 3.5 Zwei Clients verwenden die gleichen Portnummern, um mit der gleichen Server-Anwendung zu kommunizieren. WWW-ClientHost C Quell-IP: C Quell-IP: C Ziel-IP: B Ziel-IP: B Quellport: y Quellport: x Zielport: 80 Zielport: 80 WWW-ClientHost A Quell-IP: A Ziel-IP: B Quellport: x Zielport: 80 WWWServer B 183 184 Kapitel 3 – Transportschicht Die Hosts A und C und Server B haben jeweils eine eindeutige IP-Adresse – A, C bzw. B. Host C weist den beiden HTTP-Verbindungen, die von Host A ausgehen, jeweils eine unterschiedliche Quellportnummer (die SP-Nummer x bzw. y) zu. Da Host A die Quellportnummern aber unabhängig von C wählt, kann es passieren, dass er seiner HTTP-Verbindung ebenfalls SP = x zuweist. Server B wäre dennoch in der Lage, die beiden Verbindungen korrekt zu demultiplexen, weil die beiden Verbindungen je eine andere IP-Quelladresse haben. Zusammenfassend kann man sagen: Wenn ein Zielhost Daten von der Vermittlungsschicht empfängt, wird das Trio (IP-Quelladresse, Quellportnummer, Zielportnummer) verwendet, um die Daten an den entsprechenden Prozess weiterzuleiten. Nachdem Sie jetzt wissen, wie die Transportschicht Netzwerkanwendungen multiplexen und demultiplexen kann, fahren wir mit der Beschreibung des InternetTransportprotokolls UDP fort. Der nächste Abschnitt wird zeigen, dass UDP das Protokoll der Vermittlungsschicht um kaum mehr als einen Multiplex/DemultiplexDienst erweitert. 3.3 Verbindungsloser Transport: UDP In diesem Abschnitt befassen wir uns mit den Merkmalen und der Funktionsweise von UDP. Wir empfehlen dem Leser, sich noch einmal Abschnitt 2.1 anzusehen, der eine Übersicht über das UDP-Dienstmodell beinhaltet, und Abschnitt 2.7, in dem Socket-Programmierung über UDP beschrieben wird. Als Motivation für diese Beschreibung von UDP nehmen wir an, Sie sind daran interessiert, ein einfaches Transportprotokoll zu entwikkeln. Wie können Sie vorgehen? Sie können sich zuerst die Verwendung eines hirnlosen Transportprotokolls überlegen. Insbesondere könnten Sie für die Sendeseite in Betracht ziehen, die Nachrichten von dem Anwendungsprozess entgegenzunehmen und sie direkt an die Vermittlungsschicht weiterzugeben. Auf der Empfangsseite können die von der Vermittlungsschicht ankommenden Nachrichten direkt an den Anwendungsprozess weitergegeben werden. Wie wir im vorherigen Abschnitt aber gelernt haben, müssen wir ein bisschen mehr als nichts tun. Zumindest muss die Transportschicht einen Multiplex/Demultiplex-Dienst bereitstellen, damit Daten zwischen der Vermittlungsschicht und dem richtigen Prozess weitergeleitet werden können. Das in RFC 768 definierte UDP tut so wenig, wie man sich für ein Transportprotokoll nur vorstellen kann. Abgesehen von der Multiplex/Demultiplex-Funktion und einer geringen Fehlerprüfung fügt es nichts zu IP hinzu. Wenn sich der Anwendungsentwickler für UDP statt TCP entscheidet, spricht die Anwendung tatsächlich fast direkt mit IP. UDP nimmt Nachrichten vom Anwendungsprozess entgegen, hängt die Felder der Quell- und Zielportnummern für den Multiplex/DemultiplexDienst und einige kleinere Felder an und leitet das daraus resultierende Segment an die Vermittlungsschicht weiter. Die Vermittlungsschicht verkapselt das Segment in einem IP-Datagramm und macht dann einen Best-Effort-Versuch, um das Segment an den empfangenden Host zu übertragen. Wenn das Segment beim empfangenden Host ankommt, benutzt UDP die Zielportnummer, um die Daten des Segments an den richtigen Anwendungsprozess zu übertragen. Man beachte, dass es bei UDP kein Handshake zwischen den Einheiten der sendenden und empfangenden Transportschicht gibt, bevor ein Segment gesendet wird. Aus diesem Grund gilt UDP als verbindungslos. 3.3 Verbindungsloser Transport: UDP DNS ist ein Beispiel für ein Protokoll der Anwendungsschicht, das UDP nutzt. Wenn die DNS-Anwendung auf einem Host eine Anfrage stellen will, konstruiert sie eine DNS-Anfragenachricht und leitet die Nachricht an ein UDP-Socket (siehe Abschnitt 2.7) weiter. Ohne ein Handshake durchzuführen, hängt UDP Header-Felder an die Nachricht an und leitet das resultierende Segment an die Vermittlungsschicht weiter. Die Vermittlungsschicht verkapselt das UDP-Segment in einem Datagramm und sendet das Datagramm an einen Name-Server. Die DNS-Anwendung auf dem anfragenden Host wartet dann auf eine Antwort. Erhält sie keine Antwort auf ihre Anfrage (möglicherweise, weil das zugrunde liegende Netzwerk die Anfrage oder die Antwort verloren hat), versucht sie entweder, die Anfrage an einen anderen Name-Server zu senden, oder sie informiert die anfragende Anwendung, dass sie keine Antwort bekommen kann. Man beachte, dass DNS laut DNS-Spezifikation über TCP statt UDP laufen kann; in der Praxis läuft DNS aber fast immer über UDP. Sie wundern sich jetzt vielleicht, warum ein Anwendungsentwickler überhaupt eine Anwendung über UDP statt über TCP aufbauen mag. Sollte man nicht TCP immer den Vorzug vor UDP geben, weil TCP im Gegensatz zu UDP einen zuverlässigen Datentransferdienst bereitstellt? Die Antwort lautet »Nein«, weil sich UDP für viele Anwendungen aus folgenden Gründen besser eignet: õ Kein Verbindungsaufbau: Wie wir an späterer Stelle noch ausführen, nutzt TCP ein Drei-Wege-Handshake, bevor es mit dem Datentransfer beginnt. UDP legt einfach ohne jegliche Einleitungsformalitäten los. Folglich führt UDP keine Verzögerung für den Aufbau einer Verbindung ein. Dies ist wahrscheinlich der Hauptgrund, warum DNS über UDP und nicht über TCP läuft. DNS wäre über TCP viel langsamer. HTTP nutzt TCP statt UDP, weil Zuverlässigkeit für Web-Seiten mit Text wichtig ist. Wie in Abschnitt 2.2 aber erwähnt wurde, ist ein Großteil des »World Wide Wait« in HTTP der Verzögerung zuzuschreiben, die TCP durch den Verbindungsaufbau einführt. õ Kein Verbindungszustand: TCP verwaltet in den Endsystemen einen Verbindungszustand. Dies beinhaltet Empfangs- und Sendepuffer, Parameter für die Überlastkontrolle sowie für die Sequenz- und Bestätigungsnummern. In Abschnitt 3.5 wird beschrieben, dass diese Zustandsinformationen notwendig sind, um den zuverlässigen Datentransferdienst von TCP zu implementieren und Überlastkontrolle bereitzustellen. UDP verwaltet demgegenüber keinen Verbindungszustand und keinen dieser Parameter. Aus diesem Grund kann ein Server, der sich einer bestimmten Anwendung widmet, normalerweise viel mehr aktive Clients unterstützen, wenn die Anwendung über UDP und nicht über TCP läuft. õ Geringer Overhead durch Paket-Header: Das TCP-Segment umfasst einen HeaderOverhead von 20 Byte im Gegensatz zu nur 8 Byte in UDP. õ Unregulierte Senderate: TCP verfügt über einen Überlastkontrollmechanismus, der den Sender drosselt, wenn eine oder mehrere Verbindungsleitungen zwischen Sender und Empfänger überlastet werden. Dieses Drosseln kann schwerwiegende Auswirkungen auf Echtzeitanwendungen haben, die einen gewissen Paketverlust tolerieren können, aber eine minimale Senderate voraussetzen. Demgegenüber ist die Geschwindigkeit, in der UDP Daten sendet, nur durch die Rate, in der die Anwendung Daten erzeugt, die Fähigkeiten der Quelle (CPU, Taktrate usw.) und die Zugangsbandbreite zum Internet beschränkt. Man beachte allerdings, dass der empfangende Host nicht unbedingt alle Daten empfängt. Wenn das Netzwerk überlastet ist, können einige Daten aufgrund eines Pufferüberlaufs im Router ver- 185 Kapitel 3 – Transportschicht 186 loren gehen. Folglich kann die Empfangsrate durch Netzwerküberlast begrenzt werden, auch wenn die Senderate nicht beschränkt ist. Abbildung 3.6 enthält eine Aufstellung beliebter Internet-Anwendungen und der von ihnen genutzten Transportprotokolle. Wie erwartet, laufen E-Mail, RemoteLogin, das Web und Filetransfer über TCP. Alle diese Anwendungen benötigen den zuverlässigen Datentransferdienst von TCP. Dennoch laufen viele wichtige Anwendungen über UDP und nicht über TCP. UDP wird für die Aktualisierung von RIPRouting-Tabellen (siehe Kapitel 4) benutzt, weil die Aktualisierungen periodisch (normalerweise alle fünf Minuten) gesendet werden, so dass verlorene Aktualisierungen durch neuere ersetzt werden. UDP kommt auch für das Netzwerkmanagement über SNMP (siehe Kapitel 8) zum Einsatz. UDP wird in diesem Fall gegenüber TCP bevorzugt, weil Netzwerkmanagementanwendungen oft laufen, wenn sich das Netzwerk in einem Stresszustand befindet, nämlich wenn zuverlässiger Datentransfer mit Überlastkontrolle kaum oder überhaupt nicht mehr möglich ist. Wie an früherer Stelle erwähnt, läuft auch DNS über UDP, um die durch den Verbindungsaufbau von TCP entstehenden Verzögerungen zu vermeiden. Abbildung 3.6 Beliebte Internet-Anwendungen mit dem jeweils zugrunde liegenden Transportprotokoll Anwendung Protokoll der Anwendungsschicht Zugrunde liegendes Transportprotokoll E-Mail SMTP TCP Remote-Login Telnet TCP Web HTTP TCP Filetransfer FTP TCP Remote File Server NFS überwiegend UDP Streaming Multimedia proprietär überwiegend UDP Internet-Telefonie proprietär überwiegend UDP Netzwerkmanagement SNMP überwiegend UDP Routing-Protokoll RIP überwiegend UDP Namensauflösung DNS überwiegend UDP Wir sehen in Abbildung 3.6, dass UDP heute auch vorwiegend für MultimediaAnwendungen, z. B. Internet-Phone, Echtzeit-Videokonferenzen und Audio-/VideoStreaming benutzt wird. Diese Anwendungen werden ausführlich in Kapitel 6 beschrieben. Vorläufig genügt es zu wissen, dass alle diese Anwendungen einen gewissen Umfang an Paketverlust tolerieren können, so dass zuverlässiger Datentransfer für den Erfolg der Anwendung nicht unbedingt entscheidend ist. Außerdem reagieren Echtzeitanwendungen wie Internet-Phone und Videokonferenzen sehr schlecht auf die Überlastkontrolle von TCP. Aus diesen Gründen wählen Entwickler von Multimedia-Anwendungen oft UDP statt TCP. Schließlich laufen auch MulticastAnwendungen über UDP, weil TCP mit Multicast nicht benutzt werden kann. 3.3 Verbindungsloser Transport: UDP Obwohl es heute üblich ist, Multimedia-Anwendungen über UDP auszuführen, wird dies doch auch recht kontrovers diskutiert, um es einmal vorsichtig auszudrücken. Wie weiter oben erwähnt wurde, hat UDP keine Überlastkontrolle. Überlastkontrolle ist aber notwendig, um zu verhindern, dass das Netzwerk in einen Zustand gerät, in dem kaum mehr nützliche Arbeit möglich ist. Wenn jeder mit dem Streaming von Video mit hoher Bitrate beginnen würde, ohne dass eine Überlastkontrolle angewandt wird, würde derart viel Paketüberlauf in den Routern entstehen, dass keiner mehr etwas zu sehen bekäme. Folglich ist der Mangel an Überlastkontrolle in UDP ein potenziell schwerwiegendes Problem [Floyd 1999]. Viele Wissenschaftler schlugen neue Mechanismen vor, um alle Quellen – auch UDP-Quellen – zur Durchführung einer adaptiven Überlastkontrolle zu zwingen [Mahdavi 1997; Floyd 2000]. Bevor wir die UDP-Segmentstruktur beschreiben, möchten wir darauf hinweisen, dass es für eine Anwendung auch unter UDP möglich ist, einen zuverlässigen Datentransfer zu erhalten. Dies lässt sich bewerkstellig, indem die Anwendung selbst mit Zuverlässigkeit ausgestattet wird (z. B. durch Hinzufügen von Bestätigungs- und Neuübertragungsmechanismen wie diejenigen, die im nächsten Abschnitt beschrieben werden). Dies ist allerdings kein leichtes Unterfangen und kann einen Anwendungsentwickler viele Stunden an Debugging kosten. Dennoch ermöglicht es die Einbeziehung von Zuverlässigkeit direkt in die Anwendung, dass diese ebenfalls »ihr Stückchen Kuchen erhält und essen kann«. Das heißt, Anwendungsprozesse können zuverlässig miteinander kommunizieren, ohne sich den Einschränkungen hinsichtlich der Übertragungsrate, die vom TCP-Überlastkontrollmechanismus auferlegt werden, unterwerfen zu müssen. Viele der heutigen proprietären Streaming-Anwendungen tun genau dies – sie laufen über UDP, haben aber eingebaute Bestätigungsund Neuübertragungsfunktionen, um Paketverlust zu reduzieren (siehe z. B. [Rhee 1998]). 3.3.1 UDP-Segmentstruktur Die UDP-Segmentstruktur (siehe Abbildung 3.7) ist in RFC 768 definiert. Die Anwendungsdaten belegen das Datenfeld des UDP-Datagramms. Für DNS enthält das Datenfeld z. B. entweder eine Anfrage- oder eine Antwortnachricht. Für eine Streaming-Audioanwendung ist das Datenfeld mit Audio-Samples gefüllt. Der UDP-Header hat nur vier aus je zwei Byte bestehende Felder. Wie im vorherigen Abschnitt erwähnt, kann der Zielhost anhand der Portnummern die Anwendungsdaten an den richtigen Prozess auf dem Zielhost weiterleiten (das ist die Demultiplexfunktion). Die Prüfsumme wird vom empfangenden Host benutzt, um zu prüfen, ob Fehler in das Segment eingeführt wurden. In Wahrheit wird die Prüfsumme auch über mehrere Felder des IP-Headers und nicht nur über das UDP-Segment berechnet. Wir ignorieren dieses Detail aber, um den Wald vor lauter Bäumen nicht aus den Augen zu verlieren. Die Prüfsummenberechnung ist Inhalt des nächsten Abschnitts, während grundlegende Prinzipien der Fehlererkennung in Abschnitt 5.1 beschrieben werden. Das Längenfeld (Length) spezifiziert die Länge des UDP-Segments, einschließlich des Headers, in Byte. 187 Kapitel 3 – Transportschicht 188 Abbildung 3.7 Die UDP-Segmentstruktur 32 Bit Portnummer Quelle Portnummer Ziel Länge Prüfsumme Anwendungsdaten (Nachricht) 3.3.2 UDP-Prüfsumme Die UDP-Prüfsumme dient der Fehlererkennung. Auf der Senderseite führt UDP das Einer-Komplement der Summe aller 16-Bit-Wörter im Segment durch. Dieses Ergebnis wird in das Prüfsummenfeld (Checksum) des UDP-Segments eingefügt. Wir geben hier ein einfaches Beispiel der Prüfsummenberechnung. Einzelheiten über die effiziente Implementierung der Berechnung findet der Leser in RFC 1071 und die Leistung bei Versendung echter Daten in [Stone 1998 und Stone 2000]. Als Beispiel gehen wir von den folgenden drei 16-Bit-Wörtern aus: 0110011001100110 0101010101010101 0000111100001111 Die Summe der ersten dieser 16-Bit-Wörter ist 0110011001100110 0101010101010101 1011101110111011 Wenn wir das dritte Wort zur obigen Summe addieren, erhalten wir 1011101110111011 0000111100001111 1100101011001010 Das Einer-Komplement wird ermittelt, indem man alle Nullen in Einsen und alle Einsen in Nullen konvertiert. Das Einer-Komplement der Summe 1100101011001010 ist somit 0011010100110101, was die Prüfsumme wird. Beim Empfänger werden alle vier 16-Bit-Wörter, einschließlich der Prüfsumme, addiert. Wenn sich keine Fehler in das Paket einschleichen, lautet die Summe beim Empfänger 1111111111111111. Ist eines der Bits eine Null, wissen wir, dass Fehler in das Paket eingeführt wurden. Sie wundern sich vielleicht, warum UDP überhaupt eine Prüfsumme bereitstellt, da viele Protokolle auf der Sicherungsschicht (darunter das beliebte Ethernet-Protokoll) ebenfalls Fehlerprüfung bieten. Der Grund ist, dass es keine Zusicherung gibt, 3.4 Prinzipien des zuverlässigen Datentransfers dass alle Verbindungsleitungen zwischen der Quelle und dem Ziel Fehlerprüfung bieten; eine Verbindungsleitung nutzt vielleicht ein Protokoll, das keine Fehlerprüfung bereitstellt. Da IP ja über praktisch jedes Schicht-2-Protokoll laufen soll, ist es sinnvoll, Fehlerprüfung als Sicherheitsmaßnahme auf der Transportschicht bereitzustellen. Obwohl UDP Fehlerprüfung bietet, unternimmt es nichts, um den Fehler zu beheben. Einige Implementierungen von UDP verwerfen das beschädigte Segment einfach, während andere es mit einer Warnung an die Anwendung weitergeben. Damit beenden wir unsere Beschreibung von UDP. Sie werden bald sehen, dass TCP seinen Anwendungen zuverlässigen Datentransfer und weitere Dienste bietet, die UDP nicht bietet. Natürlich ist TCP auch komplexer als UDP. Bevor wir mit der Beschreibung von TCP beginnen, ist es an dieser Stelle hilfreich, einen Schritt zurück zu machen und zuerst die grundlegenden Prinzipien von zuverlässigem Datentransfer zu beschreiben (siehe nächsten Abschnitt). In Abschnitt 3.5 beginnen wir mit den Grundlagen von TCP, die auf diesen Prinzipien basieren. 3.4 Prinzipien des zuverlässigen Datentransfers Dieser Abschnitt befasst sich mit dem zuverlässigen Datentransfer im Allgemeinen. Dies ist sinnvoll, weil das Problem der Implementierung eines zuverlässigen Datentransfers nicht nur auf der Transportschicht, sondern auch auf der Sicherungs- und Anwendungsschicht gelöst werden muss. Das allgemeine Problem ist folglich für Netzwerke von zentraler Bedeutung. Auf einer »Hitliste« der zehn wichtigsten Probleme im gesamten Vernetzungsbereich wäre dies einer der obersten Kandidaten. Im nächsten Abschnitt beschreiben wir TCP und zeigen insbesondere auf, dass TCP viele der hier beschriebenen Prinzipien nutzt. Abbildung 3.8 zeigt das Rahmenwerk für unsere Untersuchung des zuverlässigen Datentransfers. Die Dienstabstraktion, die den Einheiten der höheren Schichten bereitgestellt wird, ist ein zuverlässiger Kanal, durch den Daten übertragen werden können. Auf einem zuverlässigen Kanal werden keine Bits verstümmelt (von 0 auf 1, oder umgekehrt, umgedreht) oder verloren und alle kommen in der Reihenfolge an, in der sie gesendet wurden. Dies entspricht genau dem Dienstmodell, das TCP den nutzenden Internet-Anwendungen bietet. Es gehört zum Aufgabenbereich eines zuverlässigen Datentransferprotokolls, diese Dienstabstraktion zu implementieren. Diese Aufgabe wird durch die Tatsache erschwert, dass die Schicht unter dem zuverlässigen Datentransferprotokoll unzuverlässig sein kann. Beispielsweise ist TCP ein zuverlässiges Datentransferprotokoll, das auf einer unzuverlässigen (IP) Ende-zu-Ende-Netzwerkschicht aufsetzt. Allgemeiner ausgedrückt, kann die Schicht unter den beiden zuverlässig kommunizierenden Endpunkten aus einer einzigen physikalischen Verbindung (z. B. wie im Fall eines Datentransferprotokolls auf der Sicherungsschicht) oder aus einem globalen Internetwork (z. B. wie im Fall eines Protokolls der Transportschicht) bestehen. Für unsere Zwecke genügt es aber, diese untere Schicht einfach als unzuverlässigen Punkt-zu-PunktKanal zu betrachten. In diesem Abschnitt entwickeln wir stufenweise die Sender- und Empfängerseite eines zuverlässigen Datentransferprotokolls, wobei wir zunehmend komplexere Modelle des zugrunde liegenden Kanals betrachten. Abbildung 3.8 (b) zeigt die Schnittstellen des Datentransferprotokolls. Die Sendeseite des Datentransferprotokolls wird von oben durch einen Aufruf von rdt_send() aufgerufen. Dadurch werden die zu übertragenden Daten an die obere Schicht auf der Empfangsseite weiter- 189 Kapitel 3 – Transportschicht 190 Anwendungsschicht Abbildung 3.8 Dienstmodell und -implementierung des zuverlässigen Datentransfers Sendeprozess Daten Empfangsprozess Daten rdt_send() Transportschicht Zuverlässiger Kanal Daten Zuverlässiges Datentransferprot. (Sendeseite) udt_send() Paket Daten deliver data() Zuverlässiges Datentransferprot. (Empfangsseite) Paket rdt_rcv() Unzuverlässiger Kanal (a) Bereitgestellter Dienst (b) Dienstimplementierung gegeben. (Hier steht rdt für »zuverlässiges Datentransferprotokoll« und _send für die Sendeseite, die rdt aufruft. Der erste Schritt in der Entwicklung eines jeden Protokolls ist die Auswahl eines guten Namens!) Auf der Empfangsseite wird rdt_rcv() aufgerufen, wenn ein Paket von der empfangenden Seite des Kanals ankommt. Wenn das rdt-Protokoll Daten an die obere Schicht übertragen will, bewirkt es dies durch Aufruf von deliver_data(). Im Folgenden verwenden wir für die PDU den Begriff »Paket« statt »Segment«. Da die in diesem Abschnitt behandelte Theorie auf Computernetzwerke im Allgemeinen und nicht nur auf die Internet-Transportschicht zutrifft, halten wir den generischen Begriff »Paket« für besser geeignet. In diesem Abschnitt betrachten wir nur den Fall eines unidirektionalen Datentransfers, d. h. Datentransfer von der sendenden zur empfangenden Seite. Der Fall eines zuverlässigen bidirektionalen Datentransfers (d. h. Vollduplex) ist konzeptionell nicht schwieriger, aber viel umständlicher zu beschreiben. Obwohl wir uns nur mit dem unidirektionalen Datentransfer befassen, ist zu beachten, dass die sendende und die empfangende Seite des Protokolls dennoch Pakete in beiden Richtungen übertragen müssen, wie aus Abbildung 3.8 ersichtlich wird. Wir werden in Kürze sehen, dass zusätzlich zum Austausch von Paketen, die die zu übertragenden Daten enthalten, die sendende und empfangende Seite von rdt auch Steuerpakete in beide Richtungen austauschen müssen. Beide Seiten – Sender und Empfänger von rdt – senden Pakete an die jeweils andere Seite durch einen Aufruf von udt_send() (wobei udt für »unzuverlässiger Datentransfer« steht). 3.4 Prinzipien des zuverlässigen Datentransfers 3.4.1 Aufbau eines zuverlässigen Datentransferprotokolls Wir gehen im Folgenden eine Reihe von Protokollen durch, die schrittweise komplexer werden, und schließen mit der Beschreibung eines einwandfreien zuverlässigen Datentransferprotokolls. Zuverlässiger Datentransfer über einen absolut zuverlässigen Kanal: rdt1.0 Wir betrachten zuerst den einfachsten Fall, bei dem der zugrunde liegende Kanal absolut zuverlässig ist. Das Protokoll, das wir rdt1.0 nennen, ist sehr einfach. Die FSM-Definitionen (Finite-State Machine) für Sender und Empfänger von rdt1.0 sind in Abbildung 3.9 dargestellt. Die Sender- und Empfänger-FSMs in Abbildung 3.9 haben jeweils nur einen Zustand. Die Pfeile in der FSM-Beschreibung bezeichnen den Übergang des Protokolls von einem Zustand in einen anderen. (Da jede FSM in Abbildung 3.9 nur einen Zustand hat, erfolgt ein Übergang natürlich von einem Zustand zurück in denselben; im weiteren Verlauf verwenden wir komplexere Zustandsdiagramme.) Das Ereignis, das den Übergang auslöst, ist oberhalb der horizontalen Linie dargestellt, die den Übergang beschriftet, und die Aktion(en), die unternommen werden, wenn das Ereignis eintritt, stehen unter der horizontalen Linie. Abbildung 3.9 rdt1.0 – ein Protokoll für einen zuverlässigen Kanal Warte auf Aufruf von oben rdt_send(data) make_pkt(packet, data) udt_send(packet) (a) rdt1.0: Sendeseite Warte auf Aufruf von unten rdt_rcv(packet) extract(packet,data) deliver_data(data) (b) rdt1.0: Empfangsseite Die Sendeseite von rdt nimmt einfach Daten von der oberen Schicht über das Ereignis rdt_send(data) an, gibt die Daten (über die Aktion make_pkt packet,data)) in ein Paket und schickt das Paket in den Kanal. In der Praxis würde das Ereignis rdt_send(data) aus einem Prozeduraufruf (z. B. von rdt_send()) durch die höherschichtige Anwendung resultieren. Auf der Empfangsseite empfängt rdt über das Ereignis rdt_rcv(packet) ein Paket vom zugrunde liegenden Kanal, nimmt die Daten (über die Aktion extract(packet,data)) aus dem Paket und gibt sie an die obere Schicht weiter. In der Praxis würde das Ereignis rdt_rcv(packet) aus einem Prozeduraufruf (z. B. von rdt_rcv()) durch das niederschichtige Protokoll resultieren. Bei diesem einfachen Protokoll besteht kein Unterschied zwischen einer Datenund einer Paketeinheit. Außerdem fließen alle Pakete vom Sender zum Empfänger. Bei einem vollkommen zuverlässigen Kanal besteht auf der Empfängerseite keine Notwendigkeit, irgendeine Rückmeldung (Feedback) an den Sender zu schicken, da nichts schief gehen kann! Darüber hinaus sind wir davon ausgegangen, dass der Empfänger in der Lage ist, die Daten so schnell zu empfangen wie sie der Sender sen- 191 192 Kapitel 3 – Transportschicht det. Folglich besteht für den Empfänger keine Notwendigkeit, den Sender zum »Verlangsamen« aufzufordern. Zuverlässiger Datentransfer über einen Kanal mit Bitfehlern: rdt2.0 Bei einem realistischeren Modell des zugrunde liegenden Kanals können Bits in einem Paket beschädigt werden. Solche Bitfehler entstehen normalerweise in den physikalischen Komponenten eines Netzwerks, während ein Paket übertragen oder zwischengespeichert wird. Wir wollen vorläufig wieder davon ausgehen, dass alle übertragenen Pakete in der richtigen Reihenfolge ankommen (obwohl einige Bits beschädigt sein können). Bevor wir ein Protokoll für die zuverlässige Kommunikation über einen solchen Kanal entwickeln, untersuchen wir zuerst, wie sich Menschen in einer solchen Situation verhalten würden. Angenommen, Sie diktieren eine lange Nachricht am Telefon. In einem typischen Szenario sagt die Person am anderen Ende vielleicht nach jedem Satz, den sie gehört, verstanden und aufgezeichnet hat, »OK«. Wenn die andere Person einen Satz nicht richtig versteht, werden Sie gebeten, ihn zu wiederholen. Dieses Nachrichtendiktierprotokoll benutzt sowohl positive Bestätigungen (»OK«) als auch negative Bestätigungen (»Bitte wiederholen Sie das.«) Diese Steuernachrichten ermöglichen es dem Empfänger, den Sender darüber zu informieren, was korrekt und was fehlerhaft empfangen wurde. In einem Computernetzwerk basieren zuverlässige Datentransferprotokolle auf solchen Neuübertragungen, die man als ARQ-Protokolle (Automatic Repeat reQuest) bezeichnet. Grundsätzlich sind in ARQ-Protokollen drei weitere Protokollfähigkeiten erforderlich, um vorhandene Bitfehler zu behandeln: õ Fehlererkennung: Zunächst bedarf es eines Mechanismus, damit der Empfänger Bitfehler erkennen kann. Wir wissen aus dem vorherigen Abschnitt, dass UDP das Internet-Prüfsummenfeld genau für diesen Zweck benutzt. In Kapitel 5 werden Fehlererkennungs- und -korrekturtechniken ausführlicher beschrieben. Diese Techniken ermöglichen es dem Empfänger, Paketbitfehler zu erkennen und möglicherweise zu korrigieren. Vorläufig genügt es zu wissen, dass für diese Techniken zusätzliche Bits (abgesehen von den zu übertragenden Originaldatenbits) vom Sender zum Empfänger gesendet werden müssen. Diese Bits werden im Paketprüfsummenfeld des rdt2.0-Datenpakets erfasst. õ Empfänger-Feedback: Da Sender und Empfänger normalerweise auf unterschiedlichen Endsystemen ausgeführt werden, die möglicherweise Tausende von Kilometern auseinander liegen, erhält der Sender über die Lage auf der Empfängerseite (in diesem Fall, ob ein Paket korrekt empfangen wurde) nur dadurch Informationen, dass der Empfänger dem Sender ausdrücklich Rückmeldung macht. Die positive (ACK) und negative (NAK) Bestätigung in dem Nachrichtendiktierszenario sind Beispiele eines solchen Feedbacks. Das rdt2.0-Protokoll wird ebenfalls ACK- und NAK-Pakete vom Empfänger zum Sender zurücksenden. Im Prinzip brauchen diese Pakete nur ein Bit lang zu sein; beispielsweise könnte ein 0-Wert ein NAK und ein 1-Wert ein ACK bedeuten. õ Neuübertragung: Ein Paket, das fehlerhaft beim Empfänger ankommt, wird vom Sender erneut übertragen. Abbildung 3.10 zeigt die FSM-Darstellung von rdt2.0, einem Datentransferprotokoll mit Fehlererkennung sowie positiven und negativen Bestätigungen. 3.4 Prinzipien des zuverlässigen Datentransfers Abbildung 3.10 rdt2.0 – ein Protokoll für einen Kanal mit Bitfehlern rdt_send(data) compute checksum make_pkt(sndpkt, data, checksum) udt_send(sndpkt) Warte auf Aufruf von oben Warte auf ACK oder NAK rdt_rcv(rcvpkt) && isNAK(rcvpkt) udt_send(sndpkt) rdt_rcv(rcvpkt) && isACK(rcvpkt) (a) rdt2.0: Sendeseite rdt_rcv(rcvpkt) && corrupt(rcvpkt) udt_send(NAK) Warte auf Aufruf von unten rdt_rcv(rcvpkt) && notcorrupt(rcvpkt) extract(rcvpkt,data) deliver_data(data) udt_send(ACK) (b) rdt2.0: Empfangsseite Die Sendeseite von rdt2.0 hat zwei Zustände. In einem Zustand wartet das Protokoll auf der Sendeseite auf Daten, die von der höheren Schicht nach unten weitergereicht werden. Im zweiten Zustand wartet das Senderprotokoll auf ein ACK- oder NAK-Paket vom Empfänger. Wenn ein ACK-Paket ankommt (rdt_rcv(rcvpkt) && isACK (rcvpkt) in Abbildung 3.10 entspricht diesem Ereignis), weiß der Sender, dass das zuletzt übertragene Paket korrekt empfangen wurde. Folglich kehrt das Protokoll in den Zustand des Wartens auf Daten von der höheren Schicht zurück. Kommt ein NAK an, überträgt das Protokoll das letzte Paket erneut und wartet auf ein ACK oder NAK vom Empfänger als Reaktion auf das erneut übertragene Datenpaket. Wichtig ist hier, dass der Empfänger, wenn er sich im Wartezustand auf ein ACK oder NAK befindet, keine weiteren Daten von der höheren Schicht empfangen 193 194 Kapitel 3 – Transportschicht kann; dies ist erst wieder möglich, nachdem der Sender ein ACK empfangen hat und diesen Zustand verlässt. Folglich überträgt der Sender so lange keine neuen Daten, bis er sicher ist, dass der Empfänger das aktuelle Paket korrekt empfangen hat. Aufgrund dieses Verhaltens bezeichnet man Protokolle wie unser rdt2.0 als Stop-andWait-Protokolle. Die empfängerseitige FSM für rdt2.0 hat wiederum nur einen einzigen Zustand. Bei Ankunft eines Pakets antwortet der Empfänger entweder mit einem ACK oder einem NAK, je nachdem, ob das empfangene Paket beschädigt ist. In Abbildung 3.10 entspricht die Notation rdt_rcv(rcvpkt) && corrupt(rcvpkt) dem Ereignis, bei dem ein Paket fehlerhaft angekommen ist. Das Protokoll rdt2.0 scheint vielleicht zu funktionieren; leider hat es aber einen fatalen Fehler. Wir haben nicht die Möglichkeit berücksichtigt, dass auch das ACKoder NAK-Paket beschädigt werden kann! (Bevor wir fortfahren, sollten Sie sich überlegen, wie man dieses Problem beheben kann.) Leider ist dies nicht so harmlos, wie es vielleicht scheint. Als Minimum müssen wir die ACK/NAK-Pakete um Prüfsummenbits erweitern, um solche Fehler erkennen zu können. Die schwierigere Frage betrifft die Wiederherstellung des Protokolls nach Fehlern in ACK- oder NAKPaketen. Die Schwierigkeit ist hier, dass der Sender, wenn ein ACK oder NAK beschädigt ist, nicht wissen kann, ob der Empfänger die zuletzt übertragenen Daten korrekt empfangen hat. Wir betrachten die folgenden drei Möglichkeiten für die Behandlung beschädigter ACKs oder NAKs: õ Für die erste Variante betrachte man, was ein Mensch in dem Nachrichtendiktierszenario tun würde. Hat der Sprecher die Antwort »OK« oder »Bitte wiederholen Sie das« des Empfängers nicht verstanden, so fragt er vielleicht »Was haben Sie gesagt?« (womit ein neuer Typ von Sender-an-Empfänger-Paket in unser Protokoll eingeführt wird). Der Empfänger würde dann die Antwort wiederholen. Was aber, wenn das »Was haben Sie gesagt?« des Sprechers beschädigt wird? Da der Empfänger keine Ahnung hat, ob der verstümmelte Satz Teil des Diktats oder einer Bitte um Wiederholung der letzten Antwort ist, würde er wahrscheinlich mit »Was haben Sie gesagt?« antworten. Dann könnte natürlich auch diese Antwort beschädigt werden. Sie sehen, wir haben es hier mit einer kniffligen Angelegenheit zu tun. õ Bei der zweiten Möglichkeit werden ausreichend Prüfsummenbits hinzugefügt, damit der Sender Bitfehler nicht nur erkennen, sondern auch die Fehlersituation beheben kann. Dies löst das unmittelbare Problem eines Kanals, der Pakete zwar beschädigen, aber nicht verlieren kann. õ Bei der dritten Möglichkeit überträgt der Sender das aktuelle Datenpaket einfach noch einmal, wenn er ein beschädigtes ACK- oder NAK-Paket empfängt. Diese Möglichkeit führt aber zu Duplikatpaketen auf dem Kanal vom Sender zum Empfänger. Die grundlegende Schwierigkeit bei Duplikatpaketen ist, dass der Empfänger nicht weiß, ob das zuletzt gesendete ACK oder NAK vom Sender korrekt empfangen wurde. Somit kann er grundsätzlich nicht wissen, ob ein ankommendes Paket neue Daten enthält oder eine Neuübertragung darstellt! Als einfache Lösung für dieses neue Problem (die in fast allen existierenden Datentransferprotokollen, auch TCP, angewandt wird) können wir ein neues Feld zum Datenpaket hinzufügen und den Sender seine Datenpakete nummerieren lassen. Hierfür setzt er eine Sequenznummer in dieses neue Feld. Der Empfänger muss 3.4 Prinzipien des zuverlässigen Datentransfers dann nur diese Sequenznummer ansehen, um festzustellen, ob es sich bei dem empfangenen Paket um eine Neuübertragung handelt. Für diesen einfachen Fall eines Stop-and-Wait-Protokolls genügt eine 1-Bit-Sequenznummer, da der Empfänger dadurch feststellen kann, ob der Sender das zuvor übertragene Paket (das empfangene Paket hat die gleiche Sequenznummer wie das zuletzt angekommene Paket) oder ein neues Paket (die Sequenznummer ändert sich; sie wird in der Modulus-2Arithmetik »nach vorn« geschoben) sendet. Da wir im Moment davon ausgehen, dass der Kanal keine Pakete verliert, müssen ACK- und NAK-Pakete selbst die Sequenznummer des Pakets, das sie bestätigen, nicht angeben. Der Sender weiß, dass ein empfangenes ACK- oder NAK-Paket (beschädigt oder nicht) als Reaktion auf sein zuletzt übertragenes Datenpaket erzeugt wurde. Die Abbildungen 3.11 und 3.12 zeigen die FSM-Beschreibung für rdt2.1, unsere korrigierte Version von rdt2.0. In rdt2.1 haben die Sender- und Empfänger-FSM jeweils doppelt so viele Zustände wie vorher. Der Grund dafür ist, dass der Protokollzustand nun widerspiegeln muss, ob das momentan (vom Sender) übertragene oder (vom Empfänger) erwartete Paket die Folgenummer 0 oder 1 haben muss. Man beachte, dass die Aktionen in den Zuständen, in denen ein mit 0 nummeriertes Paket gesendet oder erwartet wird, Spiegelbilder derjenigen sind, in denen ein mit 1 nummeriertes Paket gesendet oder erwartet wird. Der einzige Unterschied ist die Behandlung der Sequenznummer. Abbildung 3.11 Der Sender von rdt2.1 rdt_send(data) compute chksum make_pkt(sndpkt,0,data,chksum) udt_send(sndpkt) Warte auf Aufruf 0 von oben Warte auf ACK oder NAK 0 rdt_rcv(rcvpkt) && notcorrupt(rcvpkt) && isACK(rcvpkt) Warte auf ACK oder NAK 1 rdt_rcv(rcvpkt) && ( corrupt(rcvpkt) || isNAK(rcvpkt)) udt_send(sndpkt) rdt_rcv(rcvpkt) && notcorrupt(rcvpkt) && isACK(rcvpkt) Warte auf Aufruf 1 von oben rdt_rcv(rcvpkt) && (corrupt(rcvpkt)|| isNAK(rcvpkt)) rdt_send(data) udt_send(sndpkt) compute chksum make_pkt(sndpkt,1,data,chksum) udt_send(snkpkt) 195 Kapitel 3 – Transportschicht 196 Abbildung 3.12 Der Empfänger von rdt2.1 rdt_rcv(rcvpkt) && notcorrupt(rcvpkt) && has_seq0(rcvpkt) rdt_rcv(rcvpkt) && corrupt(rcvpkt) extract(rcvpkt,data) deliver_data(data) compute chksum make_pkt(sendpkt,ACK,chksum) udt_send(sndpkt) rdt_rcv(rcvpkt) && corrupt(rcvpkt) compute chksum make_pkt(sndpkt,NAK,chksum) udt_send(sndpkt) Warte auf 0 von unten rdt_rcv(rcvpkt) && notcorrupt(rcvpkt) && has_seq1(rcvpkt) compute chksum make_pkt(sndpkt,NAK,chksum) udt_send(sndpkt) Warte auf 1 von unten rdt_rcv(rcvpkt) && notcorrupt(rcvpkt) && has_seq0(rcvpkt) rdt_rcv(rcvpkt) compute chksum compute chksum make_pkt(sndpkt,ACK, && notcorrupt(rcvpkt) make_pkt(sndpkt,ACK, chksum) && has_seq1(rcvpkt) chksum) udt_send(sndpkt) udt_send(sndpkt) extract(rcvpkt,data) deliver_data(data) compute chksum make_pkt(sendpkt,ACK,chksum) udt_send(sndpkt) Das Protokoll rdt2.1 benutzt positive (ACK) und negative (NAK) Bestätigungen vom Empfänger zum Sender. Wenn ein Paket außer der Reihe ankommt, sendet der Empfänger eine positive Bestätigung für das empfangene Paket. Wenn ein beschädigtes Paket ankommt, sendet der Empfänger eine negative Bestätigung. Wir können die gleiche Wirkung wie ein NAK erzielen, wenn wir statt eines NAK für das zuletzt korrekt empfangene Paket ein ACK senden. Ein Sender, der zwei ACKs für das gleiche Paket (d. h. Duplikat-ACKs) empfängt, weiß, dass der Empfänger das Paket nach demjenigen, das zweimal bestätigt wird, nicht korrekt empfangen hat. Viele TCPImplementierungen nutzen den Empfang so genannter »dreifacher Duplikat-ACKs« (drei ACK-Pakete, die alle das gleiche, bereits bestätigte Paket bestätigen), um den Sender zu einer Neuübertragung zu veranlassen. Unser NAK-freies zuverlässiges Datentransferprotokoll für einen Kanal mit Bitfehlern ist rdt2.2 (siehe Abbildungen 3.13 und 3.14). Zuverlässiger Datentransfer über einen verlustbehafteten Kanal mit Bitfehlern: rdt3.0 Wir nehmen jetzt an, dass zusätzlich zu beschädigten Bits im zugrunde liegenden Kanal auch Pakete verloren gehen können, was in den heutigen Computernetzwerken (auch im Internet) recht häufig vorkommt. Das Protokoll muss nun zwei zusätzliche Fragen berücksichtigen: Wie ein Paketverlust erkannt wird und was unternommen werden kann, falls es zu einem solchen Verlust kommt. Die Verwendung von Prüfsummen, Sequenznummern, ACK-Paketen und Neuübertragungen – die in rdt2.2 bereits entwickelten Techniken – ermöglichen es uns, die zweite Frage zu klären. Die Behandlung des ersten Problembereichs setzt einen neuen Protokollmechanismus voraus. 3.4 Prinzipien des zuverlässigen Datentransfers Abbildung 3.13 Der Sender von rdt2.2 rdt_send(data) compute chksum make_pkt(sndpkt,0,data,chksum) udt_send(sndpkt) rdt_rcv(rcvpkt) && notcorrupt(rcvpkt) && isACK(rcvpkt,1) Warte auf Aufruf von oben rdt_rcv(rcvpkt) && (corrupt(rcvpkt) || isACK(rcvpkt,0)) rdt_rcv(rcvpkt) && (corrupt(rcvpkt) || isACK(rcvpkt,1)) Warte auf ACK0 Warte auf Aufruf von oben Warte auf ACK1 udt_send(sndpkt) rdt_rcv(rcvpkt) && notcorrupt(rcvpkt) && isACK(rcvpkt,0) udt_send(sndpkt) rdt_send(data) compute chksum make_pkt(sndpkt,1,data,chksum) udt_send(sndpkt) Abbildung 3.14 Der Empfänger von rdt2.2 di rdt_rcv(rcvpkt) && notcorrupt(rcvpkt) && has_seq0(recvpkt) extract(rcvpkt,data) deliver_data(data) compute chksum make_pkt(sendpkt,ACK0,chksum) udt_send(sndpkt) rdt_rcv(rcvpkt) && (corrupt(rcvpkt) || has_seq1(rcvpkt)) udt_send(sndpkt) Warte auf 0 von unten Warte auf 1 von unten rdt_rcv(rcvpkt) && (corrupt(rcvpkt) || has_seq0(rcvpkt)) udt_send(sndpkt) rdt_rcv(rcvpkt) && notcorrupt(rcvpkt) && has_seq1(recvpkt) extract(rcvpkt,data) deliver_data(data) compute chksum make_pkt(sendpkt,ACK1,chksum) udt_send(sndpkt) 197 198 Kapitel 3 – Transportschicht Für die Behandlung von Paketverlusten existieren viele mögliche Ansätze (mehrere weitere werden in den Übungen am Ende dieses Kapitels behandelt). Hier belasten wir den Sender mit der Aufgabe der Erkennung von verlorenen Paketen und der Wiederherstellung nach einem solchen Ereignis. Angenommen, der Sender überträgt ein Datenpaket und entweder dieses Paket oder das ACK des Empfängers für dieses Paket geht verloren. In beiden Fällen erhält der Sender keine Antwort vom Empfänger. Ist der Sender bereit, so lange zu warten, bis er sicher sein kann, dass das Paket verloren gegangen ist, kann er anschließend das Datenpaket einfach erneut übertragen. Sie sollten sich selbst davon überzeugen, dass dieses Protokoll tatsächlich funktioniert. Doch wie lange muss der Sender warten, um sicher zu sein, dass etwas verloren gegangen ist? Natürlich muss er mindestens so lange warten, bis eine Roundtrip-Verzögerung zwischen Sender und Empfänger verstrichen ist (was die Zwischenspeicherung in Routern oder Gateways auf der Strecke beinhalten kann), zuzüglich der für die Verarbeitung eines Pakets beim Empfänger erforderlichen Zeit. In vielen Netzwerken lässt sich diese maximale Worst-Case-Verzögerung schwer schätzen, ganz zu schweigen von Gewissheit. Außerdem sollte das Protokoll einen Paketverlust so schnell wie möglich beheben. Das Warten auf eine Worst-Case-Verzögerung könnte eine lange Zeit bedeuten, bis ein Fehler-Recovery eingeleitet wird. Deshalb wird in der Praxis meist der Ansatz angewandt, dass der Sender einen Zeitwert »mit Bedacht« auswählt, ob ein Paketverlust wahrscheinlich, aber nicht mit Sicherheit eingetreten ist. Wenn innerhalb dieser Zeit kein ACK ankommt, wird das Paket erneut übertragen. Wenn sich bei einem Paket eine besonders große Verzögerung ergibt, kann der Sender das Paket erneut übertragen, auch wenn weder das Datenpaket noch sein ACK verloren gegangen ist. Dies führt die Möglichkeit von Duplikatpaketen in dem Kanal zwischen Sender und Empfänger ein. Zum Glück verfügt das Protokoll rdt2.2 bereits über ausreichend Funktionalität (d. h. Sequenznummern), um den Fall von Duplikatpaketen behandeln zu können. Aus Sicht des Senders ist die Neuübertragung ein Allheilmittel. Der Sender weiß nicht, ob ein Datenpaket oder ein ACK verloren gegangen ist oder sich einfach übermäßig verzögert hat. In allen Fällen erfolgt die gleiche Aktion: Neuübertragung. Um einen auf Zeit basierten Neuübertragungsmechanismus zu implementieren, ist ein Countdown-Timer erforderlich, der den Sender nach Ablauf der Frist unterbrechen kann. Der Sender muss folglich in der Lage sein, (1) den Timer jedes Mal, wenn ein Paket (erstmalig oder erneut übertragenes Paket) gesendet wird, zu starten, (2) auf ein Timer-Interrupt (durch Einleitung entsprechender Aktionen) zu reagieren und (3) den Timer zu stoppen. Der Verlust von sendererzeugten Duplikatpaketen und Daten- oder ACK-Paketen macht auch die Verarbeitung der vom Sender empfangenen ACK-Pakete komplizierter. Wie kann der Sender bei Ankunft eines ACK wissen, ob es vom Empfänger als Reaktion auf sein eigenes zuletzt übertragenes Paket gesendet wurde oder ob es sich um ein verzögertes ACK handelt, das als Reaktion auf eine frühere Übertragung eines anderen Datenpakets gesendet wurde? Als Ausweg aus diesem Dilemma wird das ACK-Paket um ein Bestätigungsfeld (ACK-Feld) erweitert. Wenn der Empfänger ein ACK erzeugt, kopiert er die Sequenznummer des zu bestätigenden Datenpakets in dieses Bestätigungsfeld. Durch Prüfung des Inhalts dieses Bestätigungsfelds kann der Sender die Sequenznummer des Pakets ermitteln, das tatsächlich bestätigt wird. Abbildung 3.15 zeigt die Sender-FSM für rdt3.0. Dieses Protokoll überträgt Daten zuverlässig auf einem Kanal, der potenziell Pakete beschädigen oder verlieren kann. 3.4 Prinzipien des zuverlässigen Datentransfers Abbildung 3.16 zeigt, wie das Protokoll arbeitet, wenn keine Pakete verloren gegangen sind oder verzögert wurden, und wie verlorene Datenpakete behandelt werden. In Abbildung 3.16 erfolgt der Zeitverlauf von oben nach unten im Diagramm. Man beachte, dass die Empfangszeit eines Pakets aufgrund von Übertragungs- und Ausbreitungsverzögerungen notwendigerweise später als die Sendezeit für ein Paket ist. In Abbildung 3.16 (a) bis (d) bedeuten die Klammern auf der Sendeseite die Zeiten, zu denen ein Timer gesetzt wird und später abläuft. In den Übungen am Ende dieses Kapitels werden mehrere weitere Aspekte dieses Protokolls behandelt. Da die Sequenznummern von Paketen zwischen 0 und 1 wechseln, wird ein Protokoll wie rdt3.0 auch als Alternating-Bit-Protokoll bezeichnet. Abbildung 3.15 Der Sender von rdt3.0 rdt_send(data) compute chksum make_pkt(sndpkt,0,data,chksum) udt_send(sndpkt) start_timer rdt_rcv(rcvpkt) && (corrupt(rcvpkt) || isACK(rcvpkt,1)) rdt_rcv(rcvpkt) Warte auf Aufruf von oben 0 Warte auf ACK0 timeout udt_send(sndpkt) start_timer rdt_rcv(rcvpkt) && notcorrupt(rcvpkt) && isACK(rcvpkt,1) timeout udt_send(sndpkt) start_timer rdt_rcv(rcvpkt) && (corrupt(rcvpkt) || isACK(rcvpkt,0)) Warte auf ACK1 rdt_rcv(rcvpkt) && notcorrupt(rcvpkt) && isACK(rcvpkt,0) Warte auf Aufruf von oben 1 rdt_rcv(rcvpkt) rdt_send(data) compute chksum make_pkt(sndpkt,1,data,chksum) udt_send(sndpkt) start_timer Die wichtigsten Elemente eines Datentransferprotokolls sind nun zusammengestellt. Prüfsummen, Sequenznummern, Timer sowie positive und negative Bestätigungspakete spielen eine wichtige Rolle in der Operation des Protokolls. Wir haben jetzt ein funktionierendes zuverlässiges Datentransferprotokoll! 199 Kapitel 3 – Transportschicht 200 Abbildung 3.16 Operation von rdt3.0, dem Alternating-Bit-Protokoll Sender Empfänger send pkt0 rcv pkt0 send ACK0 ACK0 pkt1 ACK1 rcv ACK1 send pkt0 pkt0 ACK0 Empfänger pkt0 send pkt0 pkt0 rcv ACK0 send pkt1 Sender rcv pkt1 send ACK1 rcv ACK0 send pkt1 timeout resend pkt1 rcv pkt0 send ACK0 rcv ACK1 send pkt0 rcv pkt0 send ACK0 ACK0 pkt1 X (verloren) pkt1 rcv pkt1 send ACK1 ACK1 pkt0 rcv pkt0 send ACK0 ACK0 (b) Verlorenes Paket (a) Betrieb ohne Verlust Sender Empfänger send pkt0 pkt0 pkt1 ACK1 (verloren) X pkt1 ACK1 rcv ACK1 send pkt0 pkt0 ACK0 Empfänger pkt0 send pkt0 rcv pkt0 send ACK0 ACK0 rcv ACK0 send pkt1 timeout resend pkt1 Sender rcv pkt1 send ACK1 rcv pkt1 (detect duplicate) send ACK1 rcv pkt0 send ACK0 ACK0 rcv ACK0 send pkt1 timeout resend pkt1 rcv ACK1 send pkt0 pkt1 rcv pkt1 send ACK1 pkt1 rcv pkt 1 (detect ACK1 duplicate) pkt0 send ACK1 rcv pkt0 ACK0 send ACK0 ACK1 rcv pkt0 send ACK0 (c) Verlorenes ACK (d) Frühzeitiges Timeout 3.4.2 Zuverlässige Datentransferprotokolle mit Pipelining Das Protokoll rdt3.0 ist funktionell ein korrektes Protokoll, dürfte aber hinsichtlich seiner Leistung kaum jemanden zufrieden stellen, insbesondere in den heutigen Hochgeschwindigkeitsnetzen. Das Leistungsproblem von rdt3.0 basiert vor allem auf der Tatsache, dass es ein Stop-and-Wait-Protokoll ist. Um die Leistungsauswirkung dieses Stop-and-Wait-Verhaltens richtig einschätzen zu können, betrachten wir einen idealisierten Fall mit zwei Endhosts, von denen sich einer an der Westküste und der andere an der Ostküste der USA befindet. Die Ausbreitungsverzögerung in Lichtgeschwindigkeit, Tprop, zwischen diesen beiden Endsystemen beträgt ungefähr 15 Millisekunden. Es wird angenommen, dass sie über einen Kanal mit einer Kapazität C von 1 Gigabit (109 Bit) pro Sekunde verbunden sind. Bei einer Paketgröße SP von 1 KByte pro Paket, einschließlich der Header-Felder und Daten, ergibt sich die erforderliche Zeit, um das Paket tatsächlich über die 1-Gbps-Verbindungsleitung zu übertragen, wie folgt: 3.4 Prinzipien des zuverlässigen Datentransfers 8KBit/Paket SP - = 8 Mikrosekunden Ttrans = ------- = ------------------------------------9 C 10 Bit/s Wenn der Sender bei unserem Stop-and-Wait-Protokoll mit dem Senden des Pakets bei t = 0 beginnt, tritt das letzte Bit bei t = 8 Mikrosekunden auf der Senderseite in den Kanal ein. Das Paket legt dann seine 15-ms-Reise durch das Land zurück, wie in Abbildung 3.17 (a) dargestellt ist, wobei das letzte Bit des Pakets bei t = 15,008 ms beim Empfänger auftaucht. Geht man der Einfachheit halber davon aus, dass die ACK-Pakete die gleiche Größe wie die Datenpakete haben und der Empfänger mit dem Senden eines ACK-Pakets beginnen kann, sobald das letzte Bit eines Datenpakets empfangen wurde, taucht das letzte Bit des ACK-Pakets bei t = 30,016 ms wieder beim Sender auf. Über 30,016 ms hinweg war der Sender also tatsächlich nur 0,016 ms (mit dem Senden oder Empfangen) beschäftigt. Wenn wir die Auslastung (Utilization) des Senders (oder Kanals) als die Zeit definieren, in der der Sender tatsächlich mit dem Senden von Bits über den Kanal beschäftigt ist, erhalten wir eine eher magere Senderauslastung USender von 0, 008 USender = ---------------- = 0,00015 30,016 Das heißt, dass der Sender nur 2,7 Hundertstel eines Prozents der Zeit beschäftigt war. Aus anderer Sicht betrachtet, konnte der Sender in 30,016 Millisekunden nur 1 Kilobyte senden, was einem effektiven Durchsatz von nur 33 Kilobyte pro Sekunde entspricht, obwohl eine Verbindungsleitung mit 1 Gigabit pro Sekunde verfügbar war! Man stelle sich den unglücklichen Netzwerkmanager vor, der gerade ein Vermögen für eine Verbindungsleitung mit Gigabit-Kapazität ausgegeben hat und einen Durchsatz von gerade einmal 33 Kilobyte pro Sekunde erhält! Dies ist ein anschauliches Beispiel dessen, wie Netzwerkprotokolle die von der zugrunde liegenden Netzwerkhardware zur Verfügung gestellten Kapazitäten einschränken können. Außerdem haben wir die Verarbeitungszeiten der niederschichtigen Protokolle beim Sender und Empfänger sowie die Verarbeitungs- und Warteschlangenverzögerungen, die bei dazwischen liegenden Routern entstehen, nicht berücksichtigt. Bezieht man diese Verzögerungen noch mit ein, würde sich das ohnehin schon schlechte Leistungsergebnis weiter verschlechtern. Abbildung 3.17 Gegenüberstellung eines Stop-and-Wait- und eines Pipelining-Protokolls Datenpaket Datenpakete ACK-Pakete (a) Operation eines Stop-and-Wait-Protokolls (b) Operation eines Protokolls mit Pipelining Für dieses spezifische Leistungsproblem gibt es eine einfache Lösung: Statt im Stopand-Wait-Betrieb zu arbeiten, lässt man den Sender mehrere Pakete senden, ohne 201 Kapitel 3 – Transportschicht 202 dass er auf Bestätigungen warten muss; dieses Szenario ist in Abbildung 3.17 (b) dargestellt. Da die vielen zwischen dem Sender und dem Empfänger im Transit befindlichen Pakete als »Füllen einer Pipeline« betrachtet werden können, wird diese Technik als Pipelining bezeichnet. Durch Pipelining ergeben sich für zuverlässige Datentransferprotokolle mehrere Konsequenzen: õ Der Bereich der Sequenznummern muss vergrößert werden, weil jedes im Transit befindliche Paket (ohne Neuübertragungen mitzuzählen) eine eindeutige Sequenznummer haben muss und mehrere unbestätigte im Transit befindliche Pakete möglich sein müssen. õ Die Sender- und die Empfängerseite des Protokolls benötigen einen Puffer für mehr als ein Paket. Als Minimum muss der Sender die bereits übertragenen, aber noch nicht bestätigten Pakete zwischenspeichern. Außerdem müssen möglicherweise korrekt empfangene Pakete beim Empfänger zwischengespeichert werden (siehe Beschreibung weiter unten). Der Bereich der benötigten Sequenznummern und die Pufferanforderungen werden von der Art abhängen, in der ein Datentransferprotokoll auf verlorene, beschädigte und übermäßig verzögerte Pakete reagiert. Für die Wiederherstellung nach Fehlern mit der Pipelining-Technik sind zwei grundlegende Ansätze bekannt: Go-Back-N und Selective Repeat. 3.4.3 Go-Back-N (GBN) Bei einem GBN-Protokoll (Go-Back-N) ist es dem Sender gestattet, mehrere Pakete (sofern vorhanden) zu übertragen, ohne auf eine Bestätigung warten zu müssen. Insgesamt darf er aber nicht mehr als eine bestimmte Höchstzahl, N, an unbestätigten Abbildung 3.18 Sequenznummern im Go-Back-N aus Sicht des Senders base nextseqnum Fenstergröße N Bereits bestätigt Verwendbar, noch nicht gesendet Gesendet, noch nicht bestätigt Nicht verwendbar 3.4 Prinzipien des zuverlässigen Datentransfers Paketen in die Pipeline geben. Abbildung 3.18 zeigt den Bereich der Sequenznummern in einem GBN-Protokoll aus Sicht des Senders. Wenn wir base als die Sequenznummer des ältesten unbestätigten Pakets und nextseqnum als die kleinste nicht benutzte Sequenznummer (d. h. die Sequenznummer des als Nächstes zu sendenden Pakets) definieren, dann erhalten wir vier Intervalle in dem Sequenznummernbereich. Das Intervall [0,base-1] entspricht Paketen, die bereits übertragen und bestätigt wurden. Das Intervall [base,nextseqnum-1] entspricht Paketen, die bereits gesendet, aber noch nicht bestätigt wurden. Sequenznummern im Intervall [nextseqnum, base+N-1] werden für Pakete benutzt, die sofort gesendet werden können, wenn Daten von der höheren Schicht ankommen. Schließlich können die Sequenznummern, die größer als oder gleich base+N sind, so lange nicht benutzt werden, bis ein momentan in der Pipeline befindliches, nicht bestätigtes Paket bestätigt wurde. Wie aus Abbildung 3.18 deutlich wird, kann der Bereich der zulässigen Sequenznummern für übertragene, aber noch nicht bestätigte Pakete als »Fenster« mit Größe N über den Sequenznummernbereich betrachtet werden. Während der Operation des Protokolls schiebt sich dieses Fenster über den Sequenznummernbereich vorwärts. Aus diesem Grund wird N meist als Fenstergröße (Window Size) und das GBN-Protokoll als Sliding-Window-Protokoll bezeichnet. Sie wundern sich vielleicht, warum wir die Anzahl der schwebenden, nicht bestätigten Pakete überhaupt auf einen Wert von N einschränken. Warum nicht eine unbegrenzte Anzahl solcher Pakete zulassen? Abschnitt 3.5 wird zeigen, dass Flusskontrolle einer der Gründe ist, warum wir dem Sender eine Grenze auferlegen müssen. Ein weiterer Grund wird in Abschnitt 3.7 in Zusammenhang mit der TCP-Überlastkontrolle beschrieben. Abbildung 3.19 Erweiterte FSM-Beschreibung des GBN-Senders rdt_send(data) if(nextseqnum<base+N){ compute chksum make_pkt(sendpkt, nextseqnum,data,chksum) udt_send(sndpkt(nextseqnum)) if(base==nextseqnum) start_timer nextseqnum=nextseqnum+1 } else refuse_data(data) rdt_rcv(rcv_pkt) && notcorrupt(rcvpkt) base=getacknum(rcvpkt)+1 if(base==nextseqnum) stop_timer else start_timer timeout Warte start_timer udt_send(sndpkt(base)) udt_send(sndpkt(base+1) .. udt_send(sndpkt (nextseqnum-1)) In der Praxis ist die Sequenznummer eines Pakets in einem Feld mit fester Länge im Paket-Header enthalten. Wenn k die Anzahl der Bits im Sequenznummernfeld des 203 Kapitel 3 – Transportschicht 204 Pakets ist, lautet der Sequenznummernbereich [0,2k – 1]. Mit einem endlichen Sequenznummernbereich muss sämtliche Arithmetik, die mit Sequenznummern zu tun hat, mit Hilfe der Modulus-2k-Arithmetik erfolgen. (Das heißt, den Sequenznummernraum kann man sich als Ring mit der Größe 2k vorstellen, wobei der Sequenznummer 2k – 1 unmittelbar die Sequenznummer 0 folgt.) Wir erinnern uns, dass rdt3.0 eine 1-Bit-Sequenznummer und einen Sequenznummernbereich von [0,1] hatte. In mehreren Übungen am Ende dieses Kapitels werden die Konsequenzen eines endlichen Sequenznummernbereichs untersucht. Wir werden in Abschnitt 3.5 noch sehen, dass TCP ein Feld für eine 32-Bit-Sequenznummer hat, wobei TCPSequenznummern anstelle von Paketen Bytes im Bytestrom zählen. Die Abbildungen 3.19 und 3.20 zeigen eine erweiterte FSM-Beschreibung der Sender- und Empfängerseite eines GBN-Protokolls, das ACKs, jedoch keine NAKs benutzt. Wir bezeichnen diese FSM-Beschreibung als erweiterte FSM, weil wir Variablen (ähnlich Variablen in Programmiersprachen) für base und nextseqnum sowie Operationen und bedingte Aktionen für diese Variablen hinzugefügt haben. Man beachte, dass die erweiterte FSM-Spezifikation allmählich wie die Spezifikation einer Programmiersprache aussieht. In [Bochman 1984] finden Sie eine ausgezeichnete Untersuchung zusätzlicher Erweiterungen für FSM-Techniken sowie andere Techniken auf der Grundlage von Programmiersprachen für die Spezifikation von Protokollen. Abbildung 3.20 Erweiterte FSM-Beschreibung des GBN-Empfängers Warte default udt_send(sndpkt) rdr_rcv(rcvpkt) && notcorrupt(rcvpkt) && hasseqnum(rcvpkt,expectedseqnum) extract(rcvpkt,data) deliver_data(data) make_pkt(sndpkt,ACK,expectedseqnum) udt_send(sndpkt) Der GBN-Sender muss auf drei Ereignisarten reagieren: õ Aufruf von oben: Wenn rdt_send() von oben aufgerufen wird, prüft der Sender zuerst, ob das Fenster voll ist, d. h. ob N unbestätigte Pakete anstehen. Ist das Fenster nicht voll, wird ein Paket erzeugt und gesendet und die Variablen werden entsprechend aktualisiert. Ist das Fenster voll, gibt der Sender einfach die Daten an die höhere Schicht zurück; dies ist ein impliziter Hinweis darauf, dass das Fenster voll ist. Die höhere Schicht würde es dann vermutlich später erneut versuchen. In einer echten Implementierung hätte der Sender diese Daten eher zwischengespeichert (und nicht sofort gesendet) oder es wäre ein Synchronisationsmechanismus (z. B. eine Semaphore oder ein Flag) vorhanden, der es der höheren Schicht ermöglichen würde, rdt_send() nur aufzurufen, wenn das Fenster nicht voll ist. õ Empfang eines ACK: Bei unserem GBN-Protokoll wird eine Bestätigung für ein Paket mit der Sequenznummer n als kumulative Bestätigung betrachtet, was darauf hinweist, dass alle Pakete mit einer Sequenznummer bis einschließlich n korrekt beim Empfänger angekommen sind. Wir greifen dieses Thema wieder auf, wenn wir die Empfängerseite von GBN beschreiben. 3.4 Prinzipien des zuverlässigen Datentransfers õ Timeout-Ereignis: Der Name des Protokolls, »Go-Back-N«, leitet sich aus dem Verhalten des Senders bei verlorenen oder übermäßig verzögerten Pakete ab. Wie beim Stop-and-Wait-Protokoll wird auch hier ein Timer benutzt, um den ordentlichen Betrieb nach einem Verlust von Daten- oder Bestätigungspaketen wieder herzustellen. Wenn der Timer abläuft, überträgt der Sender alle Pakete, die zuvor gesendet, aber noch nicht bestätigt wurden, noch einmal. Der Sender in Abbildung 3.19 benutzt nur einen einzigen Timer, den man sich als Timer für das älteste übertragene, aber noch nicht bestätigte Paket vorstellen kann. Wenn ein ACK empfangen wird, während immer noch übertragene und noch nicht bestätigte Pakete anstehen, wird der Timer neu gestartet. Falls keine unbestätigten Pakete anstehen, wird der Timer gestoppt. Die Aktionen des Empfängers sind im GBN ebenfalls einfach. Wenn ein Paket mit Sequenznummer n korrekt und in der richtigen Reihenfolge ankommt (d. h., die zuletzt an die höhere Schicht übertragenen Daten kamen von einem Paket mit Sequenznummer n – 1), sendet der Empfänger ein ACK für Paket n und überträgt den Datenteil des Pakets an die höhere Schicht. In allen übrigen Fällen verwirft der Empfänger das Paket und sendet ein weiteres ACK für das letzte, in der richtigen Reihenfolge empfangene Paket. Da Pakete einzeln an die höhere Schicht übertragen werden, wurden alle Pakete mit einer Sequenznummer, die kleiner als k ist, ebenfalls übertragen, wenn Paket k empfangen wurde. Folglich ist die Verwendung kumulativer Bestätigungen eine natürliche Wahl für GBN. In unserem GBN-Protokoll verwirft der Empfänger außer der Reihe ankommende Pakete. Es mag zwar unsinnig und verschwenderisch erscheinen, ein korrekt (aber außer der Reihe) empfangenes Paket zu verwerfen, jedoch gibt es einen guten Grund für dieses Vorgehen. Wir erinnern uns, dass der Empfänger Daten in der richtigen Reihenfolge an die höhere Schicht weitergeben muss. Angenommen, es wird Paket n erwartet, während aber Paket n + 1 ankommt. Da Daten in der richtigen Reihenfolge übergeben werden müssen, könnte der Empfänger Paket n + 1 zwischenspeichern und es dann an die höhere Schicht weitergeben, nachdem er Paket n empfangen und weitergegeben hat. Geht Paket n aber verloren, müssen den GBN-Regeln zufolge Paket n und n + 1 letztlich erneut übertragen werden. Deshalb lassen wir den Empfänger Paket n + 1 einfach verwerfen. Der Vorteil ist dabei die Einfachheit der Zwischenspeicherung beim Empfänger: Dieser muss keine außer der Reihe ankommenden Pakete zwischenspeichern. Während der Sender also die obere und untere Grenze seines Fensters und die Position von nextseqnum innerhalb dieses Fensters einhalten muss, braucht der Empfänger als einzige Information nur die Sequenznummer des nächsten, in der richtigen Reihenfolge ankommenden Pakets zu verwalten. Dieser Wert befindet sich in der Variablen expectedseqnum (siehe Empfänger-FSM in Abbildung 3.20). Selbstverständlich hat das Wegwerfen eines korrekt empfangenen Pakets auch einen Nachteil: Die anschließende Neuübertragung dieses Pakets kann verloren gehen oder beschädigt werden, so dass unter Umständen noch mehr Neuübertragungen nötig sind. Abbildung 3.21 zeigt die Operation des GBN-Protokolls mit einer Fenstergröße von vier Paketen. Aufgrund dieser Begrenzung der Fenstergröße überträgt der Sender die Pakete 0 bis 3 und muss dann warten, bis eines oder mehrere dieser Pakete bestätigt werden, bevor er fortfahren kann. Während aufeinander folgende ACKs (z. B. ACK0 und ACK1) empfangen werden, wird das Fenster vorwärts geschoben und der Sender kann ein neues Paket (pkt4 bzw. pkt5) übertragen. Auf der Empfängerseite geht Paket 2 verloren, so dass die Pakete 3, 4 und 5 nicht in der richtigen Reihenfolge sind und verworfen werden. 205 Kapitel 3 – Transportschicht 206 Abbildung 3.21 Go-Back-N in Operation Sender Empfänger pkt0 senden pkt0 empfangen ACK0 senden pkt1 senden pkt2 senden (Verlust) X pkt1 empfangen ACK1 senden pkt3 senden (warten) pkt3 empfangen, verwerfen ACK1 senden ACK0 empfangen pkt4 senden ACK1 empfangen pkt5 senden pkt2 Timeout pkt2 senden pkt3 senden pkt4 senden pkt5 senden pkt4 empfangen, verwerfen ACK1 senden pkt5 empfangen, verwerfen ACK1 senden pkt2 ACK2 pkt3 ACK3 empfangen, weitergeben senden empfangen, weitergeben senden Bevor wir unsere GBN-Diskussion beenden, lohnt sich an dieser Stelle der Hinweis, dass eine Implementierung dieses Protokolls in einem Protokollstack wahrscheinlich ähnlich aussähe wie die der erweiterten FSM in Abbildung 3.19. Die Implementierung würde wahrscheinlich ebenfalls verschiedene Prozeduren umfassen, die die Aktionen implementieren, die als Reaktion auf die verschiedenen möglichen Ereignisse unternommen werden müssen. Bei einer derartigen ereignisbasierten Programmierung werden die verschiedenen Prozeduren entweder von anderen Prozeduren im Protokollstack oder als Ergebnis eines Interrupt aufgerufen. Im Sender wären diese Ereignisse (1) ein Aufruf von rdt_send() durch die Instanz der höheren Schicht, (2) ein Timer-Interrupt und (3) ein Aufruf von rdt_rcv() durch die höhere Schicht bei Ankunft eines Pakets. Die Programmierübungen am Ende dieses Kapitels geben Ihnen Gelegenheit, diese Routinen in einer simulierten, aber realistischen Netzwerkumgebung zu implementieren. Man beachte, dass das GBN-Protokoll fast alle Techniken beinhaltet, denen wir bei der Untersuchung der zuverlässigen Datentransferkomponenten von TCP in Abschnitt 3.5 begegnen. Diese Techniken umfassen die Verwendung von Sequenznummern, kumulative Bestätigungen, Prüfsummen und eine Timeout/Neuübertragungsoperation. In diesem Sinn weist TCP verschiedene Elemente eines GBN-Protokolls auf. Zwischen GBN und TCP existieren aber einige Unterschiede. Viele TCPImplementierungen speichern korrekt, jedoch außer der Reihenfolge empfangene 3.4 Prinzipien des zuverlässigen Datentransfers Segmente in einem Puffer [Stevens 1994]. Eine für TCP vorgeschlagene Modifikation, die sogenannte »selektive Bestätigung« [RFC 2581], wird es einem TCP-Empfänger auch ermöglichen, ein bestimmtes außer der Reihe angekommenes Paket selektiv zu bestätigen, statt kumulativ das zuletzt korrekt empfangene Paket bestätigen zu müssen. Das Konzept einer selektiven Bestätigung liegt im Kern der zweiten allgemeinen Klasse von Pipeline-Protokollen: die so genannten »Selective-Repeat-Protokolle« (siehe nächsten Abschnitt). TCP passt folglich scheinbar am besten in die Kategorie eines Hybridprotokolls, d. h. einer Mischung zwischen Go-Back-N- und SelectiveRepeat-Protokollen. 3.4.4 Selective Repeat (SR) Das GBN-Protokoll erlaubt es dem Sender, potenziell »die Pipeline in Abbildung 3.17 mit Paketen zu füllen« und daher die bei Stop-and-Wait-Protokollen auftretenden Probleme mit der Kanalauslastung zu vermeiden. Es gibt allerdings Szenarien, in denen GBN ebenfalls unter Leistungsproblemen leidet. Wenn die Fenstergröße und das Bandbreite/Verzögerung-Produkt groß sind, können sich viele Pakete in der Pipeline befinden. Ein einzelner Paketfehler kann GBN veranlassen, eine große Anzahl von Paketen erneut zu übertragen, von denen viele vielleicht unnötig sind. Abbildung 3.22 Sequenznummernraum aus Sicht des SR-Senders und -Empfängers send_base Bereits bestätigt Verwendbar, noch nicht gesendet Gesendet, noch nicht bestätigt Nicht verwendbar nextseqnum Fenstergröße N (a) Sequenznummern aus Sicht des Senders Außer der Reihe (im Puffer), aber bereits bestätigt Akzeptabel (innerhalb der Fenstergröße) Erwartet, noch nicht empfangen Nicht verwendbar Fenstergröße N rcv_base (b) Sequenznummern aus Sicht des Empfängers 207 208 Kapitel 3 – Transportschicht Mit zunehmender Wahrscheinlichkeit von Kanalfehlern füllt sich die Pipeline zunehmend mit solchen unnötigen Neuübertragungen. Man stelle sich in unserem Nachrichtendiktierszenario vor, dass jedes Mal, wenn ein Wort beschädigt wird, die umgebenden 1.000 Wörter (bei einer Fenstergröße von beispielsweise 1.000 Wörtern) wiederholt werden müssten. Das Diktat würde sich durch all die Wortwiederholungen sehr verlangsamen. Wie die Bezeichnung bereits andeutet, vermeiden SR-Protokolle (SelectiveRepeat) unnötige Neuübertragungen dadurch, dass der Sender nur jene Pakete erneut überträgt, bei denen er einen fehlerhaften Empfang (verloren oder beschädigt) auf Seiten des Empfängers vermutet. Diese selektive Neuübertragung setzt voraus, dass der Empfänger korrekt empfangene Pakete individuell bestätigt. Eine Fenstergröße von N wird wiederum benutzt, um die Anzahl der ausstehenden unbestätigten Pakete in der Pipeline zu begrenzen. Im Gegensatz zu GBN hat der Sender aber für einige der im Fenster stehenden Pakete bereits ACKs empfangen. Abbildung 3.22 zeigt den Sequenznummernraum aus Sicht des SR-Senders. Die verschiedenen, vom SR-Sender unternommenen Aktionen sehen Sie in Abbildung 3.23. Der SR-Empfänger bestätigt ein korrekt empfangenes Paket ungeachtet dessen, ob es in der richtigen oder falschen Reihenfolge ankommt. Außer der Reihe ankommende Pakete werden so lange zwischengespeichert, bis eventuell fehlende Pakete (d. h. Pakete mit niedrigeren Sequenznummern) empfangen werden. An diesem Punkt kann eine Paketserie in der richtigen Reihenfolge an die höhere Schicht weitergegeben werden. Abbildung 3.24 beschreibt die Einzelheiten der verschiedenen, vom SR-Empfänger eingeleiteten Aktionen. Abbildung 3.25 zeigt ein Beispiel der SR-Operation für den Fall verlorener Pakete. Man beachte in Abbildung 3.25, dass der Empfänger die Pakete 3 und 4 zunächst zwischenspeichert und sie dann zusammen mit Paket 2, nachdem dieses eingegangen ist, an die höhere Schicht weitergibt. Abbildung 3.23 Ereignisse und Aktionen des SR-Senders 1. Daten werden von oben empfangen. Wenn Daten von oben empfangen werden, prüft der SR-Sender die nächste, für das Paket verfügbare Sequenznummer. Liegt die Sequenznummer innerhalb des Senderfensters, werden die Daten in einem Paket gekapselt und gesendet; andernfalls werden sie entweder zwischengespeichert oder zur späteren Übertragung, wie in GBN, an die höhere Schicht zurückgegeben. 2. Timeout. Auch hier werden als Schutz vor verlorenen Paketen Timer benutzt. Jedes Paket braucht aber einen eigenen logischen Timer, weil bei einem Timeout nur jeweils ein einzelnes Paket übertragen wird. Ein einziger Hardware-Timer lässt sich verwenden, um mehrere logische Timer vorzutäuschen [Varghese 1997]. 3. ACK empfangen. Wenn ein ACK empfangen wird, markiert der SR-Sender dieses Paket als empfangen, sofern es sich im Fenster befindet. Ist die Sequenznummer des Pakets gleich send-base, rückt die Fensterbasis zu dem unbestätigten Paket mit der kleinsten Sequenznummer vor. Wenn das Fenster vorrückt, während noch unübertragene Pakete mit Sequenznummern anstehen, die jetzt innerhalb des Fensters liegen, werden diese Pakete übertragen. Wichtig ist in Schritt 2 von Abbildung 3.24, dass der Empfänger bereits empfangene Pakete mit bestimmten Sequenznummern unter der aktuellen Fensterbasis erneut bestätigt (statt sie zu ignorieren). Sie sollten sich selbst davon überzeugen, dass diese erneute Bestätigung tatsächlich nötig ist. Wenn sich beispielsweise im Fall des 3.4 Prinzipien des zuverlässigen Datentransfers Sequenznummernraums des Senders und des Empfängers in Abbildung 3.22 kein ACK für Paket send_base vom Empfänger zum Sender ausbreitet, überträgt der Sender letztlich das Paket send_base erneut, obwohl uns (nicht aber dem Sender!) klar ist, dass der Empfänger dieses Paket bereits empfangen hat. Wenn der Empfänger dieses Paket nicht bestätigen würde, würde das Fenster des Senders nie vorrücken! Dieses Beispiel zeigt einen wichtigen Aspekt von SR-Protokollen (und auch vieler anderer Protokolle). Sender und Empfänger haben nicht immer die gleiche Sicht dessen, was korrekt empfangen wurde. Für SR-Protokolle bedeutet das, dass das Senderund das Empfängerfenster nicht immer übereinstimmen. Abbildung 3.24 Ereignisse und Aktionen des SR-Empfängers 1. Paket mit Sequenznummer in [rcv_base, rcv_base+N–1] korrekt empfangen: In diesem Fall liegt das empfangene Paket innerhalb des Empfängerfensters und ein selektives ACK-Paket wird an den Sender zurückgegeben. Wenn das Paket nicht zuvor empfangen wurde, wird es zwischengespeichert. Hat dieses Paket eine Sequenznummer gleich der Basis des Empfangsfensters (rcv_base in Abbildung 3.22), dann werden dieses und eventuell zuvor zwischengespeicherte und (beginnend mit rcv_base) durchnummerierte Pakete an die höhere Schicht weitergereicht. Das Empfangsfenster wird dann um die Anzahl der an die höhere Schicht weitergereichten Pakete vorwärts geschoben. Als Beispiel betrachte man Abbildung 3.25. Wenn ein Paket mit einer Sequenznummer von rcv_base=2 empfangen wird, können dieses und die Pakete rcv_base+1 und rcv_base2 an die höhere Schicht weitergegeben werden. 2. Paket mit Sequenznummer in [rcv_base–N, rcv_base–1] empfangen: In diesem Fall muss ein ACK generiert werden, auch wenn dies ein Paket ist, das der Empfänger bereits bestätigt hat. 3. Andernfalls: Ignoriere das Paket. Der Mangel an Synchronisation zwischen dem Sender- und Empfängerfenster hat wichtige Konsequenzen, wenn wir mit der Realität eines endlichen Sequenznummernbereichs konfrontiert sind. Man betrachte, was beispielsweise mit einem endlichen Bereich von vier Paketsequenznummern, 0, 1, 2 und 3, und einer Fenstergröße von 3 passiert. Angenommen, die Pakete 0 bis 2 werden übertragen, beim Empfänger korrekt empfangen und bestätigt. An diesem Punkt befindet sich das Empfängerfenster über dem vierten, fünften und sechsten Paket, die die Sequenznummern 3, 0 bzw. 1 haben. Nun betrachte man zwei Szenarien. Im ersten Szenario, das in Abbildung 3.26 (a) dargestellt ist, gehen die ACKs für die ersten drei Pakete verloren und der Sender überträgt diese Pakete erneut. Der Empfänger empfängt also als Nächstes ein Paket mit der Sequenznummer 0 – eine Kopie des ersten gesendeten Pakets. Im zweiten Szenario, das in Abbildung 3.26 (b) dargestellt ist, wurden die ACKs für die ersten drei Pakete korrekt weitergegeben. Der Sender schiebt folglich sein Fenster weiter und sendet das vierte, fünfte und sechste Paket mit den Sequenznummern 3, 0 bzw. 1. Das Paket mit Sequenznummer 3 geht verloren, während jedoch dasjenige mit Sequenznummer 0 – ein Paket, das neue Daten enthält – ankommt. Abbildung 3.26 zeigt die Situation aus Sicht des Empfängers; es wurde eine Art »Vorhang« zwischen Sender und Empfänger eingefügt, da der Empfänger die vom Sender eingeleiteten Aktionen nicht »sehen« kann. Alles, was der Empfänger mitbekommt, ist die Sequenz von Nachrichten, die er vom Kanal empfängt und in den Kanal sendet. Was ihn anbelangt, sind die beiden Szenarien in Abbildung 3.26 iden- 209 Kapitel 3 – Transportschicht 210 Abbildung 3.25 SR-Operation pkt0 gesendet 0 1 2 3 4 5 6 7 8 9 pkt1 gesendet pkt0 empfangen,abgegeben,ACK0 gesendet 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 pkt2 gesendet pkt1 empfangen,abgegeben,ACK1 gesendet 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 X(Verlust) pkt3 gesendet, Fenster voll 0 1 2 3 4 5 6 7 8 9 pkt3 empfangen,zwischengesp.,ACK3 gesendet 0 1 2 3 4 5 6 7 8 9 ACK0 empfangen, pkt4 gesendet 0 1 2 3 4 5 6 7 8 9 pkt4 empfangen,zwischengesp.,ACK4 gesendet pkt2 Timeout, pkt2 erneut gesendet 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 ACK1 empfangen, pkt5 gesendet pkt2 empfangen;pkt2,pkt3,pkt4 abgegeben,ACK4 gesendet 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 pkt5 empfangen,abgegeben,ACK5 gesendet 0 1 2 3 4 5 6 7 8 9 tisch. Er hat keine Möglichkeit der Unterscheidung zwischen der Neuübertragung des ersten Pakets und der erstmaligen Übertragung des fünften Pakets. Natürlich kann eine Fenstergröße, die um 1 kleiner als die Größe des Sequenznummernraums ist, nicht funktionieren. Wie klein muss aber die Fenstergröße sein? In einer Übung am Ende dieses Kapitels sollen Sie aufzeigen, dass die Fenstergröße für SR-Protokolle kleiner als oder gleich die Hälfte der Größe des Sequenznummernraums sein muss. Wir beenden unsere Diskussion zuverlässiger Datentransferprotokolle mit einer verbleibenden Annahme in unserem grundlegenden Kanalmodell. Wie Sie sich sicher erinnern, sind wir davon ausgegangen, dass Pakete innerhalb des Kanals zwischen Sender und Empfänger nicht umgeordnet werden können. Dies ist im Allgemeinen eine vernünftige Annahme, wenn der Sender und der Empfänger über eine einzige physikalische Leitung verbunden sind. Ist der »Kanal«, der die beiden verbindet, aber ein Netzwerk, kann eine Paketumstellung erfolgen. Eine Folge der Umordnung von Paketen ist, dass alte Kopien eines Pakets mit einer Sequenz- oder Bestätigungsnummer von x auftauchen können, auch wenn weder das Sender- noch das Empfängerfenster x enthält. Im Sinne der Paketumstellung kann man sich den Kanal so vorstellen, dass er im Wesentlichen Pakete zwischenspeichert und diese dann spontan irgendwann in der Zukunft ausgibt. Da Sequenznummern wiederverwendet werden können, bedarf es einiger Sorgfalt, um solche Duplikatpakete zu verhindern. Mit dem in der Praxis angewandten Ansatz wird sichergestellt, dass eine Sequenznummer erst wiederverwendet wird, wenn der Sender relativ »sicher« ist, dass zuvor gesendete Pakete mit Sequenznummer x nicht mehr im Netzwerk sind. Dies gründet auf der Annahme, dass ein Paket im Netzwerk nicht länger als eine bestimmte Höchstdauer »leben« kann. Eine maximale Lebensdauer für Pakete von ungefähr drei Minuten 3.4 Prinzipien des zuverlässigen Datentransfers Abbildung 3.26 Dilemma des SR-Empfängers mit zu großen Fenstern: ein neues Paket oder eine Neuübertragung? Senderfenster (nach Empfang) Empfängerfenster (nach Empfang) 0 1 2 3 0 1 2 pkt0 0 1 2 3 0 1 2 pkt1 0 1 2 3 0 1 2 pkt2 ACK0 0 1 2 3 0 1 2 ACK1 0 1 2 3 0 1 2 ACK2 x 0 1 2 3 0 1 2 x Timeout Neuübertragung pkt0 x 0 1 2 3 0 1 2 pkt0 empfange Paket mit Sequenznummer 0 (a) Senderfenster (nach Empfang) Empfängerfenster (nach Empfang) 0 1 2 3 0 1 2 pkt0 0 1 2 3 0 1 2 pkt1 0 1 2 3 0 1 2 pkt2 ACK0 0 1 2 3 0 1 2 ACK1 0 1 2 3 0 1 2 ACK2 0 1 2 3 0 1 2 pkt3 0 1 2 3 0 1 2 pkt0 0 1 2 3 0 1 2 x empfange Paket mit Sequenznummer 0 (b) 211 212 Kapitel 3 – Transportschicht wird in den TCP-Erweiterungen für Hochgeschwindigkeitsnetze [RFC 1323] angenommen. In [Sunshine 1978] wird eine Methode für die Verwendung von Sequenznummern beschrieben, bei der Umordnungsprobleme völlig umgangen werden können. 3.5 Verbindungsorientierter Transport: TCP Nachdem wir die grundlegenden Prinzipien von zuverlässigem Datentransfer untersucht haben, wenden wir uns nun TCP – dem verbindungsorientierten, zuverlässigen Internet-Transportprotokoll – zu. Um einen zuverlässigen Datentransfer bereitzustellen, nutzt TCP viele der grundlegenden Prinzipien, die im vorherigen Abschnitt beschrieben wurden, darunter Fehlererkennung, Neuübertragungen, kumulative Bestätigungen, Timer und Header-Felder für Sequenz- und Bestätigungsnummern. TCP ist in RFC 793, RFC 1122, RFC 1323, RFC 2018 und RFC 2581 definiert. 3.5.1 Die TCP-Verbindung TCP bietet Multiplexen, Demultiplexen und Fehlererkennung auf genau die gleiche Weise wie UDP. Dennoch unterscheiden sich TCP und UDP auf vielerlei Art. Der wichtigste Unterschied ist, dass UDP verbindungslos und TCP verbindungsorientiert ist. UDP ist verbindungslos, weil es Daten sendet, ohne eine Verbindung aufzubauen. TCP ist verbindungsorientiert, weil zwei Prozesse zuerst ein »Handshake« durchführen müssen, bevor sie einander Daten senden können. Das heißt, sie müssen einige Segmente austauschen, um die Parameter für den anschließenden Datentransfer einzurichten. Als Teil des TCP-Verbindungsaufbaus initialisieren beide Seiten der Verbindung viele »TCP-Zustandsvariablen« (von denen viele in diesem Abschnitt und in Abschnitt 3.7 beschrieben werden) in Zusammenhang mit der TCP-Verbindung. Die »TCP-Verbindung« ist keine Ende-zu-Ende-TDM- oder -FDM-Leitung wie in einem leitungsvermittelten Netzwerk. Sie ist auch kein virtueller Kanal (siehe Kapitel 1), weil der Verbindungszustand vollständig in den beiden Endsystemen residiert. Da das TCP-Protokoll nur in den Endsystemen und nicht in den dazwischen liegenden Netzwerkelementen (Routern und Bridges) läuft, führen die dazwischen liegenden Netzwerkelemente keinen TCP-Verbindungszustand. Tatsächlich haben die vermittelnden Router keine Ahnung von TCP-Verbindungen; sie sehen Datagramme, aber keine Verbindungen. FALLBEISPIEL Vinton Cerf, Robert Kahn und TCP/IP Anfang der siebziger Jahre begannen sich paketvermittelte Netzwerke zu verbreiten; ARPANET, der Vorläufer des Internets, war eines davon. Jedes dieser Netzwerke hatte sein eigenes Protokoll. Die beiden Wissenschaftler Vinton Cerf und Robert Kahn erkannten die Bedeutung des Zusammenschlusses dieser Netzwerke und erfanden ein netzwerkübergreifendes Protokoll namens »TCP/IP« (Transmission Control Protocol/Internet Protocol). Obwohl Cerf und Kahn das Protokoll anfangs als eine Einheit betrachteten, wurde es später in seine zwei Teile – TCP und IP – aufgeteilt, die getrennt arbeiten. Cerf und Kahn veröffentlichten im Mai 1974 in IEEE Transactions on Communications Technology eine Arbeit über TCP/IP. ➔ 3.5 Verbindungsorientierter Transport: TCP ➔ Das TCP/IP-Protokoll, das Brot und Salz im heutigen Internet, wurde entwickelt, bevor es PCs und Workstations, Ethernet- und andere LAN-Technologien, das Web, StreamingAudio und Chat gab. Cerf und Kahn erkannten die Notwendigkeit für ein Netzwerkprotokoll, das einerseits breite Unterstützung für künftige Anwendungen und andererseits die Zusammenarbeit zwischen beliebigen Hosts und Protokollen der Sicherungsschicht ermöglicht. Eine TCP-Verbindung bietet Vollduplex-Datentransfer. Existiert eine TCP-Verbindung zwischen Prozess A auf einem Host und Prozess B auf einem anderen, dann können die Daten der Anwendungsebene von A nach B und von B nach A gleichzeitig fließen. Eine TCP-Verbindung arbeitet auch immer Punkt-zu-Punkt, d. h. zwischen einem einzigen Sender und einem einzigen Empfänger. Das sogenannte »Multicasting« (siehe Abschnitt 4.8) – die Übertragung von Daten von einem Sender an viele Empfänger in einer einzigen Sendeoperation – ist in TCP nicht möglich. Für TCP sind zwei Hosts schon eine Firma und drei eine Masse! Wir betrachten jetzt den Aufbau einer TCP-Verbindung. Angenommen, ein Prozess, der auf einem Host läuft, möchte eine Verbindung zu einem anderen Prozess auf einem anderen Host einleiten. Wir erinnern uns, dass der Prozess, der die Verbindung einleitet, als Client-Prozess und der andere als Server-Prozess bezeichnet wird. Der Client-Prozess informiert zuerst das clientseitige TCP darüber, dass er eine Verbindung zu einem Prozess im Server aufbauen will. Wie in Abschnitt 2.6 beschrieben, bewirkt dies ein Java-Client-Programm durch Abarbeitung des Befehls Socket clientSocket = new Socket(“hostname“, port number); Die Transportschicht im Client baut dann eine TCP-Verbindung zu TCP im Server auf. Wir beschreiben die Prozedur des Verbindungsaufbaus ausführlicher am Ende dieses Abschnitts. Vorläufig genügt es zu wissen, dass der Client zunächst ein spezielles TCP-Segment sendet; der Server antwortet mit einem zweiten speziellen TCPSegment; schließlich antwortet der Client wieder mit einem dritten speziellen Segment. Die ersten beiden Segmente enthalten keine Nutzdaten, d. h. keine Anwendungsdaten; das dritte kann Nutzdaten enthalten. Da drei Segmente zwischen den beiden Hosts gesendet werden, nennt man diesen Verbindungsaufbau auch DreiWege-Handshake. Nachdem eine TCP-Verbindung aufgebaut wurde, können die beiden Anwendungsprozesse einander Daten senden; dies ist die ganze Zeit möglich, weil TCP im Vollduplex läuft. Wir betrachten nun genauer, wie Daten vom Client- zum ServerProzess gesendet werden. Der Client-Prozess gibt einen Datenstrom durch das Socket (die Tür des Prozesses) weiter, wie in Abschnitt 2.6 beschrieben. Nachdem die Daten durch die Tür geflossen sind, liegen sie in der Hand von TCP, das auf dem Client läuft. Wie in Abbildung 3.27 dargestellt ist, leitet TCP diese Daten an den Sendepuffer der Verbindung weiter. Das ist einer der Puffer, die während des anfänglichen Drei-Wege-Handshake vorbereitet werden. Von Zeit zu Zeit nimmt TCP Datenstücke aus dem Sendepuffer. Interessant ist, dass die TCP-Spezifikation [RFC 793] »sehr locker« dahingehend ist, wann TCP tatsächlich zwischengespeicherte Daten senden sollte. Es wird lediglich angegeben, dass TCP »Daten in Segmenten nach eigenem Ermessen senden« sollte. Die maximale Datenmenge, die aus dem Puffer genommen und in ein Segment gestellt werden kann, ist durch die maximale Segmentgröße (Maximum Segment Size, MSS) begrenzt. Die MSS hängt von der TCP-Implementie- 213 Kapitel 3 – Transportschicht 214 rung (die vom Betriebssystem bestimmt wird) ab und kann häufig konfiguriert werden; übliche Werte sind 1.500, 536 und 512 Byte. (Diese Segmentgrößen werden oft gewählt, um IP-Fragmentierung (siehe nächstes Kapitel) zu vermeiden.) Man beachte, dass die MSS die maximale Menge von Anwendungsdaten im Segment und nicht die maximale Größe des TCP-Segments – einschließlich der Header – ist. (Diese Terminologie ist verwirrend, wir müssen aber damit leben, weil sie sich allgemein durchgesetzt hat.) TCP kapselt jedes Client-Datenstück mit einem TCP-Header und formt damit TCP-Segmente. Die Segmente werden nach unten an die Vermittlungsschicht weitergereicht, wo sie getrennt in IP-Datagrammen der Vermittlungsschicht verkapselt werden. Anschließend werden die IP-Datagramme im Netzwerk versendet. Wenn TCP am anderen Ende ein Segment empfängt, stellt es die Daten des Segments in den Empfangspuffer der TCP-Verbindung. Die Anwendung liest den Datenstrom aus diesem Puffer. Jede Seite der Verbindung hat ihren eigenen Sendepuffer und ihren eigenen Empfangspuffer. Die Sende- und Empfangspuffer für Daten, die in eine Richtung fließen, sind in Abbildung 3.27 dargestellt. Abbildung 3.27 Die Sende- und Empfangspuffer von TCP Prozess Prozess schreibt liest Daten Daten Socket Socket TCPSendepuffer Segment TCP receive buffer TCPEmpfangspuffer Eine TCP-Verbindung setzt sich also zusammen aus Puffern, Variablen und einer Socket-Verbindung zu einem Prozess in einem Host sowie einer weiteren Gruppe von Puffern, Variablen und einer Sokket-Verbindung zu einem Prozess auf einem anderen Host. Wie erwähnt, werden der Verbindung in den Netzwerkelementen (Router, Bridges und Repeater) zwischen den Hosts keine Puffer oder Variablen zugewiesen. 3.5.2 TCP-Segmentstruktur Nach einem kurzen Überblick über die TCP-Verbindung betrachten wir nun die TCPSegmentstruktur. Das TCP-Segment besteht aus Header-Feldern und einem Datenfeld. Das Datenfeld enthält einen Teil der Anwendungsdaten. Wie erwähnt, begrenzt die MSS die maximale Größe des Datenfelds eines Segments. Wenn TCP eine große Datei, z. B. ein kodiertes Bild als Teil einer Web-Seite, sendet, teilt es die Datei normalerweise in Stücke gemäß der MSS-Größe auf (mit Ausnahme des letzten Stücks, das meist kleiner als die MSS ist). Interaktive Anwendungen übertragen aber oft Datenstücke, die viel kleiner als die MSS sind; z. B. ist das Datenfeld im TCP-Segment bei 3.5 Verbindungsorientierter Transport: TCP Remote-Login-Anwendungen wie Telnet oft nur ein Byte groß. Da der TCP-Header generell 20 Byte (12 Byte mehr als der UDP-Header) umfasst, sind die von Telnet gesendeten Segmente unter Umständen nur 21 Byte lang. Abbildung 3.28 Die TCP-Segmentstruktur Portnummer Ziel Portnummer Quelle Sequenznummer Bestätigungsnummer U A P P S F Header- nicht Länge benutzt R C S S Y I G K H T N N Internet-Prüfsumme Empfänger-Fenstergröße Zeiger zu dringenden Daten Optionen Daten 32 Bit Abbildung 3.28 zeigt die Struktur des TCP-Segments. Wie bei UDP beinhaltet der Header die Portnummer für Quelle und Ziel, die für das Multiplexen/Demultiplexen von Daten von/zu Anwendungen der höheren Schicht benutzt werden. Ebenfalls wie bei UDP enthält der Header ein Prüfsummenfeld. Ein TCP-Segment-Header beinhaltet außerdem folgende Felder: õ Die beiden 32-Bit-Felder Sequenznummer und Bestätigungsnummer werden vom TCP-Sender und -Empfänger benutzt, um den weiter unten beschriebenen zuverlässigen Datentransferdienst zu implementieren. õ Das 16-Bit-Feld Fenstergröße dient der Flusskontrolle. Wir werden in Kürze sehen, dass es verwendet wird, um die Anzahl von Bytes anzugeben, die ein Empfänger anzunehmen bereit ist. 215 216 Kapitel 3 – Transportschicht õ Das 4-Bit-Feld Header-Länge spezifiziert die Länge des TCP-Headers in 32-BitWörtern. Er kann aufgrund der weiter unten beschriebenen TCP-Optionen eine variable Länge haben. (Normalerweise ist das Feld »Optionen« leer, so dass die Länge des typischen TCP-Headers 20 Byte beträgt.) õ Das optionale Feld Optionen mit variabler Länge wird benutzt, wenn ein Sender und ein Empfänger die maximale Segmentgröße (MSS) vereinbaren, oder es dient in Hochgeschwindigkeitsnetzwerken als Fensterskalierfaktor. Ferner ist eine Zeitstempeloption definiert; siehe RFC 854 und RFC 1323 mit weiteren Einzelheiten. õ Das Flag-Feld enthält 6 Bit. Das ACK-Bit spezifiziert, dass der im Bestätigungsfeld enthaltene Wert gültig ist. Die Bits RST, SYN und FIN werden für den Aufund Abbau der Verbindung benutzt. Ist das PSH-Bit gesetzt, weiß der Empfänger, dass er die Daten sofort an die höhere Schicht weiterreichen soll. Das URG-Bit spezifiziert, dass es in diesem Segment Daten gibt, die von der Instanz der höheren Schicht auf der Sendeseite als »dringend« (urgent) markiert wurden. Die Position des letzten Bytes dieser dringenden Daten wird durch einen 16-Bit-Datenzeiger gekennzeichnet. TCP muss die Instanz der höheren Schicht auf der Empfangsseite informieren, wenn dringende Daten vorliegen, und einen Zeiger (Pointer) zur Kennzeichnung des Endes der dringenden Daten bereitstellen. (In der Praxis werden PSH, URG und Zeiger auf dringende Daten nicht benutzt. Wir erwähnen die Felder hier lediglich der Vollständigkeit halber.) 3.5.3 Sequenz- und Bestätigungsnummern Zwei der wichtigsten Felder im TCP-Segment-Header enthalten die Sequenz- und die Bestätigungsnummer. Diese beiden Felder sind ein wichtiger Teil des zuverlässigen Datentransferdienstes von TCP. Bevor wir die Verwendung dieser Felder für die Bereitstellung eines zuverlässigen Datentransfers beschreiben, erklären wir, was TCP eigentlich in diese Felder einfügt. Für TCP sind Daten ein unstrukturierter, aber geordneter Bytestrom. Die Verwendung von Sequenznummern in TCP spiegelt dies dahingehend wider, dass Sequenznummern den übertragenen Bytestrom und nicht die Serie übertragener Segmente betreffen. Die Sequenznummer eines Segments ist die Bytestromnummer des ersten Bytes im Segment. Wir verdeutlichen dies an einem Beispiel. Angenommen, ein Prozess in Host A möchte einen Datenstrom über eine TCP-Verbindung an einen Prozess in Host B senden. Das TCP in Host A nummeriert jedes Byte im Datenstrom implizit. Wenn der Datenstrom aus einer Datei mit 500.000 Byte besteht, die MSS also 1.000 Byte beträgt, und das erste Byte des Datenstroms mit Null nummeriert wird, bildet TCP aus dem Datenstrom 500 Segmente (siehe Abbildung 3.29). Dem ersten Segment wird die Sequenznummer 0, dem zweiten die Sequenznummer 1.000, dem dritten die Sequenznummer 200 usw. zugewiesen. Jede Sequenznummer wird in das Sequenznummernfeld im Header des entsprechenden TCP-Segments eingefügt. Wir untersuchen jetzt, was es mit den Bestätigungsnummern auf sich hat. Sie sind etwas kniffliger als Sequenznummern. Wie erwähnt, arbeitet TCP in Vollduplex, so dass Host A vielleicht Daten von Host B empfängt, während er (über die gleiche TCPVerbindung) Daten an Host B sendet. Jedes Segment, das von Host B ankommt, hat eine Sequenznummer für die Daten, die von B nach A fließen. Die Bestätigungsnummer, die Host A in sein Segment einfügt, ist die Sequenznummer des nächsten Bytes, das Host A von Host B erwartet. Dies lässt sich leichter anhand einiger Beispiele verstehen. Angenommen, Host A hat alle von 0 bis 535 durchnummerierten Bytes von B emp- 3.5 Verbindungsorientierter Transport: TCP 217 Abbildung 3.29 Aufteilung der Daten einer Datei in TCP-Segmente Datei 0 1 ... Daten des ersten Segments 1.000 ... 1.999 ... 499.999 Daten des zweiten Segments fangen und sendet seinerseits gerade ein Segment an Host B. Anders ausgedrückt, Host A wartet auf Byte 536 und alle nachfolgenden Bytes im Datenstrom von Host B. Host A setzt also 536 in das Bestätigungsnummernfeld des Segments, das er an B sendet. Als weiteres Beispiel nehmen wir an, dass Host A ein Segment, das die Bytes 0 bis 535 enthält, und ein weiteres mit den Bytes 900 bis 1.000, von Host B empfangen hat. Aus irgendeinem Grund sind die Bytes 536 bis 899 bei Host A noch nicht angekommen. In diesem Fall wartet Host A immer noch auf Byte 536 (und folgende), um den Datenstrom von B wiederherstellen zu können. Das nächste Segment von A an B wird also 536 im Bestätigungsnummernfeld enthalten. Da TCP Bytes nur bis zum ersten fehlenden Byte im Datenstrom bestätigt, sagt man, dass TCP kumulative Bestätigungen verwendet. Das letzte Beispiel führt uns zu einem wichtigen und kniffligen Thema. Host A hat das dritte Segment (Bytes 900 bis 1.000) vor dem zweiten Segment (Bytes 536 bis 899) empfangen. Das dritte Segment ist also außerhalb der Reihenfolge angekommen. Das Knifflige daran ist: Was macht ein Host, wenn er Segmente in einer TCP-Verbindung außer der Reihe empfängt? Interessant ist, dass die TCP-RFCs hierfür keine Regeln aufstellen und die Entscheidung den Leuten überlassen, die eine TCP-Implementierung programmieren. Im Grunde bestehen zwei Möglichkeiten: Entweder verwirft der Empfänger die außer der Reihe befindlichen Bytes sofort oder er behält die außer der Reihe befindlichen Bytes und wartet auf die fehlenden Bytes, um die Lücken zu füllen. Natürlich ist die zweite Möglichkeit hinsichtlich der Netzwerkbandbreite effizienter, während die erste den TCP-Code vereinfacht. Im restlichen Teil dieser TCP-Einführung konzentrieren wir uns auf die erste Implementierung, d. h., wir gehen davon aus, dass der TCP-Empfänger außer der Reihe ankommende Segmente verwirft. In Abbildung 3.29 sind wir davon ausgegangen, dass die anfängliche Sequenznummer Null ist. In Wirklichkeit wählen beide Seiten einer TCP-Verbindung zufällig eine Anfangssequenznummer aus. Dies geschieht, um möglichst auszuschließen, dass ein Segment, das noch von einer früheren, bereits beendeten Verbindung zwischen zwei Hosts im Netzwerk vorhanden ist und irrtümlich als gültiges Segment einer späteren Verbindung zwischen den beiden gleichen Hosts (die zufällig auch die gleichen Portnummern wie bei der alten Verbindung benutzen) betrachtet wird [Sunshine 1978]. 218 Kapitel 3 – Transportschicht 3.5.4 Telnet: Eine Fallstudie für Sequenz- und Bestätigungsnummern Das in RFC 854 definierte Telnet ist ein beliebtes Protokoll der Anwendungsschicht, das für Remote-Login benutzt wird. Es läuft über TCP und dem Design zufolge zwischen zwei Hosts. Im Gegensatz zu Anwendungen, die Massendaten übertragen (siehe Kapitel 2), ist Telnet eine interaktive Anwendung. Wir beschreiben Telnet hier, weil es ein nützliches Beispiel ist, um die Sequenz- und Bestätigungsnummern in TCP weiter auszuleuchten. Angenommen, Host A leitet eine Telnet-Sitzung zu Host B ein. Da Host A die Sitzung einleitet, ist er der Client, während Host B der Server ist. Jedes vom Benutzer (am Client) eingegebene Zeichen wird an den entfernten Host gesendet. Der entfernte Host sendet dann eine Kopie jedes Zeichens zurück, das am Bildschirm des TelnetBenutzers angezeigt wird. Dieses »Echo« wird benutzt, um sicherzustellen, dass die Zeichen, die der Telnet-Benutzer sieht, am entfernten Standort bereits empfangen und verarbeitet wurden. Jedes Zeichen überquert also das Netzwerk zweimal zwischen dem Moment, in dem der Benutzer die Taste drückt, und dem Moment, wenn das Zeichen auf dem Bildschirm des Benutzers angezeigt wird. Wir nehmen jetzt an, dass der Benutzer einen einzigen Buchstaben, C, eintippt und sich einen Kaffee holt. Wir wollen die TCP-Segmente untersuchen, die zwischen dem Client und dem Server gesendet werden. Wie in Abbildung 3.30 dargestellt, gehen wir davon aus, dass die Anfangssequenznummern für den Client bzw. den Server 42 und 79 sind. Wir erinnern uns, dass die Sequenznummer eines Segments diejenige des ersten Bytes im Datenfeld ist. Folglich hat das erste, vom Client gesendete Segment die Sequenznummer 42 und das erste vom Server gesendete die Sequenznummer 79. Wir wissen, dass die Bestätigungsnummer die Sequenznummer des nächsten Datenbyte ist, auf das der Host wartet. Nach dem Aufbau der TCP-Verbindung, jedoch vor dem Versenden von Daten, wartet der Client auf Byte 79 und der Server auf Byte 42. Wie Sie sehen, werden in Abbildung 3.30 drei Segmente gesendet. Das erste Segment wird vom Client zum Server gesendet; es enthält in seinem Datenfeld die 1-Byte-ASCII-Darstellung des Buchstabens ‚C’. Im ersten Segment steht auch 42 im Sequenznummernfeld, wie oben beschrieben. Da der Client noch keine Daten vom Server erhalten hat, steht im Bestätigungsnummernfeld dieses ersten Segments 79. Das zweite Segment wird vom Server zum Client gesendet. Es dient einem doppelten Zweck. Erstens stellt es eine Bestätigung für die Daten dar, die der Server empfangen hat. Durch Angabe der Nummer 43 im Bestätigungsfeld teilt der Server dem Client mit, dass er alles bis einschließlich Byte 42 gut empfangen hat und nun auf Byte 43 und folgende wartet. Der zweite Zweck dieses Segments ist die Rücksendung von ‚C’ als »Echo«. Folglich hat das zweite Segment in seinem Datenfeld die ASCIIDarstellung von ‚C’. Dieses zweite Segment trägt die Sequenznummer 79, also die Anfangssequenznummer des Datenflusses vom Server zum Client dieser TCP-Verbindung, weil es sich um das erste Datenbyte handelt, das der Server sendet. Man beachte, dass die Bestätigung für die Daten vom Client zum Server in einem Segment gesendet wird, das die Daten vom Server zum Client befördert. Aus diesem Grund wird dies als Huckepack (Piggyback) auf dem Datensegment vom Server zum Client bezeichnet. Das dritte Segment wird vom Client zum Server gesendet. Es erfüllt den ausschließlichen Zweck, die vom Server empfangenen Daten zu bestätigen. (Das zweite 3.5 Verbindungsorientierter Transport: TCP 219 Abbildung 3.30 Sequenz- und Bestätigungsnummern in einer einfachen Telnet-Anwendung über TCP Host B Host A Benutzer tippt 'C' Host bestätigt Empfang von 'C' Seq=42 , ACK= 79, Da ten='C ' ' en='C 3, Dat 4 = K C 9, A Seq=7 Host bestätigt Empfang von 'C' und gibt Echo zurück Seq=43 , ACK= 80 Zeit Segment enthält die Daten – den Buchstaben ‚C’ – vom Server zum Client.) Dieses Segment enthält ein leeres Datenfeld (d. h., die Bestätigung wird nicht huckepack mit den Daten vom Client zum Server gesendet). Das Segment hat 80 in seinem Bestätigungsnummernfeld, weil der Client den Bytestrom bis einschließlich zur Sequenznummer 79 empfangen hat und jetzt auf Byte 80 und folgende wartet. Möglicherweise mutet es seltsam an, dass dieses Segment auch eine Sequenznummer hat, obwohl es keine Daten enthält. Da TCP aber ein Sequenznummernfeld beinhaltet, muss das Segment irgendeine Sequenznummer haben. 3.5.5 Zuverlässiger Datentransfer Wie wir wissen, ist der Dienst auf der Internet-Vermittlungsschicht (IP-Dienst) unzuverlässig. IP sichert keine Übertragung von Datagrammen und auch keine geordnete Übertragung von Datagrammen und keine Integrität der Daten in den Datagrammen zu. Im IP-Dienst können Datagramme Router-Puffer zum Überlauf bringen und nie ihr Ziel erreichen, Datagramme können außer der Reihe ankommen und Bits im Datagramm können beschädigt (von 0 auf 1, und umgekehrt, umgedreht) werden. Da Segmente der Transportschicht mittels IP-Datagrammen in einem Netzwerk übertragen werden, können die Segmente der Transportschicht auch an diesen Problemen leiden. Kapitel 3 – Transportschicht 220 TCP setzt einen zuverlässigen Datentransferdienst auf den unzuverlässigen BestEffort-Dienst von IP auf. Der zuverlässige Datentransferdienst von TCP stellt sicher, dass der Datenstrom, den ein Prozess aus seinem TCP-Empfangspuffer liest, nicht beschädigt ist, keine Lücken hat, nicht dupliziert wurde und in der richtigen Sequenz vorliegt, d. h. mit dem vom Endsystem auf der anderen Seite der Verbindung gesendeten Bytestrom identisch ist. In diesem Abschnitt bieten wir eine Übersicht über die Bereitstellung eines zuverlässigen Datentransfers durch TCP. Wir werden sehen, dass der zuverlässige Datentransferdienst von TCP viele Prinzipien nutzt, die in Abschnitt 3.4 beschrieben wurden. Abbildung 3.31 zeigt die drei wichtigsten Ereignisse in Zusammenhang mit der Datenübertragung/Neuübertragung bei einem vereinfachten TCP-Sender. Wir gehen von einer TCP-Verbindung zwischen Host A und B aus und konzentrieren uns auf den Datenstrom, der von Host A zu Host B gesendet wird. Auf dem sendenden Host (A) werden Anwendungsdaten an TCP weitergegeben, das diese in Segmente rahmt und sie dann an IP weitergibt. Die Weitergabe von Daten von der Anwendung an TCP und die anschließende Rahmung und Übertragung eines Segments ist das erste wichtige Ereignis, das der TCP-Sender bewältigen muss. Jedes Mal, wenn TCP ein Segment an IP freigibt, startet es einen Timer für dieses Segment. Wenn dieser Timer abläuft, wird ein Interrupt-Ereignis in Host A erzeugt. TCP antwortet auf das Timeout-Ereignis – das zweite wichtige Ereignis, das der TCP-Sender behandeln muss – dadurch, dass es das Segment, das das Timeout verursacht hat, erneut überträgt. Abbildung 3.31 Vereinfachter TCP-Sender /*Angenommen, der Sender ist nicht durch die Fluss- oder Überlastkontrolle von TCP eingeschränkt, die Daten von oben sind größenmäßig kleiner als die MSS, und der Datenverkehr fließt nur in eine Richtung. */ sendbase=initial_sequence number /*siehe Abbildung 3.18*/ nextseqnum=initial_sequence number loop (forever) { switch event: (event) data received from application above create TCP segment with sequence number nextseqnum start timer for segment nextseqnum pass segment to IP nextseqnum=nextseqnum+length(data) break; /* Ende des Ereignisses “Daten von oben empfangen“ */ ➔ 3.5 Verbindungsorientierter Transport: TCP ➔ event: timer timeout for segment with sequence number y retransmit segment with sequence number y compute new timeout interval for segment y restart timer for sequence number y break; /* Ende des Timeout-Ereignisses */ event: ACK received, with ACK field value of y if (y > sendbase) {/* kumulative Bestätigung aller Daten bis y */ cancel all timers for segments with sequence numbers < y sendbase=y } else { /* Duplikatbestätigung für bereits bestätigtes Segment */ increment number of duplicate ACKs received for y if (number of duplicate ACKs received for y==3) { /* TCP Fast-Retransmit */ resend segment with sequence number y restart timer for segment y } break; /* Ende des Ereignisses “ACK empfangen“ */ } /* Ende der Endlos-Schleife */ Das dritte wichtige Ereignis, das der TCP-Sender behandeln muss, ist die Ankunft eines Bestätigungssegments (ACK) vom Empfänger (genauer, ein Segment, das im ACK-Feld einen gültigen Wert enthält). Hier muss das senderseitige TCP ermitteln, ob das ACK ein erstmaliges ACK für ein Segment, für das der Sender noch eine Bestätigung erhalten muss, oder ein so genanntes Duplikat-ACK ist, das ein Segment, für das der Sender bereits eine Bestätigung erhalten hat, nochmals bestätigt. Im Fall der Ankunft eines erstmaligen ACK weiß der Sender jetzt, dass alle Daten bis zu dem Byte, das bestätigt wird, korrekt beim Empfänger angekommen sind. Der Sender kann somit seine TCP-Zustandsvariable, welche die Sequenznummer des letzten Bytes, das bekanntlich korrekt und in der richtigen Reihenfolge beim Empfänger angekommen ist, aktualisieren. 221 Kapitel 3 – Transportschicht 222 PRINZIPIEN IN DER PRAXIS TCP bietet zuverlässigen Datenverkehr durch Verwendung positiver Bestätigungen und Timer mehr oder weniger auf die Art, die wir in Abschnitt 3.4 beschrieben haben. TCP bestätigt Daten, die korrekt empfangen wurden, und sendet Segmente erneut, wenn diese oder ihre jeweiligen Bestätigungen für verloren oder beschädigt gehalten werden. Bestimmte Versionen von TCP verfügen auch über einen impliziten NAK-Mechanismus. In Verbindung mit dem Fast-Retransmit-Mechanismus von TCP dient der Empfang dreier Duplikat-ACKs für ein bestimmtes Segment als implizites NAK für das anschließende Segment, wodurch die Neuübertragung dieses Segments vor dem Timeout ausgelöst wird. TCP verwendet Sequenznummern, damit der Empfänger verlorene oder duplizierte Segmente erkennen kann. Genauso wie im Fall unseres zuverlässigen Datentransferprotokolls, rdt3.0, kann TCP selbst nicht mit Sicherheit feststellen, ob ein Segment oder dessen ACK verloren gegangen ist, beschädigt wurde oder sich übermäßig verzögert hat. Beim Sender ist die TCP-Reaktion in jedem Fall gleich: Neuübertragung des fraglichen Segments. TCP wendet auch Pipelining an, so dass mehrere übertragene, aber noch nicht bestätigte Segmente zu irgendeinem Zeitpunkt beim Sender vorliegen können. Wir haben an früherer Stelle gesehen, dass sich der Durchsatz einer Sitzung durch Pipelining erheblich verbessern lässt, wenn das Verhältnis der Segmentgröße zur Roundtrip-Verzögerung klein ist. Die spezifische Anzahl anstehender unbestätigter Segmente beim Sender wird durch die Fluss- und Überlastkontrolle von TCP bestimmt. Die TCP-Flusskontrolle wird am Ende dieses Abschnitts und die TCP-Überlastkontrolle in Abschnitt 3.7 beschrieben. Vorläufig nehmen wir einfach zur Kenntnis, dass der TCP-Sender Pipelining nutzt. Tabelle 3.1 TCP-Empfehlungen für die ACK-Erzeugung [RFC 1122, RFC 2581] Ereignis Aktion des TCP-Empfängers Ankunft eines Segments in der richtigen Reihenfolge mit der erwarteten Sequenznummer. Alle Daten bis zur erwarteten Sequenznummer bereits bestätigt. Keine Lücken in den empfangenen Daten. Verzögertes ACK; maximal 500 ms auf Ankunft eines weiteren Segments in der richtigen Reihenfolge warten. ACK senden, falls das nächste richtig nummerierte Segment innerhalb dieses Intervalls nicht ankommt. Ankunft eines Segments in der richtigen Reihenfolge mit der erwarteten Sequenznummer. Ein weiteres Segment in der richtigen Reihenfolge wartet auf eine ACK-Übertragung. Keine Lücken in den empfangenen Daten. Sofort einzelnes kumulatives ACK senden, um beide Segmente mit der richtigen Reihenfolge zu bestätigen. Ankunft eines Segments außer der Reihe mit einer höheren als der erwarteten Sequenznummer. Lücke erkannt. Sofort Duplikat-ACK mit Angabe der Sequenznummer des nächsten erwarteten Bytes senden. Ankunft eines Segments, das die Lücke in den empfangenen Daten teilweise oder vollständig füllt. Sofort ACK senden, sofern dieses Segment mit dem unteren Ende der Lücke beginnt. 3.5 Verbindungsorientierter Transport: TCP Um die Reaktion des Senders auf ein Duplikat-ACK zu verstehen, muss man sich zuerst überlegen, warum der Empfänger überhaupt ein Duplikat-ACK sendet. Tabelle 3.1 enthält eine Übersicht über die TCP-Regeln für die ACK-Erzeugung durch Empfänger. Wenn ein TCP-Empfänger ein Segment mit einer Sequenznummer erhält, die größer als die nächste erwartete Sequenznummer in der richtigen Reihenfolge ist, erkennt er eine Lücke im Datenstrom, d. h. ein fehlendes Segment. Da TCP keine negativen Bestätigungen verwendet, kann der Empfänger keine explizite negative Bestätigung an den Sender zurückschicken. Vielmehr bestätigt er einfach das zuletzt in der richtigen Reihenfolge angekommene Datenbyte erneut (d. h., er erzeugt ein Duplikat-ACK dafür). Wenn der TCP-Sender drei Duplikat-ACKs für die gleichen Daten empfängt, geht er davon aus, dass das Segment, das dem dreimal bestätigten Segment folgt, verloren gegangen ist. In diesem Fall führt TCP ein Fast Retransmit [RFC 2581] durch, überträgt das fehlende Segment also schleunigst noch einmal, bevor der Timer dieses Segments abläuft. Einige interessante Szenarien Wir beenden diese Diskussion mit einem Blick auf ein paar einfache Szenarien. Bei dem Szenario in Abbildung 3.32 sendet Host A ein Segment an Host B. Es sei gegeben, dass dieses Segment die Sequenznummer 92 hat und 8 Datenbyte enthält. Nach dem Senden dieses Segments wartet Host A auf ein Segment von B mit der BestätiAbbildung 3.32 Neuübertragung aufgrund einer verlorenen Bestätigung Host A Host B Timeout Seq=92 , 8 Da tenbyt e 0 ACK=10 X Verlust Seq=92 , 8 Da tenbyt e 0 ACK=10 Zeit 223 224 Kapitel 3 – Transportschicht gungsnummer 100. Das Segment von A kommt bei B an, die Bestätigung von B nach A geht aber verloren. In diesem Fall läuft der Timer ab und Host A überträgt das gleiche Segment erneut. Wenn Host B die Neuübertragung empfängt, stellt er natürlich anhand der Sequenznummer fest, dass das Segment Daten enthält, die er bereits empfangen hat. Folglich verwirft TCP in Host B die Bytes im neu übertragenen Segment. Beim zweiten Szenario sendet Host A zwei aufeinander folgende Segmente. Das erste Segment hat die Sequenznummer 92 und 8 Datenbyte und das zweite die Sequenznummer 100 und 20 Datenbyte. Es sei gegeben, dass beide Segmente intakt bei B ankommen und B zwei getrennte Bestätigungen für jedes dieser Segmente sendet. Die erste dieser Bestätigungen hat die Bestätigungsnummer 100 und die zweite 120. Wir nehmen weiter an, dass keine der Bestätigungen bei Host A vor dem Timeout des ersten Segments ankommt. Wenn der Timer abläuft, sendet Host A das erste Segment mit Sequenznummer 92 noch einmal. Nun stellt sich die Frage, ob A auch das zweite Segment erneut sendet? Laut den oben beschriebenen Regeln sendet Host A das Segment nur noch einmal, wenn der Timer abläuft, bevor eine Bestätigung mit einer Bestätigungsnummer von 120 oder höher ankommt. Falls die zweite Bestätigung nicht verloren geht und vor dem Timeout des zweiten Segments ankommt, sendet A das zweite Segment nicht noch einmal (siehe Abbildung 3.33). Beim dritten und letzten Szenario nehmen wir an, dass Host A – wie im zweiten Beispiel – zwei Segmente sendet. Die Bestätigung des ersten Segments geht im Netzwerk verloren, doch genau vor dem Timeout des ersten Segments empfängt Host A eine Bestätigung mit Bestätigungsnummer 120. Host A weiß also, dass Host B alles bis Byte 119 empfangen hat. Host A sendet demnach keines der beiden Segmente noch einmal. Dieses Szenario ist in Abbildung 3.34 dargestellt. Wir wissen aus dem vorherigen Abschnitt, dass TCP ein Protokoll im Stil GoBack-N ist. Das ist deshalb so, weil Bestätigungen kumulativ sind und korrekt empfangene, jedoch außer der Reihe ankommende Segmente nicht einzeln vom Empfänger bestätigt werden. Wie in Abbildung 3.31 dargestellt (siehe hierzu auch Abbildung 3.18), muss der TCP-Sender also nur die kleinste Sequenznummer eines übertragenen, aber unbestätigten Bytes (sendbase) und die Sequenznummer des als Nächstes zu sendenden Bytes (nextseqnum) verfolgen. Man beachte aber, dass TCP ungeachtet der Ähnlichkeit seiner zuverlässigen Datentransferkomponente mit Go-Back-N keinesfalls eine reine Implementierung von Go-Back-N ist. Um einige wichtige Unterschiede zwischen TCP und Go-Back-N herauszustellen, betrachte man, was passiert, wenn der Sender eine Sequenz von Segmenten 1, 2, …, N sendet und alle Segmente in der richtigen Reihenfolge ohne Fehler beim Empfänger ankommen. Weiter nehmen wir an, dass die Bestätigung für Paket n < N verloren geht, die übrigen N – 1 Bestätigungen beim Sender aber vor dem jeweiligen Timeout ankommen. Bei diesem Beispiel würde Go-Back-N nicht nur Paket n, sondern auch alle nachfolgenden Pakete n + 1, n + 2, …, N erneut übertragen. TCP würde demgegenüber höchstens ein Segment, nämlich Segment n, noch einmal übertragen. Außerdem würde TCP nicht einmal Segment n erneut übertragen, wenn die Bestätigung für Segment n + 1 vor dem Timeout für Segment n ankäme. In mehreren neueren Vorschlägen [RFC 2018; Fall 1996; Mathis 1996] für die Erweiterung des Bestätigungsschemas von TCP wird stärker auf ein SelectiveRepeat-Protokoll gesetzt. Die Kernidee in diesen Vorschlägen ist die Bereitstellung expliziter Informationen für den Sender darüber, welche Segmente korrekt empfangen wurden und welche beim Empfänger noch fehlen. 3.5 Verbindungsorientierter Transport: TCP Abbildung 3.33 Segment wird nicht erneut übertragen, weil seine Bestätigung vor dem Timeout ankommt. Host B Seq=92 , 8 Da tenbyt e Seq=10 0, 20 Datenb yte seq=92 Timeout seq=100 Timeout Host A 00 =1 K AC 120 K= AC Seq =92 , 8 Dat enb yte 0 ACK=12 Zeit 3.5.6 Flusskontrolle Wir erinnern uns, dass die Hosts auf beiden Seiten einer TCP-Verbindung jeweils einen Empfangspuffer für die Verbindung vorhalten. Wenn die TCP-Verbindung Bytes korrekt und in der richtigen Sequenz empfängt, werden die Daten im Empfangspuffer abgestellt. Der damit verbundene Anwendungsprozess liest die Daten aus diesem Puffer, aber nicht unbedingt in dem Augenblick, in dem sie ankommen. Möglicherweise ist die empfangende Anwendung mit einer anderen Aufgabe beschäftigt und versucht erst lange Zeit nach Ankunft der Daten, diese zu lesen. Wenn die Anwendung die Daten relativ langsam liest, kann der Sender sehr leicht den Empfangspuffer der Verbindung zum Überlauf bringen, falls er zu viele Daten zu schnell sendet. TCP bietet seinen Anwendungen deshalb eine Flusskontrolle, um die Möglichkeit auszuschließen, dass der Sender den Puffer des Empfängers überschwemmt. Flusskontrolle ist folglich ein Dienst zur Abstimmung der Geschwindigkeit bzw. Anpassung der Rate, in der der Sender überträgt, auf die Rate, in der die empfangende Anwendung liest. Wie bereits erwähnt, kann ein TCP-Sender auch aufgrund einer Überlast im IP-Netzwerk gedrosselt werden. Diese Form der Senderkontrolle wird als Überlastkontrolle bezeichnet (siehe Abschnitte 3.6 und 3.7). Obwohl 225 Kapitel 3 – Transportschicht 226 Abbildung 3.34 Durch eine kumulative Bestätigung wird die Neuübertragung des ersten Segments vermieden. Seq=9 2, 8 Daten byte 00 ACK=1 Seq=1 00, X 20 Da tenby te 20 ACK=1 Zeit Zeit sich die von der Fluss- und Überlaufkontrolle unternommenen Aktionen (Drosseln des Senders) ähneln, dienen sie natürlich unterschiedlichen Zwekken. Leider verwenden viele Autoren die beiden Begriffe gleichbedeutend, so dass nur der sachkundige Leser mit viel Sorgfalt die beiden Fälle aus dem Zusammenhang heraus unterscheiden kann. Wir beschreiben im Folgenden zuerst die von TCP bereitgestellte Flusskontrolle. TCP stellt Flusskontrolle dadurch bereit, dass es den Sender eine Variable verwalten lässt, die man als Empfangsfenster (Receive Window) bezeichnet. Informell wird das Empfangsfenster benutzt, um dem Sender eine Vorstellung davon zu vermitteln, wie viel freier Pufferplatz beim Empfänger zur Verfügung steht. In einer Vollduplexverbindung verwaltet der Sender auf jeder Seite der Verbindung ein eigenes Empfangsfenster. Das Empfangsfenster ist dynamisch, d. h., es ändert sich im Verlauf einer Verbindung. Wir untersuchen das Empfangsfenster in Zusammenhang mit einem Filetransfer als Beispiel. Angenommen, Host A sendet eine große Datei über eine TCP-Verbindung an Host B. Host B weist dieser Verbindung einen Empfangspuffer zu; dessen Größe sei durch RcvBuffer gegeben. Von Zeit zu Zeit liest der Anwendungsprozess in Host B aus dem Puffer. Wir definieren die folgenden Variablen: 3.5 Verbindungsorientierter Transport: TCP 227 õ LastByteRead = Nummer des letzten Bytes im Datenstrom, das von dem Anwendungsprozess in B aus dem Puffer gelesen wird. õ LastByteRcvd = Nummer des letzten Bytes im Datenstrom, das vom Netzwerk angekommen ist und in den Empfangspuffer bei B gestellt wurde. Da es TCP nicht gestattet ist, den zugeteilten Puffer zum Überlauf zu bringen, benötigen wir: LastByteRcvd – LastByteRead <= RcvBuffer Das als RcvWindow bezeichnete Empfangsfenster wird auf die Menge des freien Platzes im Puffer gesetzt: RcvWindow = RcvBuffer – [LastByteRcvd – LastByteRead] Da sich der freie Platz im Verlauf der Zeit ändert, ist RcvWindow dynamisch. Die Variable RcvWindow ist in Abbildung 3.35 dargestellt. Abbildung 3.35 Das Empfangsfenster (RcvWindow) und der Empfangspuffer (RcvBuffer) RcvWindow Daten von IP Freier Platz TCPDaten im Puffer Anwendungsprozess RcvBuffer Wie benutzt die Verbindung die Variable RcvWindow, um den Flusskontrolldienst bereitzustellen? Host B teilt Host A mit, wie viel freier Platz sich im Verbindungspuffer befindet, indem er seinen aktuellen Wert von RcvWindow in das Fensterfeld jedes an A gesendeten Segments einfügt. Anfangs setzt Host B RcvWindow = RcvBuffer. Damit dies gelingt, muss Host B natürlich mehrere verbindungsspezifische Variablen verfolgen. Host A verwaltet seinerseits die beiden Variablen LastByteSent (letztes gesendetes Byte) und LastByteAcked (letztes bestätigtes Byte). Der Unterschied zwischen diesen beiden Variablen ist die Menge der unbestätigten Daten, die A in die Verbindung gespeist hat. Dadurch, dass die Menge der unbestätigten Daten unter dem Wert von RcvWindow gehalten wird, kann Host A sicher sein, dass er den Empfangspuffer in Host B nicht zum Überlaufen bringt. Somit stellt Host A während der gesamten Lebensdauer der Verbindung Folgendes sicher: LastByteSent – LastByteAcked ≤ RcvWindow 228 Kapitel 3 – Transportschicht Bei diesem Schema ergibt sich ein kleines technisches Problem. Um es zu verdeutlichen, nehmen wir an, der Empfangspuffer von Host B füllt sich, so dass RcvWindow = 0. Nach dem Advertising von RcvWindow = 0 an Host A gehen wir ferner davon aus, dass B nichts an A zu senden hat. Während der Anwendungsprozess in B den Puffer leert, sendet TCP keine neuen Segmente mit neuen Empfangsfenstern an Host A. Das heißt, TCP sendet nur dann ein Segment an Host A, wenn Daten zum Senden anstehen oder wenn es eine Bestätigung senden muss. Deshalb wird Host A nie darüber informiert, dass im Empfangspuffer von Host B wieder Platz freigeworden ist: Host A ist blockiert und kann keine Daten mehr übertragen! Um dieses Problem zu lösen, verlangt die TCP-Spezifikation von Host A, das Senden von Segmenten mit jeweils einem Datenbyte fortzusetzen, wenn das Empfangsfenster von B Null ist. Diese Segmente werden vom Empfänger bestätigt. Letztendlich beginnt sich der Puffer zu leeren und die Bestätigungen werden einen RcvWindow-Wert von nicht Null enthalten. Nach der Beschreibung der TCP-Flusskontrolle sei an dieser Stelle kurz erwähnt, dass UDP keine Flusskontrolle bietet. Um den Unterschied hier hervorzuheben, betrachte man das Senden einer Reihe von UDP-Segmenten von einem Prozess in Host A an einen Prozess in Host B. Bei einer typischen UDP-Implementierung hängt UDP die Segmente (bzw. genauer gesagt, die Daten in den Segmenten) an eine Warteschlange mit endlicher Größe an, die dem entsprechenden Socket (der Tür zum Prozess) »vorangestellt« ist. Der Prozess liest jeweils ein ganzes Segment aus der Warteschlange. Wenn der Prozess die Segmente nicht schnell genug aus der Warteschlange liest, läuft die Warteschlange über und Segmente gehen verloren. Zu diesem Abschnitt bieten wir (in der Online-Version dieses Buchs) ein interaktives Java-Applet, das wichtige Einblicke in TCP-Empfangsfenster gewährt. 3.5.7 Roundtrip-Zeit und Timeout Wenn ein Host ein Segment in eine TCP-Verbindung einspeist, startet er einen Timer. Läuft der Timer ab, bevor der Host eine Bestätigung für die im Segment gesendeten Daten empfängt, überträgt der Host das Segment erneut. Die Zeit vom Start bis zum Ablauf des Timers bezeichnet man als Timeout. Eine natürliche Frage ist, wie groß das Timeout sein soll. Selbstverständlich sollte es größer als die Roundtrip-Zeit der Verbindung sein, d. h. die Zeit zwischen dem Senden eines Segments und seiner Bestätigung. Andernfalls würden unnötige Neuübertragungen gesendet werden. Das Timeout sollte andererseits aber nicht viel größer als die Roundtrip-Zeit sein, sonst würde TCP beim Verlust eines Segments dieses nicht schnell genug erneut übertragen und dadurch beträchtliche Datentransferverzögerungen in die Anwendung einführen. Bevor wir das Timeout-Intervall ausführlicher beschreiben, befassen wir uns eingehender mit der Roundtrip-Zeit (RTT). Die folgende Beschreibung basiert auf einer TCP-Arbeit in [Jacobson 1988]. Abschätzung der durchschnittlichen Roundtrip-Zeit Die als SampleRTT bezeichnete Muster-RTT für ein Segment ist die Zeit zwischen dem Senden des Segments (d. h. Weitergabe an IP) und dem Empfang einer Bestätigung für das Segment. Jedes gesendete Segment hat seine eigene SampleRTT. Natürlich schwanken die SampleRTT-Werte je nach Überlast in den Routern und verschiedenen Lasten in den Endsystemen von einem Segment zum anderen. Aufgrund dieser Schwankungen kann ein gegebener SampleRTT-Wert untypisch sein. Um eine 3.5 Verbindungsorientierter Transport: TCP typische RTT zu schätzen, ist es deshalb ganz natürlich, die eine oder andere Art von Durchschnitt der SampleRTT-Werte heranzuziehen. TCP verwendet eine Durchschnittszeit, EstimatedRTT, der SampleRTT-Werte. Beim Empfang einer Bestätigung und Erhalt einer neuen SampleRTT aktualisiert TCP die EstimatedRTT gemäß folgender Formel: EstimatedRTT = (1 – x) · EstimatedRTT + x · SampleRTT Die obige Formel ist in der Form einer Anweisung für Programmiersprachen geschrieben: Der neue Wert von EstimatedRTT ist eine gewichtete Kombination des vorherigen Werts von EstimatedRTT und des neuen Werts von SampleRTT. Ein typischer Wert von x ist x = 0,125 (d. h. 1/8); in diesem Fall lautet die obige Formel: EstimatedRTT = 0,875 EstimatedRTT + 0,125 · SampleRTT Man beachte, dass EstimatedRTT einen gewichteten Durchschnitt der SampleRTTWerte darstellt. Wie wir in den Wiederholungsfragen am Ende des Kapitels sehen werden, legt dieser gewichtete Durchschnitt mehr Gewicht auf neuere statt ältere Muster. Das ist ganz natürlich, denn die neueren Muster spiegeln die aktuelle Überlast im Netzwerk besser wider. In der Statistik wird ein solcher Durchschnitt als Exponential Weighted Moving Average (EWMA) bezeichnet. Das Wort »exponentiell« erscheint in EWMA, weil das Gewicht einer bestimmten SampleRTT exponentiell schneller verfällt, als die Aktualisierungen erfolgen. In den Übungen am Ende dieses Kapitels werden Sie gebeten, den exponentiellen Term in EstimatedRTT abzuleiten. Abbildung 3.36 zeigt die SampleRTT-Werte (gepunktete Linie) und die EstimatedRTT (durchgezogene Linie) für einen Wert von x = 1/8 für eine TCP-Verbindung zwischen void.cs.umass.edu (in Amherst, Massachusetts) und maria.wustl. edu (in St. Louis, Missouri). Die Schwankungen in der SampleRTT wurden in der Berechnung der EstimatedRTT geglättet. Abbildung 3.36 RTT-Muster (Samples) und RTT-Durchschnitt (Average) 229 Kapitel 3 – Transportschicht 230 Setzen eines Timeout Das Timeout sollte so gesetzt werden, dass ein Timer nur in seltenen Fällen früh vor der verzögerten Ankunft der Bestätigung eines Segments abläuft. Naturgemäß setzt man das Timeout gleich der EstimatedRTT zuzüglich eines gewissen Toleranzspielraums. Der Spielraum sollte ausreichend groß sein, wenn mit starker Fluktuation in den SampleRTT-Werten zu rechnen ist. Andererseits sollte er bei geringer Fluktuation klein sein. TCP verwendet die folgende Formel: Timeout = EstimatedRTT + 4·Abweichung wobei Abweichung eine Schätzung dessen ist, um wie viel SampleRTT normalerweise von EstimatedRTT abweicht: Abweichung = (1 – x) ⋅ Abweichung + x ⋅ |SampleRTT – EstimatedRTT| Man beachte, dass Abweichung ein EWMA dessen ist, um wie viel SampleRTT von EstimatedRTT abweicht. Wenn die SampleRTT-Werte wenig Fluktuation aufweisen, dann ist Abweichung klein und Timeout kaum größer als EstimatedRTT. Ist die Fluktuation dagegen groß, ist Abweichung groß und Timeout viel größer als EstimatedRTT. »A Quick Tour around TCP« [Cela 2000] enthält hervorragende interaktive Applets für Schätzungen der RTT-Varianz. 3.5.8 TCP-Verbindungsmanagement In diesem Abschnitt beschreiben wir ausführlich den Auf- und Abbau einer TCP-Verbindung. Das Thema mag zwar nicht sonderlich aufregend erscheinen, ist aber wichtig, weil der Aufbau einer TCP-Verbindung die wahrgenommenen Verzögerungen (z. B. beim Surfen im Web) deutlich erhöhen kann. Wir nehmen als Beispiel an, dass ein Prozess in einem Host (Client) eine Verbindung zu einem anderen Prozess in einem anderen Host (Server) aufbauen möchte. Der Anwendungsprozess im Client informiert zuerst das Client-TCP, dass er eine Verbindung zu einem Prozess im Server aufbauen möchte. Das TCP im Client fährt dann mit dem Aufbau einer TCP-Verbindung zum TCP im Server wie folgt fort: õ Schritt 1: Das clientseitige TCP sendet zuerst ein spezielles TCP-Segment an das serverseitige TCP. Dieses spezielle Segment enthält keine Anwendungsdaten. Eines der Flag-Bits im Segment-Header (siehe Abbildung 3.28), das so genannte »SYN-Bit«, ist aber auf 1 gesetzt. Aus diesem Grund gilt dieses spezielle Segment als SYN-Segment. Darüber hinaus wählt der Client eine anfängliche Sequenznummer (client_isn) und setzt diese Nummer im Sequenznummernfeld auf das anfängliche SYN-Segment. Dieses Segment wird in einem IP-Datagramm verkapselt und an den Server gesendet. õ Schritt 2: Nachdem das IP-Datagramm mit dem SYN-Segment beim Server-Host ankommt (wir gehen einfach einmal davon aus!), extrahiert der Server das SYNSegment aus dem Datagramm, weist der Verbindung TCP-Puffer und Variablen zu und sendet ein Verbindung-gewährt-Segment an das Client-TCP. Dieses Verbindung-gewährt-Segment enthält ebenfalls keine Anwendungsdaten, dafür aber drei wichtige Informationen im Segment-Header. Erstens ist das SYN-Bit auf 1 gesetzt. Zweitens ist das Bestätigungsfeld auf client_isn+1 gesetzt. Und drittens wählt der Server seine eigene Anfangssequenznummer (server_isn) und setzt diesen Wert in das Sequenznummernfeld des Segment-Headers. Dieses Verbindung-gewährt-Segment besagt: »Ich habe dein SYN-Paket empfangen, um eine 3.5 Verbindungsorientierter Transport: TCP 231 Verbindung mit deiner Anfangssequenznummer, client_isn, zu starten. Ich bin einverstanden, diese Verbindung aufzubauen. Meine eigene Anfangssequenznummer lautet server_isn.« Das Verbindung-gewährt-Segment wird als SYNACK-Segment bezeichnet. õ Schritt 3: Beim Empfang des SYNACK-Segments weist der Client seinerseits der Verbindung Puffer und Variablen zu. Anschließend sendet der Client-Host dem Server noch ein Segment, mit dem er das SYNACK-Segment des Servers bestätigt (hierfür setzt der Client den Wert server_isn+1 in das Bestätigungsfeld des TCPSegment-Headers). Das SYN-Bit wird auf 0 gesetzt, weil die Verbindung inzwischen steht. Sind diese Schritte vollzogen, können der Client- und der Server-Host einander Segmente mit Daten zusenden. In jedem dieser Segmente wird das SYN-Bit auf Null gesetzt. Für den Aufbau der Verbindung werden also drei Pakete zwischen zwei Hosts gesendet (siehe Abbildung 3.37). Aus diesem Grund wird dieser Verbindungsaufbau als Drei-Wege-Handshake bezeichnet. In den Übungen werden mehrere Aspekte des Drei-Wege-Handshake von TCP untersucht. (Warum sind Anfangssequenznummern erforderlich? Warum genügt kein Zwei-Wege-Handshake?) Abbildung 3.37 Segmentaustausch im Drei-Wege-Handshake von TCP Client-Host Server-Host Verbindu ngsanfra ge(SYN=1, seq=client _isn) sn, i _ r e erv seq=s YN=1, S ( t r +1) h _isn g ewä ient ung l d c n = i a ck Verb ACK(S YN=0, seq=c lient ack=s _isn+ erver 1) _isn+ 1) Zeit Zeit Was lange währt, muss schließlich auch einmal zu einem Ende kommen, und so ist es auch mit einer TCP-Verbindung. Beide an einer TCP-Verbindung teilnehmenden Prozesse können die Verbindung beenden. Bei Beendigung einer Verbindung wird die Zuweisung der »Ressourcen« (Puffer und Variablen) in den Hosts wieder aufgehoben. Als Beispiel nehmen wir an, dass der Client die Verbindung schließen möchte, wie in Abbildung 3.38 dargestellt. Der Anwendungsprozess des Clients gibt einen Kapitel 3 – Transportschicht Close-Befehl aus. Dies veranlasst das Client-TCP, ein spezielles TCP-Segment an den Server-Prozess zu senden. In diesem speziellen Segment ist das FIN-Bit im SegmentHeader (siehe Abbildung 3.38) auf 1 gesetzt. Empfängt der Server dieses Segment, sendet er dem Client ein Bestätigungssegment. Anschließend sendet der Server sein eigenes Shutdown-Segment, in dem das FIN-Bit auf 1 gesetzt ist. Schließlich bestätigt der Client das Shutdown-Segment des Servers. An diesem Punkt werden sämtliche zugewiesenen Ressourcen in den beiden Hosts wieder freigegeben. Abbildung 3.38 Schließen einer TCP-Verbindung Client Close Server FIN ACK FIN Close ACK Wartezeit 232 geschlossen Zeit Während der Lebensdauer einer TCP-Verbindung geht das TCP-Protokoll in jedem der beiden Hosts in verschiedene TCP-Zustände über. Abbildung 3.39 zeigt eine typische Abfolge von TCP-Zuständen, die vom Client-TCP durchschritten werden. Das Client-TCP beginnt im Zustand CLOSED. Die Anwendung auf der Client-Seite leitet eine neue TCP-Verbindung (durch Erzeugen eines Socket-Objekts, wie in unseren Java-Beispielen in Kapitel 2) ein. Dies veranlasst das TCP im Client, ein SYN-Segment an das TCP im Server zu senden. Anschließend geht das Client-TCP in den Zustand SYN_SENT über. Es wartet auf ein Segment vom Server-TCP, das eine Bestätigung für das vorherige Segment des Clients mit dem auf 1 gesetzten SYN-Bit enthält. Nach dem Empfang eines solchen Segments geht das Client-TCP in den Zustand ESTABLISHED über. In diesem Zustand kann der TCP-Client TCP-Segmente senden und empfangen, die Nutzdaten (d. h. von der Anwendung erzeugte Daten) enthalten. Angenommen, die Client-Anwendung möchte die Verbindung beenden. (Ebenso könnte das aber auch der Server sein.) Dies veranlasst das Client-TCP zum Versenden eines TCP-Segments, in dem das FIN-Bit auf 1 gesetzt ist, und zum Übergang in den 3.5 Verbindungsorientierter Transport: TCP Abbildung 3.39 Typische Abfolge von TCP-Zuständen eines TCP-Clients Client-Anwendung leitet Verbindungsaufbau ein 30 Sekunden warten CLOSED SYN senden TIME_WAIT SYN_SENT SYN und ACK empfangen, ACK senden FIN empfangen, ACK senden FIN_WAIT_2 ESTABLISHED FIN senden ACK empfangen, nichts senden FIN_WAIT_1 Client-Anwendung leitet Verbindungsabbau ein Zustand FIN_WAIT_1. In diesem Zustand wartet das Client-TCP auf ein TCP-Segment vom Server mit einer Bestätigung. Wenn das Client-TCP dieses Segment empfängt, wechselt es in den Zustand FIN_WAIT_2. In diesem Zustand wartet der Client auf ein weiteres Segment vom Server, in dem das FIN-Bit auf 1 gesetzt ist. Nach dem Empfang dieses Segments bestätigt das Client-TCP das Segment des Servers und wechselt in den Zustand TIME_WAIT. In diesem Zustand sendet der TCP-Client die letzte Bestätigung noch einmal, falls das ACK verloren geht. Die Dauer des Verharrens im Zustand TIME_WAIT hängt von der Implementierung ab; übliche Werte sind 30 Sekunden, 1 Minute und 2 Minuten. Nach Ablauf der Wartezeit wird die Verbindung formell geschlossen und alle Ressourcen auf der Client-Seite (einschließlich Portnummern) werden freigegeben. Abbildung 3.40 zeigt den Ablauf der Zustände, die das serverseitige TCP im typischen Fall unter der Annahme durchläuft, dass der Client den Abbau der Verbindung einleitet. Die Übergänge sind selbst erklärend. In diesen beiden Zustandsübergangsdiagrammen stellen wir nur dar, wie eine TCP-Verbindung normalerweise auf- und abgebaut wird. Wir beschreiben nicht, was in bestimmten pathologischen Szenarien passiert, wenn beispielsweise beide Seiten einer Verbindung gleichzeitig beenden wollen. Wenn Sie daran interessiert sind, mehr darüber und zu weiteren Fragen in Zusammenhang mit TCP zu erfahren, empfiehlt sich Stevens Buch [Stevens 1994]. Dies beschließt unsere Einführung in TCP. In Abschnitt 3.7 werden wir zu TCP zurückkehren und die TCP-Kontrolle der Netzwerküberlastung eingehend behandeln. Zuvor jedoch gehen wir einen Schritt zurück und untersuchen Einzelheiten der Kontrolle der Netzwerküberlastung in einem breiteren Kontext. 233 Kapitel 3 – Transportschicht 234 Abbildung 3.40 Typische Sequenz von TCP-Zuständen, die ein serverseitiges TCP durchläuft ACK empfangen, nichts senden Server-Anwendung erzeugt ein Listen-Socket CLOSED LAST_ACK LISTEN SYN empfangen, SYN und ACK senden FIN senden CLOSE_WAIT FIN empfangen, ACK senden SYN_RCVD ESTABLISHED ACK empfangen, nichts senden 3.6 Grundlagen der Überlastkontrolle In den vorherigen Abschnitten wurden die allgemeinen Prinzipien und spezifischen TCP-Mechanismen beschrieben, mit denen TCP im Fall eines Paketverlustes einen zuverlässigen Datentransferdienst bereitstellen kann. Wir erwähnten auch, dass ein solcher Verlust in der Praxis durch den Überlauf von Router-Puffern in einem überlasteten Netzwerk entsteht. Die Neuübertragung von Paketen behandelt somit ein Symptom (Verlust eines spezifischen Segments auf der Transportschicht), nicht aber die Ursache einer Netzwerküberlast – zu viele Quellen versuchen, Daten in einer zu hohen Rate zu senden. Für die Behandlung der Ursache einer Netzwerküberlast sind Mechanismen erforderlich, um Sender angesichts der Netzwerküberlast zu drosseln. Dieser Abschnitt befasst sich mit dem Problem der Überlastkontrolle in einem allgemeinen Zusammenhang, wobei wir untersuchen, warum Überlast »schlecht« ist, wie sich Netzwerküberlast auf die Leistung für die höherschichtigen Anwendungen auswirkt und welche Ansätze umgesetzt werden können, um Netzwerküberlast zu vermeiden oder darauf zu reagieren. Diese eher allgemeine Untersuchung von Überlastkontrolle erscheint angemessen, weil sie beim zuverlässigen Datentransfer ganz oben auf der »Hitliste« der grundlegenden Netzwerkprobleme steht. Wir beenden diesen Abschnitt mit einer Diskussion der Überlastkontrolle im ABR-Dienst in ATMNetzwerken (Asynchronous Transfer Mode). Der anschließende Abschnitt befasst sich ausführlich mit dem Überlastkontrollalgorithmus von TCP. 3.6.1 Ursachen und Kosten einer Überlast Wir beginnen unsere allgemeine Untersuchung der Überlastkontrolle mit einer eingehenden Betrachtung dreier zunehmend komplexer Szenarien, in denen Überlast ent- 3.6 Grundlagen der Überlastkontrolle 235 steht. In jedem Fall untersuchen wir die Gründe für die Überlast und welche Kosten (hinsichtlich nicht voll ausgelasteter Ressourcen und schlechter Leistung aus Sicht der Endsysteme) dadurch entstehen. Szenario 1: Zwei Sender, ein Router mit unendlichen Puffern Dieses Szenario ist das wohl einfachste, das man sich vorstellen kann: Zwei Hosts (A und B) verfügen jeweils über eine Verbindung, die einen einzigen Hop zwischen Quelle und Ziel gemeinsam nutzt (siehe Abbildung 3.41). Abbildung 3.41 Zwei Verbindungen nutzen einen einzigen Hop mit unendlichen Puffern gemeinsam. Host A Host B λin: Originaldaten λout λ’in: Originaldaten Router mit unendlichen Puffern Angenommen, die Anwendung in Host A sendet Daten über die Verbindung (z. B. Weiterleitung von Daten über ein Socket an das Protokoll der Transportschicht) in einer durchschnittlichen Rate von λin Byte/s. Diese Daten sind in dem Sinn »Originale«, als jede Dateneinheit nur einmal in das Socket gespeist wird. Das zugrunde liegende Transportprotokoll ist sehr einfach. Die Daten werden verkapselt und gesendet; es wird keine Wiederherstellung nach einem Fehler (z. B. Neuübertragung), Flusskontrolle oder Überlastkontrolle durchgeführt. Host B arbeitet auf ähnliche Weise und wir nehmen der Einfachheit halber an, dass er ebenfalls in einer Rate von λin Byte/s sendet. Die Pakete von Host A und B fließen weiter an einen Router und über eine gemeinsam genutzte Ausgangsverbindung mit Kapazität R. Der Router verfügt über Puffer, in denen ankommende Pakete gespeichert werden können, wenn die Ankunftsrate der Pakete die Kapazität der Ausgangsverbindungsleitung übersteigt. In diesem ersten Szenario gehen wir davon aus, dass der Router einen unendlichen Pufferplatz hat. Abbildung 3.42 zeigt die Leistung der Verbindung von Host A im ersten Szenario. Der linke Graph stellt den per-Verbindung-Durchsatz (Anzahl Byte pro Sekunde beim Empfänger) als Funktion der Senderate der Verbindung dar. Bei einer Senderate zwischen 0 und R/2 ist der Durchsatz beim Empfänger gleich der Senderate des Senders; alles, was der Sender sendet, wird beim Empfänger mit einer endlichen Verzö- Kapitel 3 – Transportschicht 236 gerung empfangen. Wenn die Senderate allerdings über R/2 liegt, beträgt der Durchsatz nur R/2. Diese Obergrenze des Durchsatzes ist eine Konsequenz der gemeinsamen Nutzung der Leitungskapazität durch zwei Verbindungen. Die Verbindungsleitung kann einfach keine Pakete in einer Dauerrate, die R/2 übersteigt, an einen Empfänger durchreichen. Gleichgültig, wie hoch die Rate ist, in der die Hosts A und B senden, sie werden jeweils nie einen Durchsatz von mehr als R/2 erleben. Abbildung 3.42 Überlastszenario 1: Durchsatz und Verzögerung als Funktion der HostSenderate λ out Verzögerung R/2 λ′in (a) R/2 λ′in R/2 (b) Die Erreichung eines per-Verbindung-Durchsatzes von R/2 mag tatsächlich wie eine »gute Sache« erscheinen, da die Verbindungsleitung mit der Übertragung von Paketen an ihre Ziele voll ausgelastet ist. Der rechte Graph in Abbildung 3.42 zeigt allerdings die Konsequenzen des Betriebs nahe der Verbindungsleitungskapazität. Je mehr sich die Senderate R/2 (von links) nähert, desto höher liegt die durchschnittliche Verzögerung. Wenn die Senderate R/2 übersteigt, ist die durchschnittliche Anzahl der in der Warteschlange des Routers anstehenden Pakete unbegrenzt und die durchschnittliche Verzögerung zwischen Quelle und Ziel geht gegen unendlich (unter der Annahme, dass die Verbindungen über eine unendliche Dauer in diesen Senderaten laufen). Während also ein Gesamtdurchsatz von nahe R aus Sicht des Durchsatzes ideal sein mag, ist dies aus Sicht der Verzögerung weit davon entfernt. Sogar bei diesem (extrem) idealisierten Szenario können wir einen Kostenfaktor eines überlasteten Netzwerks feststellen: Mit zunehmender Annäherung der Ankunftsrate von Paketen an die Verbindungsleitungskapazität erhöhen sich die Warteschlangenverzögerungen. Szenario 2: Zwei Sender, ein Router mit endlichen Puffern Wir modifizieren jetzt Szenario 1 in zweierlei Hinsicht (siehe Abbildung 3.43): Erstens gehen wir davon aus, dass die Puffer in den Routern endlich sind. Zweitens nehmen wir an, dass jede Verbindung zuverlässig ist. Wenn ein Paket, in dem ein Segment der Transportschicht enthalten ist, beim Router verworfen wird, überträgt es der Sender 3.6 Grundlagen der Überlastkontrolle 237 irgendwann erneut. Da also Pakete erneut übertragen werden können, müssen wir jetzt mit dem Begriff »Senderate« sorgfältiger umgehen. Insbesondere ist die Rate, in der die Anwendung Originaldaten an das Socket sendet, als λin Byte/s gegeben. Die Rate, in der die Transportschicht Segmente (in denen sich Originaldaten oder erneut übertragene Daten befinden) in das Netzwerk einspeist, ist als λ′in Byte/s gegeben. λ′in wird auch als die dem Netzwerk angebotene Last (offered load) bezeichnet. Abbildung 3.43 Szenario 2: zwei Hosts (mit Neuübertragungen) und ein Router mit endlichen Puffern Host A Host B λin: Originaldaten λ‘in: Original- und erneut übertragene Daten Host C λout Host D Router mit endlichen Puffern Die in Szenario 2 realisierbare Leistung hängt jetzt stark davon ab, wie die Neuübertragung ausgeführt wird. Erstens betrachte man den unrealistischen Fall, dass Host A irgendwie (magisch!) feststellen kann, ob im Router ein Puffer frei ist und folglich nur ein Paket sendet, falls einer frei ist. In diesem Fall würde kein Verlust entstehen, λin würde jetzt λ′in entsprechen und der Durchsatz der Verbindung wäre gleich λin. Dieser Fall ist durch die obere Kurve in Abbildung 3.44 (a) dargestellt. Aus Sicht des Durchsatzes ist die Leistung ideal; alles, was gesendet wird, wird empfangen. Die durchschnittliche Senderate des Hosts kann in diesem Szenario aber R/2 nicht übersteigen, weil ja davon ausgegangen wird, dass nie ein Paket verloren geht. Man betrachte nun den etwas realistischeren Fall, bei dem der Sender nur eine Neuübertragung durchführt, wenn mit Sicherheit feststeht, dass ein Paket verloren gegangen ist. (Wiederum ist diese Annahme ein bisschen weit hergeholt. Allerdings kann der sendende Host sein Timeout ausreichend hoch ansetzen, so dass praktisch mit Sicherheit feststeht, dass ein Paket, das nicht bestätigt wird, verloren gegangen ist.) In diesem Fall könnte die Leistung eher wie in Abbildung 3.44 (b) aussehen. Um zu verstehen, was hier passiert, betrachte man den Fall, bei dem die angebotene Last, λ′in (die Rate der Originaldatenübertragung zuzüglich Neuübertragungen), 0,5R entspricht. Gemäß Abbildung 3.44 (b) ist die Rate, in der Daten zur Empfängeranwendung übertragen werden, bei dieser angebotenen Last R/3. Folglich sind von den insgesamt 0,5R übertragenen Dateneinheiten (im Durchschnitt) 0,333R Byte/s Originaldaten und 0,166R Byte/s neu übertragene Daten. Wir sehen hier einen weiteren Kostenfaktor eines überlasteten Netzwerks: Der Sender muss Neuübertragungen durchführen, um verworfene (verlorene) Pakete aufgrund eines Pufferüberlaufs auszugleichen. Kapitel 3 – Transportschicht 238 Abbildung 3.44 Leistung in Szenario 2 R/2 R/2 R/3 λ out λ out R/4 λ’in (a) R/2 λ’in R/2 (b) Schließlich betrachten wir den Fall, bei dem der Timer des Senders vorzeitig abläuft und der Sender ein Paket, das in der Warteschlange verzögert wurde, aber nicht verloren gegangen ist, erneut überträgt. In diesem Fall kann sowohl das Originaldatenpaket als auch die Neuübertragung den Empfänger erreichen. Natürlich braucht der Empfänger nur eine Kopie dieses Pakets, so dass er die Neuübertragung verwirft. Die vom Router durchgeführte »Arbeit« in Bezug auf die Weiterleitung des neu übertragenen Exemplars war also »umsonst«, weil der Empfänger das Originalexemplar des Pakets bereits erhalten hat. Der Router hätte die Übertragungskapazität der Verbindungsleitung besser für die Übertragung eines anderen Pakets genutzt. Hier stellen wir einen weiteren Kostenfaktor eines überlasteten Netzwerks fest: Unnötige Neuübertragungen durch den Sender aufgrund von großen Verzögerungen können dazu führen, dass ein Router seine Leitungsbandbreite für die Weiterleitung unnötiger Paketexemplare verschwendet. Die untere Kurve in Abbildung 3.44 (a) zeigt den Durchsatz im Vergleich zur angebotenen Last, wenn jedes Paket (im Durchschnitt) zweimal vom Router weitergeleitet wird. Da jedes Paket zweimal weitergeleitet wird, ist der in Abbildung 3.44 (a) durch das Liniensegment dargestellte erreichte Durchsatz mit einem asymptotischen Wert von R/4 gegeben. Szenario 3: Vier Sender, Router mit endlichen Puffern und Multihop-Pfade In unserem letzten Überlastszenario übertragen vier Hosts jeweils auf überlappenden Pfaden mit zwei Hops Pakete (siehe Abbildung 3.45). Wir nehmen wiederum an, dass jeder Host einen Timeout-/Neuübertragungsmechanismus anwendet, um einen zuverlässigen Datentransferdienst zu implementieren, und dass alle Hosts den gleichen Wert von λin und alle Router-Verbindungsleitungen eine Kapazität von R Byte/s haben. Es sei gegeben, dass die Verbindung von Host A zu Host C durch die Router R1 und R2 führt. Die A-C-Verbindung teilt sich Router R1 mit der D-B-Verbindung und Router R2 mit der B-D-Verbindung. Bei extrem kleinen Werten von λin sind Pufferüberläufe selten (wie in den Überlastszenarien 1 und 2) und der Durchsatz entspricht 3.6 Grundlagen der Überlastkontrolle 239 Abbildung 3.45 Szenario 3: Vier Sender, Router mit endlichen Puffern und Multihop-Pfade Host A Host B λ in R1 Host D R4 R3 R2 Host C ungefähr der angebotenen Last. Bei geringfügig größeren Werten von λin ist auch der Durchsatz entsprechend größer, weil mehr Originaldaten über das Netzwerk zum Ziel übertragen werden und Überläufe immer noch selten sind. Folglich führt bei kleinen Werten von λin eine Erhöhung von λin zu einer Erhöhung von λout. Nach diesem Fall mit extrem niedrigem Verkehr prüfen wir jetzt den Fall, bei dem λin (und folglich λ′in) extrem groß ist. Man betrachte Router R2. Der bei Router R2 ankommende A-C-Verkehr (der bei R2 ankommt, nachdem er von R1 weitergeleitet wurde) kann bei R2 eine Ankunftsrate haben, die meist R – die Kapazität der Verbindungsleitung von R1 zu R2 – entspricht, und zwar ungeachtet des Werts von λin. Wenn λ′in für alle Verbindungen (einschließlich der B-D-Verbindung) extrem groß ist, dann kann die Ankunftsrate des B-D-Verkehrs bei R2 viel größer als die des A-C-Verkehrs sein. Da der A-C- und der B-D-Verkehr bei Router R2 um begrenzten Pufferplatz konkurrieren müssen, wird der Umfang an A-C-Verkehr, der erfolgreich durch R2 hindurchkommt (d. h. nicht aufgrund von Pufferüberlauf verloren geht), immer kleiner, während die angebotene Last von B-D immer mehr wächst. Je mehr sich die angebotene Last Unendlich nähert, füllt sich ein leerer Puffer bei R2 sofort mit einem B-D-Paket und der Durchsatz der A-C-Verbindung bei R2 sinkt auf Null. Dies impliziert, dass der Ende-zu-Ende-Durchsatz von A nach C im Extremfall starken Verkehrsaufkommens auf Null sinkt. Diese Überlegungen führen zu dem in Abbildung 3.46 dargestellten Kompromiss zwischen der angebotenen Last und dem Durchsatz. Der Grund für den eventuellen Abfall des Durchsatzes bei steigender angebotener Last wird deutlich, wenn man sich den Umfang an verschwendeter »Arbeit« durch das Netzwerk vor Augen hält. Wenn in dem oben beschriebenen Szenario mit hohem Verkehrsaufkommen ein Paket vom Router des zweiten Hops verworfen wird, war die »Arbeit« des Routers im ersten Hop, d. h. die Weiterleitung eines Pakets zum Router des zweiten Hops, regelrecht umsonst. Das Netzwerk wäre genauso gut (oder schlecht) dran, wenn der erste Router das Paket gleich verworfen hätte und untätig Kapitel 3 – Transportschicht 240 Abbildung 3.46 Leistung in Szenario 3 λout R/2 λ′in geblieben wäre. Genauer gesagt, die beim ersten Router für die Weiterleitung des Pakets zum zweiten Router verbrauchte Übertragungskapazität hätte sich für die Übertragung eines anderen Pakets viel nutzbringender verwenden lassen. Beispielsweise könnte es sich bei der Auswahl eines Pakets zur Übertragung für einen Router als besser erweisen, Paketen den Vorzug zu geben, die bereits einige Upstream-Router überquert haben.) Wir sehen hier also einen weiteren Kostenfaktor, der durch das Verwerfen eines Pakets aufgrund von Überlast entsteht: Wenn ein Paket entlang eines Pfads verworfen wird, wurde die dafür aufgewendete Übertragungskapazität in den Upstream-Routern für die Weiterleitung dieses Pakets verschwendet. 3.6.2 Ansätze für Überlastkontrolle Abschnitt 3.7 befasst sich ausführlicher mit dem spezifischen Ansatz für Überlastkontrolle in TCP. An dieser Stelle identifizieren wir die beiden allgemeinen Ansätze, auf deren Grundlage in der Praxis Überlastkontrolle umgesetzt wird. Wir beschreiben spezifische Netzwerkarchitekturen und Überlastkontrollprotokolle, die auf diesen Ansätzen basieren. Auf der allgemeinsten Ebene lassen sich die Ansätze für Überlastkontrolle danach unterscheiden, ob die Vermittlungsschicht der Transportschicht für Überlastkontrollzwecke explizit Unterstützung bietet: õ Ende-zu-Ende-Überlastkontrolle: Bei diesem Ansatz für Überlastkontrolle bietet die Vermittlungsschicht der Transportschicht keine explizite Unterstützung für Überlastkontrollzwecke. Hier muss sogar das Vorhandensein einer Überlast im Netzwerk von den Endsystemen allein auf der Grundlage des beobachteten Netzwerkverhaltens (z. B. Paketverlust und Verzögerung) hergeleitet werden. Wir werden in Abschnitt 3.7 sehen, dass TCP zwangsläufig diesen Ansatz für Überlastkontrolle anwenden muss, weil die IP-Schicht den Endsystemen hinsichtlich der Netzwerküberlast keinerlei Feedback liefert. Ein TCP-Segmentverlust (von dem nach einem Timeout oder einer dreifachen Duplikatbestätigung ausgegangen wird) 3.6 Grundlagen der Überlastkontrolle wird als Hinweis auf eine Netzwerküberlast betrachtet und TCP senkt seine Fenstergröße entsprechend. Wir werden auch noch sehen, dass neue Vorschläge für TCP zunehmend höhere Werte der Roundtrip-Verzögerung als Hinweise steigender Netzwerküberlast heranziehen. õ Vom Netzwerk unterstützte Überlastkontrolle: Bei diesem Ansatz bieten die Komponenten der Vermittlungsschicht (d. h. Router) dem Sender explizites Feedback über den Überlastzustand im Netzwerk. Dieses Feedback kann sehr einfach sein, z. B. ein einziges Bit, das auf eine Überlast in einer Verbindungsleitung hinweist. Dieser Ansatz wurde in den alten Architekturen von IBM-SNA [Schwartz 1982] und DECNET [Jain 1989; Ramakrishnan 1990] angewandt, kürzlich für TCP/IPNetzwerke vorgeschlagen [Floyd TCP 1994; RFC 2481] und auch in der ABRÜberlastkontrolle (Available Bit Rate) von ATM (siehe weiter unten) benutzt. Ein umfangreicheres Netzwerk-Feedback ist auch möglich. Bei einer Form der ATMABR-Überlastkontrolle, die wir in einem der nächsten Abschnitte beschreiben, kann beispielsweise ein Router den Sender explizit über die Übertragungsrate informieren, die er auf einer abgehenden Verbindungsleitung unterstützen kann. Bei der netzwerkunterstützten Überlastkontrolle werden Überlastinformationen normalerweise vom Netzwerk zum Sender auf eine von zwei Arten zurückgespeist (siehe Abbildung 3.47). Direktes Feedback kann von einem Netzwerk-Router zum Sender gesendet werden. Diese Form der Mitteilung ist normalerweise ein ChokePaket (was im Grunde besagt: »Ich bin überlastet!«). Die zweite Form der Mitteilung erfolgt, wenn ein Router ein Feld in einem Paket, das vom Sender zum Empfänger fließt, markiert bzw. aktualisiert, um auf eine Überlast hinzuweisen. Beim Empfang eines so gekennzeichneten Pakets benachrichtigt der Empfänger dann den Sender über den Überlasthinweis. Man beachte, dass diese Form der Mitteilung mindestens eine volle Roundtrip-Zeit beansprucht. Abbildung 3.47 Zwei Feedback-Pfade für Überlastinformationen vom Netzwerk Host A Netzwerk-Feedback über Empfänger Direktes NetzwerkFeedback 241 Kapitel 3 – Transportschicht 242 3.6.3 ABR-Überlastkontrolle in ATM Die ausführliche Untersuchung der TCP-Überlastkontrolle in Abschnitt 3.7 enthält auch eine Fallstudie über einen Ende-zu-Ende-Ansatz für Überlastkontrolle. Wir beenden diesen Abschnitt mit einer kurzen Fallstudie der netzwerkunterstützten Überlastkontrollmechanismen, die im ATM-ABR-Dienst benutzt werden. ABR wurde als elastischer Datentransferdienst ausgelegt, der an TCP erinnert. Wenn das Netzwerk unterbelastet ist, sollte der ABR-Dienst in der Lage sein, die zusätzlich verfügbare Bandbreite zu nutzen. Ist das Netzwerk jedoch überlastet, sollte der ABR-Dienst seine Übertragungsrate auf eine im Voraus festgelegte minimale Übertragungsrate drosseln. Ein ausführlicher Lehrabschnitt über ATM-ABR-Überlastkontrolle und Verkehrsmanagement findet der Leser in [Jain 1996]. Abbildung 3.48 zeigt das Rahmenwerk der ATM-ABR-Überlastkontrolle. In unserer anschließenden Diskussion verwenden wir die ATM-Terminologie (z. B. den Begriff »Switch« statt »Router« und »Zelle« statt »Paket«). Beim ATM-ABR-Dienst werden Datenzellen von einer Quelle zu einem Ziel durch eine Reihe dazwischen liegender Switches übertragen. Mit den Datenzellen vermengt sind so genannte Ressourcenmanagement-Zellen (RM-Zellen). Wir werden bald sehen, dass diese RMZellen benutzt werden können, um überlastspezifische Informationen unter Hosts und Switches zu verteilen. Wenn eine RM-Zelle am Ziel ist, wird sie »umgedreht« und an den Sender zurückgeschickt (möglicherweise, nachdem das Ziel ihren Inhalt modifiziert hat). Es ist auch möglich, dass ein Switch eine RM-Zelle selbst erzeugt und sie direkt an eine Quelle sendet. RM-Zellen können also sowohl für das direkte Netzwerk-Feedback als auch für das Netzwerk-Feedback über den Empfänger benutzt werden (siehe Abbildung 3.48). Abbildung 3.48 Überlastkontrolle im ATM-ABR-Dienst Quelle RM-Zellen Datenzellen Switch Ziel Switch Die ATM-ABR-Überlastkontrolle ist ein ratenbasierter Ansatz. Das heißt, der Sender berechnet explizit eine Höchstrate, in der er senden kann, und reguliert sich dann selbst. ABR bietet drei Mechanismen für die Signalisierung von überlastspezifischen Informationen von den Switches zum Empfänger: õ EFCI-Bit: Jede Datenzelle enthält ein EFCI-Bit (Explicit Forward Congestion Indication). Ein überlasteter Netzwerk-Switch kann das EFCI-Bit in einer Datenzelle auf 1 setzen, um dem Ziel-Host eine Überlast zu signalisieren. Das Ziel muss das 3.7 TCP-Überlastkontrolle EFCI-Bit in allen empfangenen Datenzellen prüfen. Wenn eine RM-Zelle am Ziel ankommt und in der zuletzt empfangenen Datenzelle das EFCI-Bit auf 1 gesetzt war, dann setzt das Ziel das CI-Bit (Congestion Indication) der RM-Zelle auf 1 und sendet die RM-Zelle an den Sender zurück. Durch Verwendung des EFCI-Bits in Datenzellen und des CI-Bits in RM-Zellen kann ein Sender also über eine Überlast in einem Netzwerk-Switch benachrichtigt werden. õ CI- und NI-Bits: Wie oben erwähnt, sind RM-Zellen vom Sender zum Empfänger mit Datenzellen vermengt. Die Rate der RM-Zellenvermengung ist ein einstellbarer Parameter; der Default-Wert ist eine RM-Zelle alle 32 Datenzellen. Diese RMZellen haben ein CI- (Congestion Indication) und ein NI-Bit (No Increase), die ein überlasteter Netzwerk-Switch benutzen kann. Das heißt, ein Switch kann das NIBit in einer vorbeifließenden RM-Zelle auf 1 setzen, wenn die Überlast gering ist, und unter starken Überlastbedingungen das CI-Bit auf 1 setzen. Wenn ein ZielHost eine RM-Zelle empfängt, sendet er sie mit den CI- und NI-Bits intakt an den Sender zurück (außer, dass das Ziel als Ergebnis des oben beschriebenen EFCIMechanismus CI möglicherweise auf 1 gesetzt hat). õ ER-Feld: Jede RM-Zelle enthält auch ein 2-Byte-ER-Feld (Explicit Rate). Ein überlasteter Switch kann den im ER-Feld enthaltenen Wert in einer vorbeifließenden RM-Zelle senken. Auf diese Weise wird das ER-Feld auf eine minimale Rate gesetzt, die von allen Switches auf dem Pfad von der Quelle zum Ziel unterstützt wird. Eine ATM-ABR-Quelle berichtigt die Rate, in der sie Zellen senden kann, als eine Funktion der CI-, NI- und ER-Werte in einer zurückgegebenen RM-Zelle. Die Regeln für diese Ratenberichtigung sind eher kompliziert und ein bisschen mühsam. Wir empfehlen dem interessierten Leser [Jain 1996] das umfangreiche Details bereithält. 3.7 TCP-Überlastkontrolle Dieser Abschnitt widmet sich wieder der Untersuchung von TCP. Wie wir aus Abschnitt 3.5 wissen, bietet TCP einen zuverlässigen Transportdienst zwischen zwei Prozessen, die auf unterschiedlichen Hosts laufen. Eine weitere sehr wichtige Komponente von TCP ist der Überlastkontrollmechanismus. Wie im vorherigen Abschnitt erwähnt, muss TCP eine Ende-zu-Ende- und keine netzwerkunterstützte Überlastkontrolle anwenden, weil die IP-Schicht ihren Endsystemen kein explizites Feedback hinsichtlich Netzwerküberlast bietet. Bevor wir auf die Details der TCP-Überlastkontrolle übergehen, geben wir einen Überblick über den Überlastkontrollmechanismus von TCP sowie über das allgemeine Ziel, das TCP anstrebt, wenn mehrere TCP-Verbindungen sich die Bandbreite einer überlasteten Verbindungsleitung teilen müssen. Eine TCP-Verbindung steuert ihre Übertragungsrate durch Einschränkung der Anzahl von übertragenen, jedoch noch nicht bestätigten Segmenten. Wir nennen diese Anzahl von zulässigen unbestätigten Segmenten w, die meist als TCP-Fenstergröße bezeichnet wird. Im Idealfall sollte es TCP-Verbindungen gestattet sein, so schnell wie möglich (d. h. die größtmögliche Anzahl anstehender unbestätigter Segmente) zu übertragen, solange keine Segmente aufgrund von Überlast verloren gehen (bzw. von den Routern verworfen werden). In einem sehr allgemeinen Sinn beginnt eine TCPVerbindung mit einem kleinen Wert von w und »tastet« dann die Verbindungsleitungen ihres Ende-zu-Ende-Pfads auf Vorhandensein unbenutzter Leitungsbandbreite durch stufenweise Erhöhung von w ab. Die TCP-Verbindung fährt mit der Erhöhung 243 244 Kapitel 3 – Transportschicht von w so lange fort, bis ein Segment verloren geht (was durch ein Timeout oder Duplikatbestätigungen erkannt wird). Im Fall eines Verlusts reduziert die TCP-Verbindung w auf ein »sicheres Maß« und beginnt anschließend erneut mit dem Abtasten auf eine mögliche unbenutzte Bandbreite, indem sie w langsam wieder erhöht. Eine wichtige Messgröße der Leistung einer TCP-Verbindung ist ihr Durchsatz – die Rate, in der sie Daten vom Sender zum Empfänger überträgt. Natürlich hängt der Durchsatz von dem Wert von w ab. Wenn ein TCP-Sender alle w-Segmente nacheinander überträgt, muss er anschließend eine Roundtrip-Zeit (RTT) warten, bis er Bestätigungen für diese Segmente erhält. An diesem Punkt kann er w weitere Segmente senden. Wenn eine Verbindung w Segmente mit einer Größe von MSS Byte alle RTT Sekunden überträgt, dann beträgt der Durchsatz der Verbindung bzw. die Übertragungsrate (w · MSS)/RTT Bytes pro Sekunde. Es sei gegeben, dass K TCP-Verbindungen eine Verbindungsleitung mit der Kapazität R benutzen, über diese Verbindungsleitung keine UDP-Pakete fließen, jede TCPVerbindung eine sehr große Datenmenge überträgt und keine dieser TCP-Verbindungen eine andere überlastete Verbindungsleitung durchquert. Im Idealfall sollten die Fenstergrößen der TCP-Verbindungen, die diese Verbindungsleitung durchqueren, ausreichend groß sein, so dass jede Verbindung einen Durchsatz von R/K erreicht. Allgemeiner ausgedrückt: Wenn eine Verbindung durch N Verbindungsleitungen läuft und die Verbindungsleitung n eine Übertragungsrate von Rn hat und insgesamt Kn TCP-Verbindungen unterstützt, dann sollte diese Verbindung im Idealfall eine Rate von Rn/Kn auf der n-ten Verbindungsleitung erreichen. Die durchschnittliche Ende-zu-Ende-Rate dieser Verbindung kann aber die Mindestrate, die auf allen Verbindungsleitungen entlang des Ende-zu-Ende-Pfads erreicht wird, nicht übersteigen. Das heißt, die Übertragungsrate von Ende-zu-Ende für diese Verbindung beträgt r = min{R1/K1, …, RN/KN}. Wir können uns das Ziel von TCP so vorstellen, dass es dieser Verbindung eine Ende-zu-Ende-Rate r bereitstellt. (In Wirklichkeit ist die Formel für r komplizierter, weil man die Tatsache berücksichtigen muss, dass eine oder mehrere der nutzenden Verbindungen auf irgendeiner anderen Verbindungsleitung, die nicht auf diesem Ende-zu-Ende-Pfad liegt, auf einen Flaschenhals stößt und daher ihren Bandbreitenanteil Rn/Kn nicht nutzen kann. In diesem Fall wäre der Wert von r höher als min{R1/K1, …, RN/KN}; siehe [Bertsekas 1991]. 3.7.1 Übersicht über die TCP-Überlastkontrolle In Abschnitt 3.5 haben wir gesehen, dass sich jede Seite einer TCP-Verbindung aus einem Empfangs- und Sendepuffer und mehreren Variablen (LastByteRead, RcvWin usw.) zusammensetzt. Der Überlastkontrollmechanismus von TCP lässt jede Seite der Verbindung zwei zusätzliche Variablen verwalten: Überlastfenster (Congestion Window) und Grenzwert (Threshold). Das Überlastfenster, CongWin, bringt die zusätzliche Einschränkung, wie viel Verkehr ein Host in eine Verbindung einspeisen kann. Insbesondere darf die bei einem Host für eine TCP-Verbindung anstehende unbestätigte Datenmenge das Minimum von CongWin und RcvWin nicht übersteigen, d. h.: LastByteSent – LastByteAcked ≤ min{CongWin, RcvWin} Der Grenzwert, der weiter unten ausführlich beschrieben wird, ist eine Variable, die sich darauf auswirkt, wie CongWin wächst. Wir untersuchen jetzt, wie sich das Überlastfenster im Verlauf der Lebensdauer einer TCP-Verbindung entwickelt. Um uns auf Überlastkontrolle (im Gegensatz zur 3.7 TCP-Überlastkontrolle Flusskontrolle) zu konzentrieren, nehmen wir an, dass der TCP-Empfangspuffer so groß ist, dass die Einschränkung des Empfangsfensters ignoriert werden kann. In diesem Fall ist die unbestätigte Datenmenge, die ein Host für eine TCP-Verbindung vorhalten darf, höchstens durch CongWin begrenzt. Außerdem nehmen wir an, dass ein Sender eine sehr große Datenmenge an einen Empfänger senden muss. Nachdem eine TCP-Verbindung zwischen den beiden Endsystemen aufgebaut wurde, schreibt der Anwendungsprozess beim Sender in den TCP-Sendepuffer. TCP entnimmt davon Stücke in der MSS-Größe, verkapselt jedes Stück in einem TCP-Segment und gibt die Segmente an die Vermittlungsschicht zur Übertragung im Netzwerk weiter. Das TCP-Überlastfenster reguliert die Zeiten, in denen die Segmente in das Netzwerk gespeist (d. h. an die Vermittlungsschicht weitergegeben) werden. Anfangs entspricht das Überlastfenster einer MSS. TCP sendet das erste Segment zum Netzwerk und wartet auf eine Bestätigung. Wird dieses Segment bestätigt, bevor sein Timer abläuft, erhöht der Sender das Überlastfenster um eine MSS und sendet zwei Segmente mit maximaler Größe. Wenn diese Segmente vor ihrem jeweiligen Timeout bestätigt werden, erhöht der Sender das Überlastfenster um eine MSS pro bestätigtem Segment, so dass das Überlastfenster jetzt vier MSS umfasst, und sendet vier Segmente mit maximaler Größe. Diese Prozedur wird fortgesetzt, (1) solange das Überlastfenster unter dem Grenzwert liegt und (2) die Bestätigungen vor ihrem jeweiligen Timeout ankommen. Während dieser Phase der Überlastkontrollprozedur erhöht sich das Überlastfenster exponentiell sehr schnell. Das Überlastfenster wird auf eine MSS initialisiert; nach einer RTT wird das Fenster auf zwei Segmente erhöht; nach zwei Roundtrip-Zeiten wird das Fenster auf vier Segmente erhöht; nach drei Roundtrip-Zeiten wird das Fenster auf acht Segmente erhöht und so fort. Diese Phase des Algorithmus nennt man Slow-Start (Langsamstart), weil sie mit einem kleinen, einer MSS entsprechenden Überlastfenster beginnt. (Die Übertragungsrate der Verbindung startet langsam, beschleunigt aber schnell.) Die Slow-Start-Phase endet, wenn die Fenstergröße den Wert von threshold (Grenzwert) übersteigt. Ist das Überlastfenster größer als der aktuelle Wert von threshold, wächst es linear und nicht mehr exponentiell. Wenn w der aktuelle Wert des Überlastfensters und größer als threshold ist, ersetzt TCP w nach Ankunft von w Bestätigungen durch w + 1. Als Auswirkung davon wird das Überlastfenster in jeder RTT, in der die Menge von Bestätigungen für ein ganzes Fenster ankommt, um 1 erhöht. Diese Phase des Algorithmus wird als Überlastvermeidung (Congestion Avoidance) bezeichnet. Die Überlastvermeidungsphase wird fortgesetzt, solange die Bestätigungen vor ihrem jeweiligen Timeout ankommen. Die Fenstergröße und damit die Rate, in der der TCP-Sender senden kann, kann sich aber nicht ewig erhöhen. Irgendwann wird die TCP-Rate so sein, dass eine der Verbindungsleitungen auf dem Pfad gesättigt wird und ein Verlust (sowie ein daraus resultierendes Timeout beim Sender) eintritt. Wenn ein Timeout erfolgt, wird der Wert von threshold auf die Hälfte des Werts des aktuellen Überlastfensters gesetzt und das Überlastfenster auf eine MSS zurückgesetzt. Der Sender erhöht dann das Überlastfenster mit Hilfe der Slow-Start-Prozedur wieder exponentiell sehr schnell, bis das Überlastfenster an den Grenzwert stößt. Fazit: õ Solange das Überlastfenster unter dem Grenzwert liegt, wächst es exponentiell. õ Steigt das Überlastfenster über den Grenzwert, wächst es linear. õ Bei jedem Timeout wird der Grenzwert auf die Hälfte des aktuellen Überlastfensters zurückgesetzt und das Überlastfenster auf 1 gesetzt. 245 Kapitel 3 – Transportschicht 246 Wenn wir die Slow-Start-Phase ignorieren, sehen wir, dass TCP im Grunde seine Fenstergröße pro RTT um 1 (und damit seine Übertragungsrate um einen zusätzlichen Faktor) erhöht, wenn sein Netzwerkpfad nicht überlastet ist, und um einen Faktor von 2 pro RTT senkt, wenn der Pfad überlastet ist. Aus diesem Grund wird TCP oft als AIMD-Algorithmus (Additive-Increase, Multiplicative-Decrease) bezeichnet. Die Evolution des TCP-Überlastfensters ist in Abbildung 3.49 dargestellt. In dieser Abbildung ist der Grenzwert anfangs gleich 8 · MSS. Das Überlastfenster steigt exponentiell recht schnell während des Slow-Starts und erreicht dann bei der dritten Übertragung den Grenzwert. Anschließend steigt das Überlastfenster linear, bis nach Übertragung 7 ein Verlust entsteht. Man beachte, dass das Überlastfenster 12 · MSS ist, wenn der Verlust eintritt. Der Grenzwert wird dann auf 0,5 · CongWin = 6 · MSS und das Überlastfenster auf 1 gesetzt; anschließend wird der Prozess fortgesetzt. Dieser Überlastkontrollalgorithmus stammt von V. Jacobson [Jacobson 1988]; eine Reihe von Modifikationen zu Jacobsons erster Version des Algorithmus sind in Stevens (1994) und in RFC 2581 beschrieben. Wir fügen an dieser Stelle an, dass die Beschreibung des TCP-Slow-Starts hier idealisiert wurde. Ein anfängliches Fenster von bis zu zwei MSS ist ein vorgeschlagener Standard [RFC 2581] und wird in einigen Implementierungen bereits angewandt. Abbildung 3.49 Evolution des Überlastfensters von TCP 15 14 13 Überlastfenster (in Segmenten) 12 11 10 9 Grenzwert 8 7 Grenzwert 6 5 4 3 2 1 0 1 2 3 4 5 6 7 8 9 10 11 12 Anzahl der Übertragungen 13 14 15 3.7 TCP-Überlastkontrolle Reise nach Nevada: Tahoe, Reno und Vegas Der soeben beschriebene TCP-Überlastkontrollalgorithmus wird auch als Tahoe bezeichnet. Ein Problem mit dem Tahoe-Algorithmus ist, dass die Senderseite der Anwendung, wenn ein Segment verloren geht, eventuell längere Zeit auf ein Timeout warten muss. Aus diesem Grund wird in den meisten Betriebssystemen eine Variante von Tahoe namens Reno implementiert. Wie Tahoe setzt Reno sein Überlastfenster beim Ablauf eines Timers auf ein Segment. Reno beinhaltet aber den Fast-RetransmitMechanismus, der in Abschnitt 3.5 beschrieben wurde. Wir erinnern uns, dass dieser Mechanismus die Übertragung eines verworfenen Segments auslöst, wenn vor dem Timeout des Segments drei Duplikat-ACKs für das Segment ankommen. Reno beinhaltet auch einen Fast-Recovery-Mechanismus, der im Grunde die Slow-Start-Phase nach einem Fast-Retransmit annulliert. Dem interessierten Leser empfehlen wir [Stevens 1994] und [RFC 2581]. In [Cela 2000] sind interaktive Animationen von Überlastvermeidung, Slow-Start, Fast-Retransmit und Fast-Recovery in TCP enthalten. Die meisten heutigen TCP-Implementierungen verwenden den Reno-Algorithmus. In der Literatur wird aber noch ein weiterer – der Vegas-Algorithmus – beschrieben, der die Leistung von Reno verbessern kann. Während Tahoe und Reno auf Überlast (d. h. auf Überlauf von Router-Puffern) reagieren, versucht Vegas, Überlast durch Wahrung eines guten Durchsatzes zu vermeiden. Das Grundkonzept von Vegas ist (1) Überlast in den Routern zwischen Quelle und Ziel vor einem Paketverlust zu erkennen und (2) die Rate linear zu senken, wenn ein bevorstehender Paketverlust erkannt wird. Ob ein Paketverlust bevorsteht, wird durch Beobachtung der Roundtrip-Zeiten festgestellt. Je länger die Roundtrip-Zeiten der Pakete, um so größer ist die Überlast in den Routern. Der Vegas-Algorithmus wird ausführlich in [Brakmo 1995] behandelt; eine Untersuchung seiner Leistung findet sich in [Ahn 1995]. Zum Stand von 1999 wurde Vegas in den meisten gängigen TCP-Implementierungen nicht benutzt. Wir betonen an dieser Stelle, dass sich die TCP-Überlastkontrolle im Laufe der Jahre entwickelt hat und noch weiterentwickelt wird. Was für das Internet gut war, als TCP-Verbindungen größtenteils SMTP-, FTP- und Telnet-Verkehr übertrugen, ist nicht unbedingt gut für das heutige, vom Web dominierte Internet oder für das Internet der Zukunft, das wer weiß welche Dienste unterstützen wird. Gewährleistet TCP Fairness? In der obigen Diskussion wurde deutlich, dass mit dem Überlastkontrollmechanismus von TCP unter anderem das Ziel verfolgt wird, die Bandbreite einer Flaschenhalsleitung gleichmäßig zwischen den TCP-Verbindungen aufzuteilen, die über diese Verbindungsleitung auf einen Flaschenhals stoßen. Warum sollte der AIMD-Algorithmus (Additive-Increase, Multiplicative-Decrease) von TCP dieses Ziel aber erreichen, insbesondere vor dem Hintergrund, dass verschiedene TCP-Verbindungen zu unterschiedlichen Zeiten starten können und somit zu einem bestimmten Zeitpunkt unterschiedliche Fenstergrößen haben? [Chiu 1989] bietet eine elegante und intuitive Erklärung, warum die TCP-Überlastkontrolle darauf hinausläuft, eine gleichmäßige Nutzung der Bandbreite einer Flaschenhalsleitung unter konkurrierenden TCP-Verbindungen bereitzustellen. Wir betrachten den einfachen Fall zweier TCP-Verbindungen, die eine einzige Verbindungsleitung mit Übertragungsrate R gemeinsam nutzen (siehe Abbildung 3.50). Wir nehmen an, dass die beiden Verbindungen die gleiche MSS und RTT haben (so dass sie dann den gleichen Durchsatz haben, falls ihre Überlastfenstergröße gleich 247 248 Kapitel 3 – Transportschicht ist), dass sie eine große Datenmenge senden müssen und keine der übrigen TCP-Verbindungen oder UDP-Datagramme diese gemeinsame Verbindungsleitung durchqueren. Außerdem ignorieren wir die Slow-Start-Phase von TCP und nehmen an, dass die TCP-Verbindungen die ganze Zeit im Überlastvermeidungsmodus (Additive-Increase, Multiplicative-Decrease) laufen. Abbildung 3.50 Zwei TCP-Verbindungen, die eine einzige Flaschenhalsverbindung gemeinsam nutzen TCP-Verbindung 2 FlaschenhalsRouter, Kapazität R TCP-Verbindung 1 Abbildung 3.51 zeigt den von den beiden TCP-Verbindungen realisierten Durchsatz. Soll TCP die Bandbreite der Verbindungsleitungen zwischen den beiden Verbindungen gleichmäßig nutzen, dann müsste der realisierte Durchsatz auf der 45-Grad-Linie (»Nutzung der Bandbreite zu gleichen Anteilen«) liegen, die vom Ursprung ausgeht. Im Idealfall sollte die Summe der beiden Durchsätze R entsprechen. (Sicherlich ist es nicht wünschenswert, dass jede Verbindung zwar einen gleichen Anteil an der Leitungskapazität erhält, allerdings einen von Null!) Das Ziel sollte also sein, dass die erreichten Durchsätze irgendwo nahe des Schnittpunkts der Linien »Nutzung der Bandbreite zu gleichen Anteilen« und »Volle Bandbreitenauslastung« in Abbildung 3.51 liegen. Angenommen, die TCP-Fenstergrößen sehen so aus, dass die Verbindungen 1 und 2 zu einem bestimmten Zeitpunkt die durch Punkt A in Abbildung 3.51 gekennzeichneten Durchsätze realisieren. Da die von den beiden Verbindungen gemeinsam verbrauchte Leitungsbandbreite kleiner als R ist, entsteht kein Verlust und beide Verbindungen werden ihr Fenster als Ergebnis des Überlastvermeidungsalgorithmus von TCP um 1 pro RTT erhöhen. Der gemeinsame Durchsatz der beiden Verbindungen verläuft also auf einer 45-Grad-Linie (bei gleicher Erhöhung für beide Verbindungen), ausgehend von Punkt A. Irgendwann wird die von den beiden Verbindungen gemeinsam verbrauchte Leitungsbandbreite größer als R sein und ein Paketverlust eintreten. Angenommen, dass die Verbindungen 1 und 2 einen Paketverlust erleiden, wenn sie die durch Punkt B gekennzeichneten Durchsätze realisieren. Die daraus resultierenden Durchsätze liegen also auf Punkt C, etwa auf halber Strecke entlang 3.7 TCP-Überlastkontrolle 249 Abbildung 3.51 Durchsatz zweier TCP-Verbindungen Nutzung der Bandbreite zu gleichen Anteilen R Volle Bandbreitenauslastung D Durchsatz, Verbindung 2 B C A Durchsatz, Verbindung 1 R eines Vektors, der bei B beginnt und am Ursprung endet. Da die gemeinsame Bandbreitennutzung auf Punkt C unter R liegt, erhöhen die beiden Verbindungen wieder ihre Durchsätze entlang der bei C beginnenden 45-Grad-Linie. Schließlich ereignet sich noch ein Verlust, z. B. bei Punkt D, und die beiden Verbindungen senken ihre Fenstergrößen wieder um einen Faktor von Zwei und so weiter. Sie sollten sich selbst davon überzeugen, dass die von den beiden Verbindungen realisierte Bandbreite letztlich entlang der Linie »Nutzung der Bandbreite zu gleichen Anteilen« schwankt. Sie sollten sich auch selbst davon überzeugen, dass die beiden Verbindungen auf dieses Verhalten konvergieren, ungeachtet dessen, wo sie sich in dem zweidimensionalen Raum befinden! Obwohl dieses Szenario auf einer Reihe idealisierter Annahmen gründet, bietet es dennoch einen guten Eindruck darüber, warum TCP zu einer Bandbreitennutzung zu gleichen Anteilen zwischen Verbindungen führt. In unserem idealisierten Szenario gingen wir davon aus, dass nur TCP-Verbindungen die Flaschenhalsleitung durchqueren und nur eine einzige TCP-Verbindung mit je einem Host/Ziel-Paar in Verbindung steht. In der Praxis sind diese beiden Bedingungen normalerweise nicht gegeben, so dass Client/Server-Anwendungen sehr ungleiche Anteile an der Bandbreite einer Verbindungsleitung erhalten können. Viele Netzwerkanwendungen laufen über TCP statt über UDP, weil sie den zuverlässigen Transportdienst von TCP nutzen wollen. Ein Anwendungsentwickler, der TCP wählt, erhält aber nicht nur den zuverlässigen Datentransfer, sondern auch die TCPÜberlastkontrolle. Wie oben ausgeführt, reguliert die TCP-Überlastkontrolle die Übertragungsrate einer Anwendung mittels Überlastfenstermechanismus. Viele Multimedia-Anwendungen laufen genau aus diesem Grund nicht über TCP. Sie wollen nicht, Kapitel 3 – Transportschicht 250 dass man ihre Übertragungsrate drosselt, auch wenn das Netzwerk stark überlastet ist. Insbesondere laufen die meisten Internet-Phone- und Internet-Videokonferenzanwendungen über UDP. Diese Anwendungen pumpen ihre Audio- und Videodaten bevorzugt in einer konstanten Rate in das Netzwerk. Sie nehmen gelegentliche Paketverluste in Kauf, sind aber nicht bereit, ihre Raten zu Zeiten von Überlast auf einen »fairen« Umfang zu reduzieren und dafür keine Pakete zu verlieren. Aus Sicht von TCP sind die Multimedia-Anwendungen, die über UDP laufen, überhaupt nicht fair: Sie kooperieren nicht mit den anderen Verbindungen und passen ihre Übertragungsraten nicht entsprechend an. Eine der großen Herausforderungen in den nächsten Jahren wird es sein, Überlastkontrollmechanismen für das Internet zu entwickeln, die UDP-Verkehr daran hindern, den Internet-Durchsatz völlig lahm zu legen [Floyd 1999]. Doch auch wenn manr UDP-Verkehr zu fairem Verhalten zwingen könnte, hätte man das Problem mit der Fairness noch lange nicht gelöst. Der Grund dafür ist, dass nichts eine über TCP laufende Anwendung daran hindern kann, mehrere parallele Verbindungen zu verwenden. Beispielsweise nutzen Web-Browser oft mehrere parallele TCP-Verbindungen, um eine Web-Seite zu übertragen. (Die genaue Anzahl der gleichzeitigen Verbindungen ist in den meisten Browsern konfigurierbar.) Nutzt eine Anwendung mehrere parallele Verbindungen, erhält sie einen größeren Anteil an der Bandbreite einer überlasteten Verbindungsleitung. Als Beispiel betrachte man eine Verbindungsleitung mit der Rate R, die neun laufende Client/Server-Anwendungen unterstützt, wobei jede eine TCP-Verbindung benutzt. Gesellt sich eine weitere Anwendung dazu, die ebenfalls eine TCP-Verbindung benutzt, erhält jede Anwendung ungefähr die gleiche Übertragungsrate von R/10. Benutzt diese letzte Anwendung aber elf parallele TCP-Verbindungen, erhält sie eine unfaire Zuteilung von mehr als R/2. Da Web-Verkehr im Internet vorherrscht, sind mehrere parallele Verbindungen absolut üblich. Makroskopische Beschreibung der TCP-Dynamik Man stelle sich die Übersendung einer sehr großen Datei über eine TCP-Verbindung vor. Betrachtet man den von der Quelle gesendeten Verkehr aus makroskopischer Sicht, so kann man die Slow-Start-Phase ignorieren. Die Verbindung ist tatsächlich über eine relativ kurze Zeit in der Slow-Start-Phase, weil die Verbindung exponentiell schnell aus der Phase herauswächst. Wenn wir die Slow-Start-Phase ignorieren, wächst das Überlastfenster linear. Es wird bei Eintritt eines Verlusts halbiert, wächst linear, wird bei einem erneuten Verlust wieder halbiert und so fort. Dies war der Anlass für das Sägezahnverhalten von TCP [Stevens 1994], das in Abbildung 3.49 dargestellt ist. Was ist der durchschnittliche Durchsatz einer TCP-Verbindung angesichts dieses Sägezahnverhaltens? Während eines bestimmten Roundtrip-Intervalls ist die Rate, in der TCP Daten sendet, eine Funktion des Überlastfensters und der aktuellen RTT. Wenn die Fenstergröße w · MSS und die aktuelle Roundtrip-Zeit RTT ist, dann beträgt die Übertragungsrate von TCP (w · MSS)/RTT. Während der Überlastvermeidungsphase tastet TCP auf zusätzliche Bandbreite ab, indem es w pro RTT um Eins erhöht, bis ein Verlust eintritt. (W sei der Wert von w, wenn ein Verlust eintritt.) Wenn RTT und W über die Dauer der Verbindung ungefähr konstant sind, liegt die TCP-Übertragungsrate im Bereich von W ⋅ MSS W ⋅ MSS ----------------------- bis ----------------------RTT 2RTT 3.7 TCP-Überlastkontrolle 251 Diese Annahmen führen zu einem höchst vereinfachten makroskopischen Modell für das Verhalten von TCP im Dauerzustand. Das Netzwerk verwirft ein von der Verbindung ankommendes Paket, wenn die Fenstergröße der Verbindung auf W · MSS steigt; das Überlastfenster wird dann halbiert und anschließend um eine MSS pro Roundtrip-Zeit erhöht, bis es wieder W erreicht. Dieser Prozess wiederholt sich immer wieder. Da der TCP-Durchsatz linear zwischen den beiden Extremwerten steigt, ergibt sich Folgendes: 0, 75 ⋅ W ⋅ MSS Durchschnittlicher Durchsatz einer Verbindung: --------------------------------------RTT Mit diesem stark idealisierten Modell für die Dauerzustandsdynamik von TCP lässt sich auch ein interessanter Ausdruck herleiten, der die Verlustrate einer Verbindung mit ihrer verfügbaren Bandbreite in Bezug setzt [Mahdavi 1997]. Diese Herleitung ist Thema der Übungen am Ende dieses Kapitels. 3.7.2 Latenz-Modellierung: statisches Überlastfenster Viele TCP-Verbindungen transportieren relativ kleine Dateien von einem Host zu einem anderen. Bei HTTP/1.0 wird beispielsweise jedes Objekt einer Web-Seite über eine getrennte TCP-Verbindung übertragen und viele dieser Objekte sind kleine Textdateien oder winzige Icons. Bei der Übertragung einer kleinen Datei können der Verbindungsaufbau und der Slow-Start von TCP eine beträchtliche Auswirkung auf die Latenz haben. In diesem Abschnitt präsentieren wir ein analytischen Modell, das die Auswirkung des Verbindungsaufbaus und Slow-Starts auf die Latenz quantifiziert. Für ein bestimmtes Objekt definieren wir Latenz als die Zeit von der Einleitung einer TCP-Verbindung durch den Client bis zum Empfang des gesamten angeforderten Objekts beim Client. Die hier präsentierte Analyse gründet auf der Annahme, dass das Netzwerk nicht überlastet ist, d. h. dass die TCP-Verbindung, die das Objekt befördert, die Bandbreite der Verbindungsleitung nicht mit anderem TCP- oder UDP-Verkehr teilen muss. (Wir kommentieren diese Annahme weiter unten.) Um die zentralen Fragen nicht zu verwässern, führen wir die Analyse außerdem in Zusammenhang mit dem einfachen Netzwerk von Abbildung 3.52 durch, das nur über eine Verbindungsleitung verfügt. (Diese Verbindungsleitung könnte einen einzigen Flaschenhals auf einem Ende-zuEnde-Pfad modellieren; siehe auch die Übungen bezüglich einer expliziten Erweiterung auf den Fall mit mehreren Verbindungsleitungen.) Abbildung 3.52 Einfaches Netzwerk mit einer Verbindungsleitung, über die ein Client und ein Server verbunden sind R bps Client Server 252 Kapitel 3 – Transportschicht Wir gehen außerdem von folgenden vereinfachenden Annahmen aus: õ Die Datenmenge, die der Sender übertragen kann, ist ausschließlich durch das Überlastfenster des Senders begrenzt. (Folglich sind die TCP-Empfangspuffer groß.) õ Es werden keine Pakete beschädigt, noch gehen welche verloren, so dass es keine Neuübertragungen gibt. õ Der gesamte Overhead aller Protokoll-Header – TCP-, IP- und SicherungsschichtHeader – ist verschwindend klein und kann ignoriert werden. õ Das zu übertragende Objekt (d. h. die Datei) besteht aus einer Ganzzahl von Segmenten mit der Größe MSS (maximale Segmentgröße). õ Die einzigen Pakete, die keine vernachlässigbaren Übertragungszeiten haben, sind diejenigen, die TCP-Segmente mit maximaler Größe befördern. Segmente für Anfragenachrichten, Bestätigungen und TCP-Verbindungsaufbau sind klein und haben vernachlässigbare Übertragungszeiten. õ Der anfängliche Grenzwert des TCP-Überlastkontrollmechanismus ist ein großer Wert, der vom Überlastfenster nie erreicht wird. Wir führen darüber hinaus folgende Notation ein: õ Die Größe des zu übertragenden Objekts ist 0 Bit. õ Die MSS (maximale Segmentgröße) ist S Bit (z. B. 536 Byte). õ Die Übertragungsrate der Verbindungsleitung vom Server zum Client beträgt R bps. õ Die Roundtrip-Zeit wird als RTT bezeichnet. In diesem Abschnitt definieren wir RTT als die Zeit, die verstreicht, bis ein kleines Paket vom Client zum Server und dann wieder zum Client zurück reist, ausschließlich der Übertragungszeit des Pakets. Enthalten sind die beiden Ausbreitungsverzögerungen von Ende zu Ende zwischen den beiden Endsystemen und die Verarbeitungszeiten in den beiden Endsystemen. Wir nehmen an, dass die RTT auch die Roundtrip-Zeit eines Pakets, beginnend beim Server, ist. Obwohl die in diesem Abschnitt dargestellte Analyse von einem nicht überlasteten Netzwerk mit einer einzigen TCP-Verbindung ausgeht, bietet sie einen guten Einblick in den realistischeren Fall eines überlasteten Netzwerks mit mehreren Verbindungsleitungen. Bei einem überlasteten Netzwerk stellt R grob die Bandbreitenmenge dar, die im Dauerzustand in der Netzwerkverbindung von Ende zu Ende verfügbar ist, während RTT für eine Roundtrip-Verzögerung steht, die Warteschlangenverzögerungen in den Routern vor den überlasteten Verbindungsleitungen beinhaltet. Im Falle des überlasteten Netzwerks modellieren wir jede TCP-Verbindung als Verbindung mit einer konstanten Bitrate von R bps vor einer einzigen Slow-StartPhase. (Dies entspricht ungefähr dem Verhalten von TCP-Tahoe, wenn Verluste durch dreifache Duplikatbestätigungen erkannt werden.) In unseren numerischen Beispielen verwenden wir Werte von R und RTT, die typische Werte eines überlasteten Netzwerk widerspiegeln. Bevor wir mit der formellen Analyse beginnen, versuchen wir uns vorzustellen, welche Latenz bestünde, wenn es keine Einschränkung durch das Überlastfenster gäbe, d. h. wenn es dem Server gestattet wäre, Segmente nacheinander zu senden, bis das gesamte Objekt gesendet wurde. Um diese Frage zu beantworten, beachte man zuerst, dass eine RTT erforderlich ist, um die TCP-Verbindung einzuleiten. Nach 3.7 TCP-Überlastkontrolle 253 einer RTT sendet der Client eine Anfrage für das Objekt (das huckepack auf dem dritten Segment im Drei-Wege-Handshake von TCP gesendet wird). Nach insgesamt zwei RTTs beginnt der Client, Daten vom Server zu empfangen. Der Client empfängt Daten vom Server über eine Zeitdauer O/R, die Zeit, bis der Server das gesamte Objekt übertragen hat. Folglich beläuft sich die Gesamtlatenz in dem Fall ohne Einschränkung durch das Überlastfenster auf 2 RTT + O/R. Dies stellt eine untere Grenze dar; die Slow-Start-Prozedur mit ihrem dynamischen Überlastfenster wird diese Latenz natürlich strecken. Statisches Überlastfenster Obwohl TCP ein dynamisches Überlastfenster nutzt, ist es lehrreich, zuerst den Fall eines statischen Überlastfensters zu analysieren. Angenommen, W ist eine positive Ganzzahl, die ein statisches Überlastfenster mit fester Größe bezeichnet. Bei einem statischen Überlastfenster sind dem Server nicht mehr als W unbestätigte ausstehende Segmente gestattet. Wenn der Server die Anfrage vom Client erhält, sendet der Server sofort W Segmente nacheinander an den Client. Der Server sendet dann für jede Bestätigung, die er vom Client empfängt, ein Segment zum Netzwerk. Der SerAbbildung 3.53 Darstellung des Falls WS/R > RTT + S/R TCP-Verbindung einleiten RTT Objekt anfordern S/R RTT WS/R erstes ACK kommt zurück O/R Zeit beim Client Zeit beim Server 254 Kapitel 3 – Transportschicht ver fährt mit dem Senden eines Segments pro Bestätigung fort, bis alle Segmente des Objekts gesendet wurden. Hier sind zwei Fälle zu berücksichtigen: 1. WS/R > RTT + S/R: In diesem Fall empfängt der Server eine Bestätigung für das erste Segment im ersten Fenster, bevor er die Übertragung des ersten Fensters abgeschlossen hat. 2. WS/R < RTT + S/R: In diesem Fall überträgt der Server die Segmentmenge des ersten Fensters, bevor er eine Bestätigung für das erste Segment im Fenster empfängt. Wir betrachten zuerst Fall 1, der in Abbildung 3.53 dargestellt ist. In dieser Abbildung ist die Fenstergröße W = 4 Segmente. Eine RTT ist erforderlich, um die TCP-Verbindung einzuleiten. Nach einer RTT sendet der Client eine Anfrage für das Objekt (das huckepack auf dem dritten Segment im Drei-Wege-Handshake von TCP gesendet wird). Nach insgesamt zwei RTTs beginnt der Client, Daten vom Server zu empfangen. Vom Server kommen periodisch alle S/R Sekunden Segmente an und der Client bestätigt jedes vom Server empfangene Segment. Da der Server die erste Bestätigung erhält, bevor er mit dem Senden der Segmentmenge eines Fensters fertig ist, fährt der Server mit der Übertragung von Segmenten fort, nachdem er die Segmentmenge des ersten Fensters übertragen hat. Und weil die Bestätigungen beim Server periodisch alle S/R Sekunden ab der ersten Bestätigung ankommen, überträgt der Server kontinuierlich Segmente, bis er das gesamte Objekt übertragen hat. Nachdem der Server also mit der Übertragung des Objekts in Rate R begonnen hat, fährt er mit der Übertragung des Objekts in Rate R fort, bis das gesamte Objekt übertragen ist. Die Latenz beträgt somit 2 RTT + O/R. Jetzt betrachten wir Fall 2, der in Abbildung 3.54 dargestellt ist. In dieser Abbildung ist die Fenstergröße W = 2 Segmente. Wiederum beginnt der Client nach insgesamt zwei RTTs, Segmente vom Server zu empfangen. Diese Segmente kommen periodisch alle S/R Sekunden an und der Client bestätigt jedes vom Server empfangene Segment. Jetzt beendet der Server aber die Übertragung des ersten Fensters, bevor die erste Bestätigung vom Client ankommt. Nach dem Senden eines Fensters muss der Server deshalb aufhören und auf eine Bestätigung warten, bevor er mit der Übertragung fortfährt. Wenn eine Bestätigung ankommt, sendet der Server ein neues Segment an den Client. Nach der ersten Bestätigung kommen Bestätigungen für eine Fenstermenge an, wobei die einzelnen Bestätigungen in einem Abstand von S/R Sekunden einlaufen. Für jede dieser Bestätigungen sendet der Server genau ein Segment. Folglich wechselt der Server zwischen zwei Zuständen: Im Übertragungszustand überträgt er W Segmente, während er im Wartezustand nichts überträgt und auf eine Bestätigung wartet. Die Latenz entspricht 2 RTT zuzüglich der erforderlichen Zeit, bis der Server das Objekt überträgt, O/R, zuzüglich der Zeit, die der Server im Wartezustand verharrt. Um die Zeit zu ermitteln, die der Server im Wartezustand verharrt, sei K = O/WS; wenn O/WS keine Ganzzahl ist, dann runden wir K auf die nächste Ganzzahl auf. Man beachte, dass K die Anzahl der Fenster mit Daten ist, die im Objekt mit Größe O enthalten sind. Der Server ist zwischen der Übertragung jedes Fensters im Wartezustand, d. h. K – 1 Zeitperioden, wobei jede Periode RTT – (W – 1)S/R dauert (siehe Abbildung 3.54). Folglich ergibt sich für Fall 2: Latenz = 2 RTT + O/R + (K – 1) [S/R + RTT – WS/R] 3.7 TCP-Überlastkontrolle 255 Abbildung 3.54 Darstellung des Falls WS/R < RTT + S/R TCP-Verbindung einleiten RTT Objekt anfordern S/R WS/R RTT erstes ACK kommt zurück Zeit beim Client Zeit beim Server Kombiniert man die beiden Fälle, erhält man: Latenz = 2 RTT + O/R + (K – 1) [S/R + RTT – WS/R]+ wobei [x]+ = max(x,0). Damit ist unsere Analyse statischer Fenster beendet. Die folgende Analyse für dynamische Fenster ist komplizierter, verläuft aber genauso wie die für statische Fenster. 3.7.3 Latenz-Modellierung: dynamisches Überlastfenster Wir untersuchen jetzt die Latenz für einen Dateitransfer, wenn das dynamische Überlastfenster von TCP in Kraft ist. Der Server beginnt zuerst mit einem Überlastfenster von einem Segment und sendet ein Segment an den Client. Wenn er eine Bestätigung für das Segment erhält, erhöht er sein Überlastfenster auf zwei Segmente und sendet (im Abstand von S/R Sekunden) zwei Segmente an den Client. Empfängt er die Bestätigungen für die beiden Segmente, erhöht er das Überlastfenster auf vier Segmente und sendet (wiederum im Abstand von S/R Sekunden) vier Segmente an den Client. Der Prozess wird dann fortgesetzt, wobei das Überlastfenster in jeder RTT verdoppelt wird. Abbildung 3.55 zeigt ein Zeitablaufdiagramm für TCP. Kapitel 3 – Transportschicht 256 Abbildung 3.55 Zeitlicher Ablauf von TCP während des Slow-Starts TCP-Verbindung einleiten Objekt anfordern erstes Fenster = S/R zweites Fenster = 2S/R RTT drittes Fenster = 4S/R viertes Fenster = 8S/R vollständige Übertragung Objekt abgegeben Zeit beim Client Zeit beim Server O/S ist die Anzahl der Segmente im Objekt; im obigen Diagramm ist O/S = 15. Man betrachte die Anzahl von Segmenten, die sich in jedem der Fenster befinden. Das erste Fenster enthält ein Segment, das zweite zwei und das dritte vier. Allgemeiner ausgedrückt, enthält das k-te Fenster 2k–1 Segmente. Es sei gegeben, dass K die Anzahl der Fenster ist, die das Objekt abdeckt; dann ist im obigen Diagramm K = 4. Im Allgemeinen können wir K in Bezug auf O/S wie folgt ausdrücken: O k–1 0 1 ≥ ---- K = min k : 2 + 2 + ... + 2 S O k K = min k : 2 – 1 ≥ ---- S O K = min k : k ≥ log 2 ---- + 1 S O K = log 2 ---- + 1 S 3.7 TCP-Überlastkontrolle Nach der Übertragung der Datenmenge eines Fensters kann der Server innehalten (d. h. die Übertragung stoppen), während er auf eine Bestätigung wartet. In Abbildung 3.55 wartet der Server nach der Übertragung des ersten und zweiten Fensters, jedoch nicht nach dem dritten. Wir berechnen jetzt die Wartezeit nach der Übertragung des k-ten Fensters. Der Zeitraum vom Beginn der Übertragung des k-ten Fensters durch den Server bis zu dem Zeitpunkt, wenn der Server eine Bestätigung für das erste Segment im Fenster empfängt, beträgt S/R + RTT. Die Übertragungszeit des k-ten Fensters ist (S/R) 2k–1. Die Wartezeit ist der Unterschied dieser beiden Größen, d. h. [ S ⁄ R + RTT – 2 k–1 (S ⁄ R)] + Der Server kann potenziell nach der Übertragung jedes der ersten k – 1 Fenster warten. (Der Server ist nach der Übertragung des k-ten Fensters fertig.) Wir können jetzt die Latenz für die Übertragung der Datei berechnen. Die Latenz hat drei Komponenten: 2 RTT für die Einrichtung der TCP-Verbindung und Anforderung der Datei, O/R, die Übertragungszeit des Objekts und die Summe aller Wartezeiten. Folglich gilt: K–1 O Latenz = 2RTT + ---- + R ∑ + S k–1S ---- + RTT – 2 ---R R k=1 Der Leser sollte die obige Gleichung mit der Latenzgleichung für statische Überlastfenster vergleichen; alle Terme sind genau gleich, außer dass der Term WS/R für dynamische Fenster durch 2k–1(S/R) ersetzt wurde. Um einen kompakteren Ausdruck für die Latenz zu erhalten, sei Q die Zeit, in der der Server wartet, wenn das Objekt eine unendliche Anzahl von Segmenten enthält: S S k–1 Q = max k : RTT + ---- – ---- 2 ≥0 R R RTT k–1 ≤ 1 + -----------Q = max k : 2 S⁄R RTT Q = max k : k ≤ log 2 1 + ------------ + 1 S ⁄ R Q = RTT log 2 1 + ------------ + 1 S ⁄ R Die tatsächliche Zeit, die der Server wartet, ist P = min{Q, K – 1}. In Abbildung 3.55 ist das P = Q = 2. Wenn wir die beiden obigen Gleichungen kombinieren, erhalten wir: P O Latenz = ---- + 2RTT + R ∑ RTT + ---R- – ---R- 2 S k=1 S k – 1 257 Kapitel 3 – Transportschicht 258 Wir können die obige Formel für Latenz weiter vereinfachen: P ∑2 k–1 = 2P – 1 k=1 Kombiniert man die beiden obigen Gleichungen, erhält man folgenden Ausdruck für Latenz in geschlossener Form: S S O P Latenz = 2RTT + ---- + P RTT + ---- – ( 2 – 1 ) ---R R R Um also die Latenz zu berechnen, müssen wir einfach K und Q berechnen, P = min {Q, K – 1} setzen und P in die obige Formel einfügen. Interessant ist ein Vergleich der TCP-Latenz mit der Latenz ohne Überlastkontrolle (d. h. ohne Einschränkung durch Überlastfenster). Ohne Überlastkontrolle ist die Latenz 2 RTT + O/R, was wir als minimale Latenz bezeichnen. Wir können dann Folgendes leicht aufzeigen: Latenz P --------------------------------------------- ≤ 1 + ---------------------------------------------------Minimale Latenz [ ( O ⁄ R ) ⁄ ( RTT ) ] + 2 Die obige Formel zeigt, dass der Slow-Start von TCP die Latenz nicht erheblich erhöht, wenn RTT << O/R ist, d. h. wenn die Roundtrip-Zeit viel geringer als die Übertragungszeit des Objekts ist. Wenn wir also ein relativ großes Objekt über eine nicht überlastete Hochgeschwindigkeitsleitung senden, hat der Slow-Start eine unbedeutende Auswirkung auf die Latenz. Im Web übertragen wir aber oft viele kleine Objekte auf überlasteten Verbindungsleitungen, so dass der Slow-Start die Latenz beträchtlich erhöhen kann (wie Sie im nächsten Unterabschnitt sehen werden). Im Folgenden werden einige Beispielszenarien betrachtet. In allen Szenarien gilt S = 536 Byte; das ist ein üblicher Default-Wert für TCP. Wir verwenden eine RTT von 100 ms; dies ist ein typischer Wert für eine kontinentale oder interkontinentale Verzögerung über mäßig überlastete Verbindungsleitungen. Zuerst betrachten wir das Versenden eines eher großen Objekts mit der Größe O = 100 Kbyte. Die Anzahl der Fenster für dieses Objekt ist K = 8. Die folgende Tabelle listet die Auswirkung des SlowStart-Mechanismus auf die Latenz bei mehreren Übertragungsraten auf. R O/R P Minimale Latenz: O/R + 2 RTT Latenz mit SlowStart 28 Kbps 28,6 s 1 28,8 s 28,9 s 100 Kbps 8s 2 8,2 s 8,4 s 1 Mbps 800 ms 5 1s 1,5 s 10 Mbps 80 ms 7 0,28 s 0,98 s Die obige Aufstellung zeigt, dass der Slow-Start bei einem großen Objekt nur eine beträchtliche Verzögerung hinzufügt, wenn die Übertragungsrate hoch ist. Bei einer niedrigeren Übertragungsrate kommen die Bestätigungen relativ schnell zurück und 3.7 TCP-Überlastkontrolle 259 TCP erreicht rasch wieder seine maximale Rate. Wenn beispielsweise R = 100 Kbps ist, beträgt die gesamte Wartezeit P = 2, während die Anzahl der zu übertragenden Fenster K = 8 ist. Folglich wartet der Server nur jeweils nach den ersten beiden von insgesamt acht Fenstern. Ist andererseits R = 10 Mbps, wartet der Server nach jedem Fenster, was zu einer beträchtlichen Erhöhung der Verzögerung führt. Als Nächstes betrachten wir das Versenden eines kleinen Objekts mit der Größe O = 5 Kbyte. Die Anzahl der Fenster für dieses Objekt ist K = 4. Die folgende Tabelle listet die Auswirkung des Slow-Start-Mechanismus auf die Latenz bei verschiedenen Übertragungsraten auf. R O/R P Minimale Latenz: O/R + 2 RTT Latenz mit SlowStart 28 Kbps 1,43 s 1 1,63 s 1,73 s 100 Kbps 0,4 s 2 0,6 s 0,757 s 1 Mbps 40 ms 3 0,24 s 0,52 s 10 Mbps 4 ms 3 0,20 s 0,50 s Wiederum fügt der Slow-Start eine beträchtliche Verzögerung hinzu, wenn die Übertragungsrate hoch ist. Wenn beispielsweise R = 1 Mbps ist, wartet der Server nach jedem Fenster, so dass die Latenz mehr als das Doppelte der minimalen Latenz beträgt. Bei einer größeren RTT wird die Auswirkung des Slow-Starts für kleine Objekte bei kleineren Übertragungsraten bedeutsam. Die folgende Tabelle enthält eine Aufstellung der Auswirkungen des Slow-Starts bei RTT = 1 Sekunde und O = 5 Kbyte (K = 4). R O/R P Minimale Latenz: O/R + 2 RTT Latenz mit SlowStart 28 Kbps 1,43 s 3 3,4 s 5,8 s 100 Kbps 0,4 s 3 2,4 s 5,2 s 1 Mbps 40 ms 3 2,0 s 5,0 s 10 Mbps 4 ms 3 2,0 s 5,0 s Zusammenfassend kann man sagen, dass der Slow-Start die Latenz erheblich erhöhen kann, wenn die Objektgröße relativ klein und die RTT relativ groß ist. Leider ist dies ein häufiges Szenario, wenn Objekte über das World Wide Web gesendet werden. Ein Beispiel: HTTP Als eine Anwendung für unsere Analyse der Latenz berechnen wir die Reaktionszeit für eine Web-Seite, die über das nicht persistente HTTP gesendet wird. Angenom- 260 Kapitel 3 – Transportschicht men, die Seite besteht aus einer HTML-Basisseite und M referenzierten Bildern. Der Einfachheit halber nehmen wir an, dass jedes der M + 1 Objekte genau O Bit enthält. Beim nicht persistenten HTTP wird jedes Objekt unabhängig – eines nach dem anderen – übertragen. Die Reaktionszeit der Web-Seite ist daher die Summe der Latenzen für die einzelnen Objekte: S O S P Reaktionszeit = ( M + 1 ) 2RTT + ---- + P RTT + ---- – ( 2 – 1 ) ---- R R R Die Reaktionszeit für nicht persistentes HTTP nimmt folgende Form an: Reaktionszeit = (M + 1)O/R + 2M + 1)RTT + Latenz durch TCP-Slow-Start für jedes der M + 1 Objekte Wenn die Web-Seite viele Objekte enthält und die RTT groß ist, hat nicht persistentes HTTP natürlich eine schlechte Reaktionszeit bzw. Leistung. In den Übungen wird die Reaktionszeit für andere HTTP-Transportschemata, darunter persistente und nicht persistente Verbindungen mit parallelen Verbindungen, behandelt. Dem Leser wird die Durchsicht einer damit zusammenhängenden Analyse in [Heidemann 1997] empfohlen. 3.8 Zusammenfassung Wir haben dieses Kapitel mit einer Untersuchung der Dienste begonnen, die ein Protokoll der Transportschicht den Netzwerkanwendungen bereitstellen kann. In einem Extrem ist das Transportprotokoll sehr einfach und bietet den Anwendungen äußerst einfache Dienste, d. h. nur Multiplexen/Demultiplexen für Kommunikationsprozesse. Das UDP-Protokoll des Internets ist ein Beispiel für ein solches einfaches Transportprotokoll. Im anderen Extrem kann ein Transportprotokoll eine Vielzahl von Zusicherungen für Anwendungen bieten, z. B. zuverlässige Übertragung von Daten sowie zugesicherte Verzögerungen und Bandbreiten. Dennoch sind die Dienste, die ein Transportprotokoll bereitstellen kann, oft durch das Dienstmodell des Protokolls auf der zugrunde liegenden Vermittlungsschicht begrenzt. Wenn das Protokoll der Vermittlungsschicht für die Segmente der Transportschicht keine Verzögerungen oder Bandbreiten zusichern kann, dann kann das Protokoll der Transportschicht auch keine Verzögerungen oder Bandbreiten für die zwischen Prozessen ausgetauschten Nachrichten zusichern. Abschnitt 3.4 hat gezeigt, dass ein Transportprotokoll zuverlässigen Datentransfer auch dann bereitstellen kann, wenn die zugrunde liegende Vermittlungsschicht unzuverlässig ist. Die Bereitstellung eines zuverlässigen Datentransfers umfasst viele komplexe Aspekte, lässt sich aber meistern, wenn man Bestätigungen, Timer, Neuübertragungen und Sequenznummern sorgfältig kombiniert. Obwohl wir in diesem Kapitel zuverlässigen Datentransfer behandelt haben, sollte man bedenken, dass zuverlässiger Datentransfer grundsätzlich von Protokollen auf der Sicherungs-, Vermittlungs-, Transport- und Anwendungsschicht bereitgestellt werden kann. Jede der oberen vier Schichten des Protokollstacks kann Bestätigungen, Timer, Neuübertragungen und Sequenznummern implementieren und der jeweils darunter liegenden Schicht zuverlässigen Datentransfer bieten. Im Laufe der Jahre haben Techniker und Computerwissenschaftler unabhängig Protokolle für die Siche- 3.8 Zusammenfassung rungs-, Vermittlungs-, Transport- und Anwendungsschicht entwickelt, die zuverlässigen Datentransfer bereitstellen (allerdings sind viele dieser Protokolle still und leise wieder verschwunden). In Abschnitt 3.5 wurde TCP, das verbindungsorientierte und zuverlässige Transportprotokoll des Internets, genauer untersucht. Sie haben erfahren, dass TCP komplex ist und Verbindungsmanagement, Flusskontrolle, Schätzungen der RoundtripZeit sowie zuverlässigen Datentransfer umfasst. Tatsächlich ist TCP noch komplexer als in unserer Beschreibung: Wir haben absichtlich eine Vielzahl von TCP-Patches, -Fixes und Verbesserungen nicht erwähnt, die in vielen TCP-Versionen implementiert werden. Diese Komplexität wird allerdings vor der Netzwerkanwendung verborgen. Wenn ein Client auf einem Host Daten zuverlässig an einen Server auf einem anderen Host senden will, öffnet er einfach ein TCP-Socket zum Server und pumpt die Daten in dieses Socket. Die Client/Server-Anwendung hat von der ganzen TCP-Komplexität keine Kenntnis. In Abschnitt 3.6 wurde Überlastkontrolle aus einer allgemeinen Perspektive behandelt, während in Abschnitt 3.7 beschrieben wurde, wie TCP Überlastkontrolle implementiert. Sie haben erfahren, dass Überlastkontrolle für ein gut funktionierendes Netzwerk unerlässlich ist. Ohne Überlastkontrolle kann ein Netzwerk schnell in einen Stau geraten, so dass kaum oder überhaupt keine Daten mehr von Ende zu Ende übertragen werden können. Abschnitt 3.7 hat gezeigt, dass TCP einen Überlastkontrollmechanismus von Ende zu Ende implementiert, der seine Übertragungsrate »additiv« erhöht, wenn der Pfad der TCP-Verbindung als nicht überlastet gilt, und sie »multiplikativ« senkt, wenn ein Datenverlust erkannt wird. Mit diesem als »Additive Increase / Multiplicative Decrease« bezeichneten Mechanismus wird auch angestrebt, jeder TCP-Verbindung auf einer überlasteten Verbindungsleitung einen fairen Anteil an der Leitungsbandbreite zukommen zu lassen. Außerdem wurde die Auswirkung des TCP-Verbindungsaufbaus und des Slow-Starts auf die Latenz geprüft. Wir haben festgestellt, dass Verbindungsaufbau und Slow-Start in vielen wichtigen Szenarien erheblich zur Ende-zu-Ende-Verzögerung beitragen. Außerdem wurde deutlich gemacht, dass die TCP-Überlastkontrolle immer noch einen Bereich intensiver Forschungsarbeiten darstellt und im Laufe der nächsten Jahre sicherlich weiterentwickelt wird. In Kapitel 1 wurde gesagt, dass sich ein Computernetzwerk in die »Netzwerkperipherie« und den »Netzwerkkern« aufteilen lässt. Die Netzwerkperipherie deckt alles ab, was in den Endsystemen passiert. Nachdem wir die Anwendungs- und Transportschicht behandelt haben, ist unsere Diskussion der Netzwerkperipherie abgeschlossen. Nun ist es an der Zeit, den Netzwerkkern zu erforschen! Diese Reise beginnt im nächsten Kapitel mit der Vermittlungsschicht und wird in Kapitel 5 mit der Sicherungsschicht fortgeführt. 261 Kapitel 3 – Transportschicht 262 WIEDERHOLUNGSFRAGEN Abschnitte 3.1 bis 3.3 1. Man betrachte eine TCP-Verbindung zwischen Host A und Host B. Angenommen, die TCP-Segmente von Host A nach Host B haben Quellportnummer x und Zielportnummer y. Wie lauten die Quell- und Zielportnummern für die Segmente von Host B nach Host A? 2. Beschreiben Sie, warum sich ein Anwendungsentwickler möglicherweise dafür entscheidet, eine Anwendung über UDP und nicht über TCP zu betreiben. 3. Kann eine Anwendung in den Genuss eines zuverlässigen Datentransfers kommen, auch wenn sie über UDP läuft? Falls ja, wie? Abschnitt 3.5 4. Richtig oder falsch: a. Host A sendet Host B eine große Datei über eine TCP-Verbindung. Host B hat keine Daten an A zu senden. Host B sendet keine Bestätigungen an Host A, weil Host B die Bestätigungen nicht Huckepack auf den Daten senden kann. b. Die Größe des TCP-Empfangsfensters RcvWindow ändert sich nie während der gesamten Dauer der Verbindung. c. Host A sendet Host B eine große Datei über eine TCP-Verbindung. Die Anzahl der unbestätigten Bytes, die A sendet, darf die Größe des Empfangspuffers nicht übersteigen. d. Host A sendet eine große Datei an Host B über eine TCP-Verbindung. Wenn die Sequenznummer für ein Segment dieser Verbindung m ist, dann lautet die Sequenznummer für das anschließende Segment m + 1. e. Das TCP-Segment hat in seinem Header ein Feld für RcvWindow. f. Die letzte SampleRTT in einer TCP-Verbindung ist beispielsweise gleich 1 s. Dann wird Timeout für die Verbindung notwendigerweise auf einen Wert von > = 1 s gesetzt. g. Host A sendet Host B ein Segment mit Sequenznummer 38 und 4 Datenbyte. Die Bestätigungsnummer im gleichen Segment muss dann 42 sein. 5. Angenommen, A sendet zwei aufeinander folgende TCP-Segmente an B. Das erste Segment hat Sequenznummer 90 und das zweite 110. a. Wie viele Daten enthält das erste Segment? b. Wenn beispielsweise das erste Segment verloren geht, das zweite aber bei B ankommt, wie lautet dann die Bestätigungsnummer in der Bestätigung, die B an A sendet? 6. Man betrachte das Telnet-Beispiel in Abschnitt 3.5. Ein paar Sekunden, nachdem der Benutzer den Buchstaben ‚C’ eingegeben hat, tippt er den Buchstaben ‚R’. Wie viele Segmente werden nach dem Eintippen des Buchstabens ‚R’ gesendet und was wird in die Sequenznummern- und Bestätigungsfelder der Segmente eingefügt? Abschnitt 3.7 7. Angenommen, zwei TCP-Verbindungen sind auf einer Flaschenhalsleitung mit Rate R bps aktiv. Beide Verbindungen müssen eine riesige Datei (in der gleichen Richtung über die Flaschenhalsleitung) senden. Die Übertragungen der Dateien beginnen zur gleichen Zeit. Welche Übertragungsrate kann TCP den beiden Verbindungen jeweils zur Verfügung stellen? 8. Richtig oder falsch: Wenn bei der Überlastkontrolle in TCP ein Timer beim Sender abläuft, wird der Grenzwert auf die Hälfte seines vorherigen Werts gesetzt. Übungen 263 ÜBUNGEN 3.1 Angenommen, Client A leitet eine Telnet-Sitzung zu Server S ein. Etwa zur gleichen Zeit leitet auch Client B eine Telnet-Sitzung zu Server S ein. Geben Sie die möglichen Quell- und Zielportnummern für: a. die von A nach S gesendeten Segmente. b. die von B nach S gesendeten Segmente. c. die von S nach A gesendeten Segmente. d. die von S nach B gesendeten Segmente. e. Wenn A und B unterschiedliche Hosts sind, ist es dann möglich, dass die Quellportnummer in den Segmenten von A nach S die gleiche wie die von B nach S ist? f. Was ist, wenn es sich bei A und B um den gleichen Host handelt? 3.2 UDP und TCP verwenden das Einer-Komplement für ihre Prüfsummen. Angenommen, Sie haben die folgenden drei 8-Bit-Wörter: 01010101, 01110000, 11001100. Wie lautet das Einer-Komplement für die Summe dieser Wörter? Zeigen Sie die ganze Berechnung. Warum nimmt UDP das Einer-Komplement der Summe; warum wird nicht einfach die Summe verwendet? Wie erkennt der Empfänger Fehler, wenn das Einer-Komplement-Schema angewandt wird? Ist es möglich, dass ein 1-Bit-Fehler unerkannt bleibt? Was ist mit einem 2-Bit-Fehler? 3.3 Betrachten Sie unsere Motivation für die Korrektur von Protokoll rdt2.1. Zeigen Sie, dass dieser Empfänger, wenn er mit dem Sender von Abbildung 3.11 läuft, dazu führen kann, dass Sender und Empfänger in eine Verklemmung geraten können, was bedeutet, dass beide auf ein Ereignis warten, das nie eintritt. rdt_rcv(rcvpkt) && notcorrupt(rcvpkt) && has_seq0(rcvpkt) extract(rcvpkt,data) deliver_data(data) compute chksum make_pkt(sendpkt,ACK,chksum) udt_send(sndpkt) rdt_rcv(rcvpkt) && (corrupt(rcvpkt) || has_seq0(rcvpkt)) rdt_rcv(rcvpkt) && (corrupt(rcvpkt) || has_seq1(rcvpkt)) compute chksum make_pkt(sndpkt,NAK,chksum) udt_send(sndpkt) warte auf 0 von unten warte auf 1 von unten compute chksum make_pkt(sndpkt,NAK,chksum) udt_send(sndpkt) rdt_rcv(rcvpkt) && notcorrupt(rcvpkt) && has seq1(recvpkt) extract(rcvpkt,data) deliver_data(data) compute chksum make_pkt(sendpkt,ACK,chksum) udt_send(sndpkt) 3.4 In Protokoll rdt3.0 haben die ACK-Pakete, die vom Empfänger zum Sender fließen, keine Sequenznummern (obwohl sie ein ACK-Feld haben, das die Sequenznummer des Pakets enthält, das sie bestätigen). Warum brauchen unsere ACKPakete keine Sequenznummern? 3.5 Zeichnen Sie die FSM für die Empfängerseite von Protokoll rdt3.0. ➔ 264 ➔ Kapitel 3 – Transportschicht 3.6 Beschreiben Sie kurz den Betrieb von Protokoll rdt3.0, wenn Daten- und Bestätigungspakete beschädigt werden. Ihre Kurzbeschreibung sollte derjenigen von Abbildung 3.16 ähneln. 3.7 Betrachten Sie einen Kanal, der Pakete verlieren kann, aber eine maximale bekannte Verzögerung hat. Modifizieren Sie das Protokoll rdt2.1, um ein SenderTimeout und Retransmit einzubeziehen. Erklären Sie, warum Ihr Protokoll korrekt über diesen Kanal kommunizieren kann. 3.8 Die Senderseite von rdt3.0 ignoriert einfach alle empfangenen Pakete (d. h., sie unternimmt keine Aktion), die fehlerhaft sind oder – wenn es sich um ein Bestätigungsfeld handelt – im Bestätigungsnummernfeld einen falschen Wert haben. Wir nehmen beispielsweise an, dass rdt3.0 unter solchen Umständen das aktuelle Datenpaket einfach erneut überträgt. Würde das Protokoll immer noch funktionieren? (Hinweis: Überlegen Sie, was passiert, wenn nur Bitfehler auftreten würden; es gäbe keine Paketverluste, aber vorzeitige Timeouts. Überlegen Sie auch, wie oft das n-te Paket gesendet wird, wenn sich n Unendlich nähert.) 3.9 Betrachten Sie das Beispiel in Abbildung 3.17. Wie groß müsste die Fenstergröße sein, um eine Kanalauslastung von mehr als 90% zu erreichen? 3.10 Entwerfen Sie ein zuverlässiges Datentransferprotokoll mit Pipelining, das nur negative Bestätigungen verwendet. Wie schnell würde Ihr Protokoll auf verlorene Pakete reagieren, wenn die Ankunftsrate von Daten zum Sender niedrig ist? Und wenn sie hoch ist? 3.11 Bei dem generischen Selective-Repeat-Protokoll (SR), das in Abschnitt 3.4.4 beschrieben wurde, überträgt der Sender eine Nachricht, sobald sie verfügbar ist (falls sie sich im Fenster befindet), ohne auf eine Bestätigung zu warten. Angenommen, wir wünschen uns ein SR-Protokoll, das jeweils zwei Nachrichten gleichzeitig sendet. Das heißt, der Sender sendet zuerst zwei Nachrichten als Paar und das nächste Paar erst, wenn er weiß, dass die ersten beiden korrekt empfangen wurden. Wir nehmen an, dass der Kanal zwar Nachrichten verlieren kann, dass aber keine beschädigt oder umgeordnet werden. Entwerfen Sie ein Fehlerkontrollprotokoll für den unidirektionalen zuverlässigen Transfer von Nachrichten. Erstellen Sie eine FSM-Beschreibung des Senders und des Empfängers. Beschreiben Sie das Format der zwischen Sender und Empfänger ausgetauschten Pakete. Wenn Sie einen anderen als die in Abschnitt 3.4 beschriebenen Prozeduraufrufe (z. B. udt_send(), start_timer(), rdt_rcv() usw.) verwenden, beschreiben Sie klar die jeweiligen Aktionen. Geben Sie ein Beispiel (eine Timeline-Übersicht für Sender und Empfänger), um aufzuzeigen, wie Ihr Protokoll nach einem verlorenen Paket fortfährt (Recovery). 3.12 Betrachten Sie ein Szenario, bei dem ein Host A gleichzeitig Nachrichten an die Hosts B und C senden will. A ist mit B und C über einen Broadcast-Kanal verbunden. Ein von A gesendetes Paket wird über den Kanal also an B und C befördert. Der Broadcast-Kanal, über den A, B und C verbunden sind, kann unabhängig Nachrichten verlieren und beschädigen (so dass z. B. eine Nachricht von A bei B korrekt ankommt, nicht aber bei C). Entwerfen Sie ein Stop-and-Wait-ähnliches Fehlerkontrollprotokoll für die zuverlässige Übertragung eines Pakets von A nach B und C, so dass A die Daten von der höheren Schicht erst erhält, wenn er weiß, dass sowohl B als auch C das aktuelle Paket korrekt empfangen haben. Erstellen Sie FSM-Beschreibungen von A und C. (Hinweis: Die FSM für B und C sollte im ➔ Übungen ➔ 265 Wesentlichen gleich sein.) Außerdem beschreiben Sie das bzw. die verwendeten Paketformate. 3.13 Betrachten Sie das Go-Back-N-Protokoll mit einer Senderfenstergröße von 3 und einem Sequenznummerbereich von 1.024. Angenommen, dass zum Zeitpunkt t das nächste Paket der Reihenfolge, das der Empfänger erwartet, eine Sequenznummer von k hat. Gehen Sie davon aus, dass das Medium die Reihenfolge der Nachrichten nicht verändert. Beantworten Sie folgende Fragen: a. Welche Serien von Sequenznummern können sich zum Zeitpunkt t im Senderfenster befinden? Erklären Sie Ihre Antwort. b. Welche Werte können sich im ACK-Feld der Nachricht befinden, die sich in Zeitpunkt t zum Sender zurück ausbreitet? Erklären Sie Ihre Antwort. 3.14 Gehen Sie von zwei Netzwerkeinheiten A und B aus. B hat Datennachrichten anstehen, die entsprechend den folgenden Konventionen an A gesendet werden. Wenn A eine Anfrage von der höheren Schicht erhält, die nächste Datennachricht (D) von B zu holen, muss A eine Anfragenachricht (R) auf dem Kanal von A nach B an B senden. Nur falls B eine R-Nachricht empfängt, kann sie eine Datennachricht (D) auf dem Kanal von B nach A an A zurücksenden. A sollte genau eine Kopie von jeder D-Nachricht an die höhere Schicht abgeben. R-Nachrichten können im Kanal von A nach B verloren gehen (aber nicht beschädigt werden). Einmal versendete D-Nachrichten werden immer korrekt zugestellt. Die Verzögerung auf beiden Kanälen ist unbekannt und variabel. Entwerfen Sie eine FSMBeschreibung eines Protokolls, das die entsprechenden Mechanismen implementiert, um den verlustbehafteten Kanal von A nach B zu kompensieren, und das Nachrichten implementiert, die an die höhere Schicht in Einheit A weitergegeben werden. Verwenden Sie nur die Mechanismen, die absolut notwendig sind. 3.15 Betrachten Sie die Go-Back-N- und Selective-Repeat-Protokolle. Angenommen, der Sequenznummernraum hat Größe k. Welche maximal zulässige Größe kann das Senderfenster haben, so dass Probleme wie die in Abbildung 3.26 bei beiden Protokollen nicht vorkommen können? 3.16 Beantworten Sie folgende Fragen mit »Richtig« oder »Falsch« und erklären Sie kurz Ihre Antwort: a. Beim Selective-Repeat-Protokoll ist es möglich, dass der Sender ein ACK für ein Paket empfängt, das außerhalb seines aktuellen Fensters liegt. b. Bei Go-Back-N ist es möglich, dass der Sender ein ACK für ein Paket empfängt, das außerhalb seines aktuellen Fensters liegt. c. Das Alternating-Bit-Protokoll ist das Gleiche wie das Selective-Repeat-Protokoll, mit einer Sender- und Empfängerfenstergröße von 1. d. Das Alternating-Bit-Protokoll ist das Gleiche wie das Go-Back-N-Protokoll, mit einer Sender- und Empfängerfenstergröße von 1. 3.17 Denken Sie an die Übertragung einer sehr großen Datei mit L Byte von Host A an Host B. Gehen Sie von einer MSS von 1.460 Byte aus. a. Welcher maximale Wert von L ist möglich, so dass TCP-Sequenznummern nicht erschöpft werden? Denken Sie daran, dass das Sequenznummernfeld in TCP vier Byte groß ist. b. Für den in a. ermittelten L-Wert stellen Sie fest, wie lange es dauert, um die Datei zu übertragen. Gehen Sie davon aus, dass an jedes Segment ein Header der Transport-, Ver-mittlungs-, und Sicherungsschicht von insgesamt 66 ➔ Kapitel 3 – Transportschicht 266 ➔ Byte angefügt wird, bevor das resultierende Paket über eine 10-Mbps-Verbindungsleitung versendet wird. Ignorieren Sie Fluss- und Überlastkontrolle, was bedeutet, dass A die Segmente nacheinander und kontinuierlich herauspumpen kann. 3.18 Abbildung 3.31 zeigt, dass TCP wartet, bis es drei Duplikat-ACKs empfangen hat, bevor es ein Fast-Retransmit durchführt. Warum haben sich die TCP-Designer Ihrer Meinung nach dafür entschieden, nach dem ersten Duplikat-ACK für ein Segment kein Fast-Retransmit durchzuführen? 3.19 Betrachten Sie die TCP-Prozedur für die Schätzung der RTT. Angenommen, x = 0,1, SampleRTT1 die letzte Muster-RTT, SampleRTT2 die vorletzte Muster-RTT usw. c. Gehen Sie für eine bestimmte TCP-Verbindung davon aus, dass vier Bestätigungen mit entsprechenden Muster-RTTs – SampleRTT4, SampleRTT3, SampleRTT2 und SampleRTT1 – zurückgegeben wurden. Drücken Sie EstimatedRTT in Bezug zu den vier Muster-RTTs aus. d. Generalisieren Sie Ihre Formel für n Muster-Roundtrip-Zeiten. e. Es sei gegeben, dass n bei der Formel in Teil b. sich Unendlich nähert. Erklären Sie, warum man diese Art der Ermittlung des Durchschnitts als »Exponential Weighted Moving Average« (EWMA) bezeichnet. 3.20 In Abbildung 3.51 ist die Konvergenz des TCP-Algorithmus Additive-Increase/Multiplicative-Decrease dargestellt. Es wird angenommen, dass TCP statt eines Multiplicative-Decrease die Fenstergröße um einen konstanten Wert erhöht. Würde das resultierende Additive-Increase/Additive-Decrease auf einen anteilig gleichen Algorithmus konvergieren? Erklären Sie Ihre Antwort mit Hilfe eines Diagramms, ähnlich Abbildung 3.51. 3.21 Denken Sie an das idealisierte Modell für die Dauerzustandsdynamik von TCP. In der Zeitperiode, ab der die Fenstergröße der Verbindung von (W·MSS)/2 bis W·MSS schwankt, geht nur ein Paket verloren (ganz am Ende der Periode). a. Weisen Sie nach, dass die Verlustrate wie folgt aussieht: 1 L = Verlustrate = ------------------------3 2 3 --- w + --- w 8 4 b. Verwenden Sie das obige Ergebnis, um aufzuzeigen, dass bei einer Verlustrate L einer Verbindung die durchschnittliche Bandbreite dieser Verbindung ungefähr wie folgt angegeben werden kann: ~ 1,22 · MSS/[RTT · sqrt(L)] 3.22 Sie möchten ein Objekt mit der Größe O = 100 Kbyte vom Server zum Client senden. Seien S = 536 Byte und RTT = 100 ms. Gehen Sie davon aus, dass das Transportprotokoll statische Fenster mit der Fenstergröße W verwendet. a. Ermitteln Sie die mögliche minimale Latenz für eine Übertragungsrate von 28 Kbps. Bestimmen Sie die minimale Fenstergröße, die diese Latenz erreicht. b. Wiederholen Sie a. für 100 Kbps. c. Wiederholen Sie a. für 1 Mbps. d. Wiederholen Sie a. für 10 Mbps. 3.23 Angenommen, TCP würde sein Überlastfenster während des Slow-Starts um Zwei statt Eins für jede empfangene Bestätigung erhöhen. Folglich besteht das erste ➔ Übungen ➔ 267 Fenster aus einem Segment, das zweite aus drei Segmenten, das dritte aus neun Segmenten usw. Lösen Sie für diese Slow-Start-Prozedur folgende Aufgaben: a. Drücken Sie K in Bezug zu O und S aus. b. Drücken Sie Q in Bezug zu RTT, S und R aus. c. Drücken Sie die Latenz in Bezug zu P = min(K – 1, Q), O, R und RTT aus. 3.24 Betrachten Sie den Fall von RTT = 1 Sekunde und O = 100 Kbyte. Arbeiten Sie ein Diagramm (ähnlich denen in Abschnitt 3.5.2) aus, das die minimale Latenz (O/R + 2 RTT) mit der Latenz beim Slow-Start für R = 28 Kbps, 100 Kbps, 1 Mbps und 10 Mbps vergleicht. 3.25 Richtig oder falsch? a. Wenn eine Web-Seite aus genau einem Objekt besteht, dann haben nicht persistente und persistente Verbindungen hinsichtlich der Reaktionszeit genau die gleiche Leistung. b. Betrachten Sie das Senden eines Objekts mit der Größe O vom Server zu einem Browser über TCP. Wenn O > S, wobei S die maximale Segmentgröße (MSS) ist, dann wartet der Server mindestens einmal. c. Angenommen, eine Web-Seite besteht aus zehn Objekten mit jeweils einer Größe von O Bit. Beim persistenten HTTP ist der RTT-Anteil an der Reaktionszeit 20 RTT. d. Angenommen, eine Web-Seite besteht aus zehn Objekten mit jeweils einer Größe von O Bit. Beim nicht persistenten HTTP mit fünf parallelen Verbindungen ist der RTT-Anteil an der Reaktionszeit 12 RTT. 3.26 Die Analyse für dynamische Fenster im Textteil basiert auf der Annahme, dass es eine Verbindungsleitung zwischen Server und Client gibt. Erstellen Sie die Analyse neu für T Verbindungsleitungen zwischen Server und Client. Gehen Sie davon aus, dass das Netzwerk nicht überlastet ist, so dass Pakete keinen Warteschlangenverzögerungen ausgesetzt sind. Die Pakete müssen allerdings eine Store-and-Forward-Verzögerung über sich ergehen lassen. Die Definition von RTT entspircht der im Abschnitt über die TCP-Überlastkontrolle enthaltenen. (Hinweis: Die Zeit, die zwischen dem Absenden des ersten Segments durch den Server und dem Empfang der Bestätigung vegeht, ist TS/R + RTT.) 3.27 Erinnern Sie sich an die Diskussion über die Reaktionszeit für eine Web-Seite in Abschnitt 3.7.3. Ermitteln Sie für den Fall nicht persistenter Verbindungen einen allgemeinen Ausdruck für den Anteil an der Reaktionszeit, der auf den TCP-SlowStart zurückzuführen ist. 3.28 Beim persistenten HTTP werden alle Objekte über die gleiche TCP-Verbindung gesendet. Wie in Kapitel 2 beschrieben wurde, ist eine der Motivationen für persistentes HTTP (mit Pipelining), die Auswirkungen des Verbindungsaufbaus und des Slow-Starts von TCP auf die Reaktionszeit für eine Web-Seite zu verringern. In dieser Übung untersuchen wir die Reaktionszeit für persistentes HTTP. Angenommen, der Client fordert alle Bilder gleichzeitig an, aber erst, wenn er die ganze HTML-Basisseite erhalten hat. Es seien M + 1 die Anzahl der Objekte und O die Größe jedes Objekts. a. Erklären Sie, dass die Reaktionszeit aufgrund des Slow-Starts die Form (M + 1)O/R + 3RTT + Latenz annimmt. Vergleichen Sie die Verteilung der RTTs in diesem Ausdruck mit der beim nicht persistenten HTTP. ➔ Kapitel 3 – Transportschicht 268 ➔ b. Nehmen Sie an, dass K = log2(O/S + 1) eine Ganzzahl ist; folglich überträgt das letzte Fenster der HTML-Basisdatei die Segmentmenge eines ganzen Fensters, d. h., Fenster K überträgt 2K–1 Segmente. P’ = min{Q, K’ – 1} und K’ = O log 2 ( M + 1 ) ---+1 S Beachten Sie, dass K’ die Anzahl der Fenster ist, die ein Objekt mit der Größe (M + 1)O abdeckt, während P’ die Anzahl der Warteperioden ist, wenn das große Objekt über eine einzige TCP-Verbindung gesendet wird. Nehmen wir (falsch!) an, dass der Server die Bilder senden kann, ohne auf die formelle Anfrage für die Bilder vom Client warten zu müssen. Weisen Sie nach, dass die Reaktionszeit diejenige für das Senden eines großen Objekts mit der Größe (M + 1)O ist: Ungefähre Reaktionszeit ( M + 1 )O = 2RTT + ------------------------ + R S S P’ P’ RTT + --- – ( 2 – 1 ) --R R c. Die tatsächliche Reaktionszeit beim persistenten HTTP ist etwas größer als die Annäherung. Der Grund ist, dass der Server auf eine Anfrage für die Bilder warten muss, bevor er sie sendet. Die Wartezeit zwischen dem K-ten Fenster und Fenster (K + 1) ist nicht [S/R + 2K – 1(S/R]+, sondern RTT. Beweisen Sie Folgendes: S ( M + 1 )O Reaktionszeit = 3RTT + ------------------------ + P’ RTT + --- – R R S S S K–1 P’ ( 2 – 1 ) --- – --- + RTT – ---2 R R R + 3.29 Betrachten Sie das Szenario mit RTT = 100 ms, O = 5 Kbyte, S = 536 Byte und M = 10. Erstellen Sie ein Diagramm, das die Reaktionszeiten für nicht persistente und persistente Verbindungen mit 28 Kbps, 100 Kbps, 1 Mbps und 10 Mbps vergleicht. Beachten Sie, dass persistentes HTTP bei allen genannten Übertragungsraten außer 28 Kbps eine wesentlich niedrigere Reaktionszeit als nicht persistentes HTTP hat. 3.30 Wiederholen Sie die obige Übung für den Fall von RTT = 1 s, O = 5 Kbyte, S = 536 Byte und M = 10. Beachten Sie bei diesen Parametern, dass persistentes HTTP bei allen genannten Übertragungsraten eine beträchtlich niedrigere Reaktionszeit als nicht persistentes HTTP aufweist. 3.31 Betrachten Sie nicht persistentes HTTP mit parallelen TCP-Verbindungen. Browser arbeiten normalerweise in diesem Modus, wenn sie HTTP/1.0 verwenden. Sei X die maximale Anzahl an parallelen Verbindungen, die der Client (Browser) gleichzeitig öffnen kann. In diesem Modus benutzt der Client zuerst eine TCP-Verbindung, um die HTML-Basisdatei zu holen. Nach Empfang der HTML-Basisdatei baut der Client M/X Gruppen von TCP-Verbindungen auf, wobei jede Gruppe X parallele Verbindungen hat. Erklären Sie, dass die gesamte Reaktionszeit folgende Form annimmt: ➔ Übungen und Programmieraufgaben ➔ Reaktionszeit = (M + 1)O/R + 2(M/X + 1) RTT + Latenz aufgrund der Slow-Start-Wartezeit Vergleichen Sie die Verteilung des Terms in Bezug zu RTT bei persistenten und nicht persistenten (nicht parallelen) Verbindungen. DISKUSSIONSFRAGEN 3.1 Betrachten Sie Streaming von gespeichertem Audio. Ist es sinnvoller, die Anwendung über UDP oder TCP auszuführen? Welches der beiden Protokolle wird von RealNetworks benutzt? Warum? Sind Ihnen weitere Produkte für das Streaming von gespeichertem Audio bekannt? Welches Transportprotokoll verwenden sie und warum? PROGRAMMIERAUFGABEN In dieser Programmieraufgabe werden Sie Code für die Sende- und Empfangsseite auf Transportebene für die Implementierung eines einfachen zuverlässigen Datentransferprotokolls – entweder das Alternating-Bit-Protokoll oder ein Go-Back-N-Protokoll – schreiben. Dies sollte Spaß machen, weil sich Ihre Implementierung kaum davon unterscheiden wird, was für die wirkliche Welt nötig wäre. Da Sie wahrscheinlich nicht über Standalone-Rechner (mit einem Betriebssystem, das Sie modifizieren können) verfügen, muss Ihr Code in einer simulierten Hardware/Software-Umgebung ausgeführt werden. Die für Ihre Routinen bereitgestellte Programmieroberfläche (d. h. der Code, der Ihre Instanzen von oben (von Schicht 5) und von unten (von Schicht 3) aufrufen würde), ähnelt stark einer echten Unix-Umgebung. (Tatsächlich sind die in dieser Programmieraufgabe beschriebenen Softwareoberflächen viel realistischer als die Sender und Empfänger mit unendlicher Schleife, die man in vielen Lehrbüchern findet.) Außerdem wird das Stoppen und Starten von Timern simuliert und TimerInterrupts werden Ihre Timer-Behandlungsroutine aktivieren. Ausführliche Einzelheiten zu dieser Programmierübung sowie einen C-Code, den Sie für das Erstellen der simulierten Hardware/Software-Umgebung benötigen, finden Sie auf http://www.awl.com/kurose-ross. 269 Kapitel 3 – Transportschicht 270 INTERVIEW Sally Floyd Sally Floyd ist Forscherin am AT&T Center for Internet Research am ICSI-Institut (ACIRI), das sich mit Internet- und Vernetzungsfragen beschäftigt. Sie ist in der Industrie durch ihre Internet-Protokolldesigns, insbesondere zuverlässiges Multicast, Überlastkontrolle (TCP), Paket-Scheduling (RED) sowie Protokollanalyse bekannt. Sally erhielt ihren B.A. in Soziologie und ihren M.S. und Ph.D. in Computerwissenschaft an der University of California, Berkeley. õ Was hat Sie dazu bewegt, Computerwissenschaften zu studieren? Was hat Ihr Interesse an diesem Gebiet geweckt? Nach meinem Soziologiestudium musste ich mir Gedanken darüber machen, wie ich meinen Lebensunterhalt verdienen würde. Es ergab sich für mich eine zweijährige Assistententätigkeit im Elektronikforschungsbereich am örtlichen College, wonach ich dann schließlich zehn Jahre im Bereich der Elektronik und Computerwissenschaften tätig war. Das umfasste acht Jahre als Computer Systems Engineer für die Computer, die die BART-Züge (Bay Area Rapid Transit) steuern. Später entschloss ich mich zum Studium einer mehr formellen Computerwissenschaft und bewarb mich an der Graduate School beim Computer Science Department der UC Berkeley. õ Was hat Sie dazu bewegt, sich auf Vernetzung zu spezialisieren? An der Graduate School entstand mein Interesse an theoretischer Computerwissenschaft. Ich arbeitete zuerst an der Wahrscheinlichkeitsanalyse von Algorithmen und später an Computerlerntheorie. Einen Tag im Monat arbeitete ich außerdem am LBL (Lawrence Berkeley Laboratory). Mein Büro befand sich genau gegenüber von dem von Van Jacobson, der zur damaligen Zeit an TCP-Überlastkontrollalgorithmen arbeitete. Van fragte mich, ob ich den Sommer über an Analysen von Algorithmen für ein netzwerkbezogenes Problem, das mit der unerwünschten Synchronisation periodischer Routing-Nachrichten zu tun hatte, arbeiten möchte. Ich fand das sehr interessant und nahm das Angebot an. Nach meinem Diplom bot mir Van Jacobson eine Vollzeitbeschäftigung im Vernetzungsbereich an. Ich hatte eigentlich nicht vor, so lange im Vernetzungsbereich zu arbeiten. Ich finde Netzwerkforschung inzwischen aber erfüllender als theoretische Computerwissenschaft. Ich fühle mich wohler mit praxisnaher Arbeit, deren Ergebnisse greifbarer sind. õ Was war Ihre erste Stelle in der Computerindustrie? Welches Aufgabengebiet hatten Sie? Mein erster Computerjob war bei BART (Bay Area Rapid Transit) von 1975 bis 1982. Ich arbeitete dort an den Computern, die die BART-Züge steuern. Ich begann als Wartungstechnikerin für die verschiedenen dezentralen Computersysteme des BARTSystems. Dies umfasste ein zentrales Rechensystem und ein verteiltes Minicomputersystem für die Kontrolle der Zugbewegung, ein System aus DEC-Computern für die Anzeige von Werbungen und Fahrplänen sowie ein System aus Modcomp-Computern für die Erfassung von Informationen von den Fahrkartenschaltern. Meine letzten Jahre bei ➔ Interview ➔ BART arbeitete ich an einem gemeinsamen BART/LBL-Projekt für die Ablösung der alternden BART-Computeranlagen. õ Was ist der interessanteste Teil Ihres Aufgabenbereichs? Der interessanteste Teil ist sicherlich die eigentliche Forschungsarbeit. Derzeit bedeutet das die Entwicklung und Erforschung eines neuen Mechanismus für gleichungsbasierte Ende-zu-Ende-Überlastkontrolle. Damit soll TCP nicht etwa abgelöst werden. Vielmehr könnte man damit für Unicast-Verkehr, wie beispielsweise einen ratenadaptiven Echtzeitverkehr, hohe Ratenänderungen bzw. die Halbierung der Senderate aufgrund eines einzigen Paketverlusts vermeiden. Eine auf Gleichungen basierte Überlastkontrolle ist auch als potenzielle Grundlage für Multicast interessant. Weitere Informationen hierüber befinden sich auf der Web-Seite unter http:// www.psc.edu/networking/tcp_friendly.html. õ Wie sieht Ihrer Meinung nach die Zukunft der Computernetzwerke/ des Internets aus? Eine Möglichkeit ist, dass die im Internet-Verkehr häufig vorkommende Überlastung abgebaut werden kann, wenn gebührenbasierte Mechanismen eingeführt werden und die verfügbare Bandbreite schneller als die Nachfrage steigt. Meiner Meinung nach zeichnet sich ein Trend hin zu geringeren Überlastungen ab, wobei mittelfristig ein gelegentlicher Kollaps durch Überlastungen nicht auszuschließen ist. Die Zukunft des Internets oder der Internet-Architektur ist mir überhaupt nicht klar. Es gibt viele Faktoren, die zu einem schnellen Wandel beitragen, so dass es schwierig ist vorauszusagen, wie sich das Internet oder die Internet-Architektur weiterentwickeln wird, und schon gar nicht, wie erfolgreich diese Weiterentwicklung angesichts der vielen potenziellen Fallen sein wird. õ Durch welche Leute wurden Sie beruflich inspiriert? Richard Karp, mein Betreuer an der Graduate School, zeigte mir im Wesentlichen, wie man richtige Forschungsarbeit betreibt, Meinem »Gruppenleiter« bei LBL, Van Jacobson, verdanke ich mein Interesse an Vernetzung und meine Kenntnisse der InternetInfrastruktur. Dave Clark inspirierte mich durch seine klare Sicht der Internet-Architektur und seine Rolle in der Entwicklung dieser Architektur im Rahmen von Forschungsarbeiten, Veröffentlichungen und Teilnahmen an der IETF und anderen öffentlichen Foren. Deborah Estrin hat mich durch ihren Weitblick und ihre Fähigkeit, kluge Entscheidungen über Projekte zu treffen, inspiriert. Einer der Gründe, warum ich meinen Arbeitsbereich in den letzten zehn Jahren auf Netzwerkforschung verlagert habe, ist der, dass es so viele Leute gibt, die auf diesem Gebiet arbeiten, die ich mag und respektiere und die mich anregen. Sie sind klug, tüchtig und stark in der Entwicklung des Internets engagiert; sie leisten eindrucksvolle Arbeit und sind angenehme Kollegen und nette Kumpel, mit denen man auch mal ein Bier trinken kann, auch wenn man beruflich nicht unbedingt immer gleicher Meinung ist. 271