VI. Informelle Beschreibung der Netzoperationen
Wir stellen hier Schilderungen der Abläufe in den drei Hauptphasen der Netznutzung vor: Verbindungsaufbau, Flusskontrolle und Verbindungsabbau.
A. Verbindungsaufbau
Um eine Verbindung zur Datenübertragung herzustellen, muss ein Paar von RFCs ausgetauscht werden. Ein RTS muss von der Empfangsseite zur Sendeseite gehen, und ein STR muss von der Sendeseite an die Empfangsseite ausgegeben werden. Außerdem muss die Empfangsseite in ihrem RTS eine Link-Nummer angeben. Diese RFCs (RFC ist ein Oberbegriff, der RTS und STR umfasst) können in beliebiger zeitlicher Reihenfolge ausgegeben werden. Es muss auch dafür gesorgt werden, anstehende Anrufe (d. h. RFCs, die vom Benutzerprogramm noch nicht behandelt wurden) in eine Warteschlange zu stellen. Wenn ein Benutzer also mit einer Verbindung fertig ist, kann er sich entscheiden, den nächsten anstehenden Anruf eines anderen Prozesses zu prüfen und die Verbindungsanforderung entweder anzunehmen oder abzulehnen. Ein Problem entsteht dadurch, dass der Benutzer sich möglicherweise nicht entscheidet, seine anstehenden Anrufe zu prüfen; dann belegen sie lediglich Warteschlangenplatz im NCP. Mehrere alternative Lösungen für dieses Problem werden später erwähnt.
Auf Grundlage der oben beschriebenen prototypischen Systemaufrufe sehen wir mindestens vier zeitliche Abfolgen, die zu einer erfolgreich geöffneten Verbindung führen:
- Der Benutzer kann ein LISTEN ausgeben und damit anzeigen, dass er bereit ist, eine Verbindung mit jedem in Betracht zu ziehen, der ihm einen RFC sendet. Trifft ein RFC ein, wird der Benutzer benachrichtigt. Der Benutzer entscheidet dann, ob er sich mit diesem Socket verbinden möchte, und gibt auf Grundlage dieser Entscheidung ein ACCEPT oder ein CLOSE aus. Ein CLOSE „lehnt“ die Verbindung „ab“, wie unter „Verbindungsabbau“ erörtert. Ein ACCEPT zeigt an, dass er zur Verbindung bereit ist; ein RFC wird ausgegeben, und die Verbindung ist vollständig geöffnet.
- Bei der Verarbeitung einer Benutzeranforderung LISTEN stellt der NCP fest, dass für diesen lokalen Socket ein anstehender Anruf vorliegt. Der Benutzer wird sofort benachrichtigt und kann wie oben mit ACCEPT oder CLOSE reagieren.
- Der Benutzer gibt ein CONNECT aus und gibt dabei einen bestimmten fremden Socket an, mit dem er sich verbinden möchte. Ein RFC wird ausgegeben. Nimmt der fremde Prozess die Anforderung an, antwortet er, indem er einen RFC zurücksendet. Wenn dieser bestätigende RFC empfangen wird, ist die Verbindung geöffnet.
- Bei einem CONNECT kann der NCP feststellen, dass ein anstehender Anruf vom angegebenen fremden Socket an den betreffenden lokalen Socket vorliegt. Ein bestätigender RFC wird ausgegeben, und die Verbindung ist geöffnet.
In allen obigen Fällen wird der Benutzer benachrichtigt, wenn die Verbindung geöffnet ist, doch der Datenfluss kann erst beginnen, wenn Pufferplatz zugewiesen und ein ALL-Befehl übertragen wurde.
Jedes dieser Verbindungsszenarien wird unterbrochen, wenn ein CLS eintrifft, wie unter „Verbindungsabbau“ erörtert.
1. Warteschlangen für anstehende Anrufe
Es ist unerlässlich, eine Form der Warteschlangenbildung für anstehende RFCs zu implementieren. Das lässt sich leicht an einer typischen LISTEN-CONNECT-Abfolge erkennen. Eine Seite gibt ein LISTEN aus, die andere ein CONNECT. Wird das LISTEN ausgegeben, bevor der RFC vom entfernten CONNECT eintrifft, ist alles in Ordnung. Aufgrund der asynchronen Natur des Netzes können wir jedoch nie garantieren, dass die Ereignisse in dieser Reihenfolge eintreten. Werden Anrufe nicht eingereiht und trifft der RFC ein, bevor das LISTEN ausgegeben wurde, wird er abgelehnt; trifft er später ein, wird er angenommen. Damit haben wir eine äußerst mehrdeutige Situation.
Sofern man nicht über unbegrenzten Warteschlangenplatz verfügt, ist ein Mechanismus wünschenswert, der die Warteschlangen von alten RFCs bereinigt, die der Benutzer nie geprüft hat. Eine naheliegende, aber informelle Methode besteht darin, den Zeitpunkt festzuhalten, zu dem jeder RFC in die Warteschlange eingetragen wird, und dann periodisch alle RFCs abzulehnen, die ein willkürliches Zeitlimit überschritten haben. Ein weiterer Gedanke, der wohl im Rahmen jedes Schemas berücksichtigt werden sollte, ist, dass der NCP ein CLS für alle offenen Verbindungen oder anstehenden Anrufe sendet, wenn sich ein Benutzer abmeldet oder abstürzt.
Das in dieser Beschreibung verwendete Schema mag auf den ersten Blick wenig intuitiv erscheinen; wir halten es jedoch für realistischer als andere Vorschläge. Im Wesentlichen nimmt der NCP bei einem CONNECT an, dass dieser Socket mit dem angegebenen fremden Socket kommunizieren möchte, und nur mit diesem. Er entfernt daher alle nicht passenden RFCs aus der Warteschlange anstehender Anrufe, indem er CLS zurücksendet. Ebenso werden alle nicht passenden RFCs abgelehnt, wenn sich die Verbindung im Zustand RFC-SEND befindet (ein CONNECT wurde ausgegeben). Wird eine Abfolge LISTEN-ACCEPT oder LISTEN-CLOSE ausgeführt, werden die übrigen anstehenden Anrufe nicht aus der Warteschlange entfernt, da der Benutzer diese Anforderungen möglicherweise später annehmen möchte.
Auch wenn die letztgenannte Methode willkürlich und/oder unnötig restriktiv erscheinen mag, haben wir noch kein Szenario ersonnen, das durch diese Methode verhindert würde, vorausgesetzt, wir haben es mit einem kompetenten Programmierer zu tun (d. h. einem, der sich vor Race Conditions und der asynchronen Natur des Netzes in Acht nimmt). Welches Schema oder welche Schemata ein bestimmter Standort wählt, hängt natürlich stark von der Implementierung ab; wir empfehlen, eine Warteschlangenbildung für RFCs für einen Zeitraum vorzusehen, der mindestens in der Größenordnung der Zeit liegt, während der sie im oben erwähnten CONNECT-Bereinigungsschema aufbewahrt werden.
B. Flusskontrolle
Sinnvolle Daten können auf einer Verbindung nur fließen, wenn sie vollständig geöffnet ist (d. h. zwei RFCs ausgetauscht wurden und der Abbau noch nicht begonnen hat). Wir nehmen an, dass die NCPs einen Puffer für eingehende Daten haben und dass es eine sinnvolle Größe gibt, die sie (pro Verbindung) bekanntgeben können und die angibt, welche Nachrichtengröße sie verarbeiten können. Wir nehmen ferner an, dass die Sendeseite ihre Übertragung gemäß den bekanntgegebenen Größen regelt.
Wird eine Verbindung geöffnet, wird eine Zelle (genannt 'Their Size') auf null gesetzt. Die Empfangsseite entscheidet, wie viel Platz sie zuweisen kann, und sendet eine ALL-Nachricht, die diesen Platz angibt. Die Sendeseite erhöht 'Their Size' um den zugewiesenen Platz und kann dann Nachrichten senden, deren Länge kleiner oder gleich 'Their Size' ist. Werden Nachrichten übertragen, wird die Länge der Nachricht von 'Their Size' abgezogen. Weist die Empfangsseite weiteren Pufferplatz zu (z. B. wenn der Benutzer eine Nachricht übernimmt und dadurch Systempufferplatz frei wird), wird die Anzahl der freigegebenen Bits per ALL-Nachricht an die Sendeseite gesendet.
Somit darf 'Their Size' nie negativ werden, und es kann keine Übertragung stattfinden, wenn 'Their Size' gleich null ist.
Man beachte, dass die in ALL-Nachrichten angegebenen Längen Inkremente sind und nicht die absolute Größe des Empfangspuffers. Dies ist durch die Vollduplex-Natur des Flusskontrollprotokolls bedingt. Das Längenfeld der ALL-Nachricht kann 32 Bit lang sein (Hinweis: Dies ist eine vorzeichenlose Ganzzahl) und bietet damit die Möglichkeit einer im Wesentlichen unendlichen „Bitsenke“, falls dies jemals gewünscht sein sollte.
C. Verbindungsabbau
So wie zum Öffnen einer Verbindung zwei RFCs erforderlich sind, sind zum Schließen einer Verbindung zwei CLS erforderlich. Der Verbindungsabbau erfolgt unter verschiedenen Umständen und dient mehreren Zwecken. Um die Analyse von Race Conditions zu vereinfachen, unterscheiden wir vier Fälle: Abbrechen, Ablehnen, Beenden durch den Empfänger, Beenden durch den Sender.
Ein Benutzer „bricht“ eine Verbindung „ab“ ("aborts"), wenn er ein CONNECT und dann ein CLOSE ausgibt, bevor das CONNECT bestätigt wurde. Typischerweise bricht ein Benutzer nach längerem Warten auf die Bestätigung ab; sein System kann auch für ihn abbrechen, wenn er abstürzt.
Ein Benutzer „lehnt“ eine Verbindung „ab“ ("refusing"), wenn er ein LISTEN ausgibt und, nachdem er über einen potenziellen Anrufer benachrichtigt wurde, ein CLOSE ausgibt. Alle Verbindungsanforderungen an einen Socket, der einen Anruf von einem bestimmten Socket erwartet, werden ebenfalls abgelehnt.
Nachdem eine Verbindung hergestellt ist, kann jede Seite sie beenden. Die erforderliche Ereignisfolge legt nahe, CLOSE-Versuche der Empfangsseite als „Anforderungen“ ("requests") zu betrachten, denen die Sendeseite stets so bald wie möglich nachkommt. Alle Daten, die noch nicht an den Benutzer weitergegeben wurden oder noch über das Netz unterwegs sind, werden verworfen. CLOSE-Anforderungen der Sendeseite wird nachgekommen, sobald die gesamte Datenübertragung abgeschlossen ist.
1. Abbrechen
Wir können drei Fälle unterscheiden:
- a) Im einfachsten Fall senden wir einen RFC, gefolgt später von einem CLS. Die andere Seite antwortet mit einem CLS, und der Verbindungsversuch endet.
- b) Der fremde Prozess kann die Verbindung annehmen, während der lokale Prozess sie gleichzeitig abbricht. In diesem Fall glaubt der fremde Prozess, dass der lokale Prozess eine offene Verbindung beendet.
- c) Der fremde Prozess kann die Verbindung ablehnen, während der lokale Prozess sie gleichzeitig abbricht. In diesem Fall glaubt der fremde Prozess, dass der lokale Prozess seine Ablehnung bestätigt.
2. Ablehnen
Nachdem ein RFC empfangen wurde, kann der lokale Host mit einem RFC oder einem CLS antworten, oder er antwortet nicht. (Der lokale Host hat möglicherweise bereits seinen eigenen RFC gesendet usw.) Sendet der lokale Host ein CLS, so sagt man, der lokale Host „lehne“ die Verbindungsanforderung „ab“ ("refusing").
Wir verlangen, dass zum Schließen einer Verbindung CLS-Befehle ausgetauscht werden; daher muss der lokale Host den Eintrag in der Rendezvous-Tabelle beibehalten, bis ein bestätigendes CLS zurückkommt.
3. Beenden durch den Sender
Wenn der Benutzer auf der Sendeseite einen Systemaufruf CLOSE ausgibt, muss sein NCP ihn sofort annehmen, darf aber keinen CLS-Befehl aussenden, bevor alle Daten in den lokalen Puffern an den fremden Host übergeben wurden. Daher muss vor dem Senden des CLS-Befehls sowohl auf 'buffer-empty' als auch auf 'RFNM-received' geprüft werden. Wie üblich muss das CLS bestätigt werden, bevor der Eintrag gelöscht werden darf.
4. Beenden durch den Empfänger
Wenn der Benutzer auf der Empfangsseite einen Systemaufruf CLOSE ausgibt, nimmt sein NCP ihn an und sendet den CLS-Befehl sofort. Es können jedoch noch Daten eintreffen, und diese Daten sollten verworfen werden. Die Sendeseite sollte beim Empfang des CLS den Datenfluss sofort beenden.