Ein OO-Framework für Netzwerk-Protokoll Software

Hermann Hüni
GLUE Software Engineering AG
Beat Keller
Ascom Tech AG

Zusammenfassung

Endgeräte und Softwareprozesse kommunizieren miteinander über mehrschichtige Protokolle durch den Austausch von Meldungen. Die Realisierung solcher Protokolle sowie deren Integration weisen viele Gemeinsamkeiten, aber auch manchen Unterschied auf.

ATM, die Schlüsseltechnologie für zukünftige Telekommunikationsnetze, benötigt effiziente Signalisierungsprotokolle. Dafür wurde eine objektorientierte Software entworfen und in C++ implementiert. Durch mehrere Iterationen ist daraus ein Framework für Protokollsoftware entstanden, das eine hohe Wiederverwendung von gemeinsamen und eine leichte Integration variabler Komponenten ermöglicht. Wir stellen die Architektur dieses Frameworks vor, zeigen auf, welche Entwurfsmuster angewendet wurden, und erläutern die Erfahrungen bei der Entwicklung und der Wiederverwendung.

1. Ausgangslage

Die vielgepriesene Informationsgesellschaft basiert auf einer ausgebauten, schnellen, zuverlässigen und preiswerten Telekommunikationsinfrastruktur. Diese Infrastruktur wird sehr oft unter den Begriffen: Datenautobahn, Superhighway oder Infobahn vermarktet. Um die Daten der zukünftigen Multimedia Anwendungen optimal und effizient transportieren zu können, wurde in der Telekommunikation eine neue Technologie entwickelt, welche unter dem Begriff ATM (Asynchroner Transfer Mode) bekannt ist. In ATM werden die Informationen zerstückelt und in kompakte Pakete (genannt Zellen) mit konstanter Länge gepackt. Im Kopffeld der Zelle (Header) werden Informationen mitgeführt, welche den schnellen Transport im Netzwerk über virtuelle Verbindungen erlauben.

Jede ATM-Zelle besteht aus einem Kopffeld von fünf Byte und einem Informationsfeld von 48 Byte. Diese Zellen werden im Netz mit einer Übertragungsgeschwindigkeit von 155 Mbit/s transportiert. Da netzintern ein einfaches Verfahren verwendet wird, entsteht eine Entkoppelung des Netzes und der Dienste. Das führt zu einer schnellen ,Datenautobahn`. Zwischen den Endgeräten entsteht beim Verbindungsaufbau eine virtuelle Verbindung. Eine virtuelle Verbindung wird durch einen virtuellen Kanal (VC) und den dazugehörigen virtuellen Pfad (VP) definiert. Die virtuelle Verbindung ermöglicht den Transport unterschiedlicher Dienste. Die Asynchronität wird dadurch erreicht, dass der zeitliche Abstand von Nutzzellen unterschiedlich gehalten wird.

2. Lösungsansätze zur Realisierung von Protokollen

Der Aufbau, Abbau und Unterhalt dieser virtuellen Verbindungen geschieht mittels Signalisierungsprotokollen, welche in internationalen Standardisierungsgremien festgelegt werden. In jedem Endgerät und in allen Elementen der Netzinfrastruktur sind die Protokolle integriert. Diese mehrschichtigen Protokolle bestehen bei der Signalisierung für ATM Netze aus dem Schicht 2 (gemäss OSI) Protokoll SAAL (Signalling ATM Adaption Layer) und dem Schicht 3 Protokoll Q.2931, sowie dazugehörenden Software-Komponenten für die Adressierung, Numerierung, Leitweglenkung und Verbindungssteuerung.

Die Entwicklung dieser Protokolle ist aufwendig und die Spezifikationen sind oft noch instabil. Die Instabilität kann nach der erstmaligen Entwicklung zu grösseren Änderungen führen. Die neuen Protokolle (z.B. Q.2931) werden zeitlich gestaffelt spezifiziert. Durch ITU (International Telecommunication Union) sind drei aufeinanderfolgende Schritte (Capability Set CS 1, CS 2 & CS 3) definiert, wobei jeder eine Funktionalitätserweierung spezifiziert. Bei der Entwicklung muss daher der Änderbarkeit und Erweiterbarkeit grosse Beachtung geschenkt werden.

Bis ca. 1993 kamen bei der Entwicklung von Protokollen zwei Lösungsansätze zur Anwendung. Beim Ersten, dem sogenannten `klassischen` Software-Ansatz, wird mittels einer Programmiersprache (C, Modula, ADA, ...) die Protokollspezifikation durch einen Programmierer in eine Anzahl verschachtelter `switch/case`-Anweisungen umgesetzt. Die Verschachtelung ist häufig sehr unübersichtlich und Änderungen sind schwierig.

Beim zweiten Ansatz wird das Protokoll mittels eines Codegenerators automatisch erzeugt. Nach erfolgter Spezifikationen, (z.B. mittels dem Werkzeug SDT) wird ein Lösungsansatz auf einem Computer analysiert und simuliert. Bei fehlerfreier Simulation wird der Programmcode automatisch generiert. Änderungen und Erweiterungen sind nur auf Spezifikationsniveau einfach praktikabel. Die Integration mehrerer Protokollschichten erfordert jedoch meist ein nachträgliches Eingreifen in den erzeugten Programmcode.

Die Nachteile dieser beiden Ansätze haben uns bewogen, nach neuen Lösungen zu suchen.

3. Conduits+: Ein objektorientiertes Framework

'Conduits+' ist ein objektorientiertes Framework, realisiert in C++, zur Konstruktion von mehrschichtigen Kommunikationsprotokollen. Es besteht aus zwei Sorten wiederverwendbarer Komponenten, 'Visitors' und `Conduits' und erfordert zwei Arten anwendungsspezifischer Klassen, 'Messenger' und 'State'.

Die Kopplung zwischen den wiederverwendbaren Komponenten und den anwendungsspezifischen Klassen ist bewusst sehr klein gehalten. Dagegen sind die Klassen Messenger und State immer anwendungsspezifisch und eng miteinander gekoppelt. Weil meistens eine Spezifikation des Protokolls als Standarddokument bereits vorliegt, sollten diese Teile leicht (automatisch) zu generieren sein. Es ist ein wichtiges Ziel dieses Frameworks, die wiederverwendbaren Komponenten Visitor, Conduit und deren Subklassen - möglichst ohne Quellcodeänderung - in unterschiedlichen Protokollschichten und in verschiedenen, möglicherweise sich dynamisch ändernden Conduitgraphen einsetzen zu können.

Ein Framework ist mehr als eine Klassenbibliothek. Ein Framework codiert die Architektur für eine Familie von Anwendungen. Dies geschieht, indem die zugelassenen Strukturen sowie der Kontrollfluss der Anwendung bereits durch das Framework komponentenübergreifend definiert wird.

Abbildung1:
Aufteilung eines mehrschichtigen Protokolls in Multiplex- und Protokollfunktionalität.

Innerhalb von Protokollschichten finden sich häufig zwei Grundbausteine: Die eigentliche Protokoll- sowie eine 1:N Multiplexfunktionalität. Damit sich aus diesen Grundbausteinen beliebige Graphen (siehe Abbildung 1) zusammenbauen lassen, muss die dazwischenliegende Schnittstelle aller Bausteinsorten kompatibel sein. Dies erreichen wir, indem wir für jeden dieser Bausteine eine Subklasse, abgeleitet von einer abstrakten Basisklasse `Conduit' bilden.

3.1. Die wiederverwendbaren Komponenten

Ein Conduit ist eine Komponente mit zwei Seiten, A und B. Ein Conduit transportiert Visitors bidirektional zwischen seinen Seiten A und B. Jede Seite eines Conduits kann mit einer Seite eines Nachbar-Conduits bidirektional verbunden werden. Conduits können mit ihren Nachbar-Conduits Visitors in beiden Richtungen austauschen. Das Conduit Konzept wird durch eine C++ Basisklasse mit entsprechendem Interface realisiert. Abbildung 2 zeigt die spezialisierten, wiederverwendbaren Framworkkomponenten, die die Conduit- oder Visitor-Schnittstelle erben.

Abbildung 2:
Die Klassenhierarchie der wiederverwendbaren Komponenten

Ein Visitor ist ein Objekt, das eine Kette von Conduits traversiert. 'MsgTransporter' ist eine Subklasse von Visitor, die eine Meldung (einen 'Messenger') mitträgt. 'Installer', 'Disconnecter' und 'CrossConnecter' sind weitere Visitors, deren Aufgabe darin besteht, neue Verbindungen zwischen Conduits zu erstellen oder bestehende zu unterbrechen.

Die vier Subklassen von Conduit realisieren folgende Funktionalität:

  1. Ein Kommunikationsprotokoll lässt sich durch einen endlichen Automaten (engl. Finite State Machine, FSM) beschreiben. Die Subklasse 'Protocol' realisiert solche FSM's indem sie ein Objekt der Klasse State (oder einer Subklasse davon) referenziert.
  2. Ein Stapel von Conduits muss an beiden Extremitäten über einen Abschluss der Conduit-Schnittstelle mit anderer Software interagieren. Die dazu dienende Subklasse 'Adapter' kann nur auf ihrer Seite A mit einem Nachbar-Conduit verbunden werden. Auf Seite B wird immer eine Anbindung an ein spezifisches API (Application Programming Interface) realisiert.
  3. Vielfach sollen mehrere virtuelle Verbindungen über dasselbe Medium gleichzeitig betrieben werden. Dazu ist es notwendig, Visitors von mehreren Nachbar-Conduits in einer Richtung zu multiplexieren und in der Gegenrichtung zu demultiplexieren. Diese Funktionalität wird durch die Subklasse 'Mux' realisiert. Ein Mux ist auf seiner Seite A immer mit einem einzigen Nachbar-Conduit verbunden, kann auf der Seite B jedoch auch mit mehreren Nachbar-Conduits bidirektional verbunden werden. Kann ein Visitor nicht demultiplexiert werden, weil noch kein zugehöriges Nachbar-Conduit auf Seite B bekannt ist, gelangt er über den immer besetzten 'Notausgang' an ein Nachbar-Conduit.
  4. Es soll möglich sein, dynamisch neue virtuelle Verbindungen aufzubauen. Dazu müssen neue Protokollinstanzen erzeugt und auf der Seite B eines Mux installiert werden. Diese Aufgabe wird durch die Subklasse 'ConduitFactory' wahrgenommen.
    Eine Installation wird durch einen MsgTransporter (der den Verbindungsaufbau signalisiert) ausgelöst, für den beim Demultiplexieren im Mux noch kein Nachbar-Conduit auf Seite B verfügbar ist. Der 'Notausgang' eines Mux ist daher meistens mit einer ConduitFactory verbunden.

    Ein Visitor, der via Notausgang zu einer ConduitFactory gelangt, veranlasst diese ConduitFactory, eine neue Instanz eines Protocol-Conduits zu erzeugen, zu konfigurieren und auf Seite B des Mux zu  installieren (siehe Abbildung 3). Dadurch wird erreicht, dass alle folgenden MsgTransporter für dieselbe virtuelle Verbindung direkt an das neu installierte Protokoll-Objekt gelangen.

Abbildung 3:
Beispiel einer Protokollschicht, die sich aus mehreren Conduit-Objekten zusammensetzt

3.2. Die anwendungsspezifischen Klassen

Mit Hilfe der wiederverwendbaren Komponenten möchte man in der Lage sein, beliebige, mehrschichtige Kommunikationsprotokolle (TCP/IP, ISDN, ATM, ...) zu bauen. Im Fall von ATM Signalisierprotokollen ist es zudem entscheidend, der Evolution der Spezifikationen mit einer flexiblen und leicht erweiterbaren Realisierung zu begegnen. Das bedeutet, dass alle protokollspezifischen Informationen und Verhalten in separate Klassen ausgelagert werden müssen. Die wiederverwendbaren Komponenten lassen sich somit durch die vom Anwender realisierten spezifischen Klassen Messenger und State entweder zur Compilationszeit oder sogar zur Laufzeit konfigurieren. Abbildung 4 zeigt die partielle Klassenhierarchie der ATM-spezifischen Klassen.

Abbildung 4:
Die Klassenhierarchie der spezifischen Klassen
für ATM

Messenger
ist eine Basisklasse von der für jeden Meldungstyp eine Subklasse realisiert wird. Objekte einer solchen Subklasse werden durch einen MsgTransporter (ein Visitor) durch den Graph von Conduits geführt. Weil Meldungen meistens als Sequenzen von Bytes eintreffen, muss der Adapter daraus zuerst Objekte der richtigen Messenger-Subklasse fabrizieren.
State
ist eine Basisklasse, die für jede Subklasse von Messenger eine gleichnamige Methode anbietet, um die entsprechende Transition der FSM zu realisieren. Für jeden Zustand der FSM wird eine eigene State-Subklasse realisiert. Jede Transition kann zu einem Wechsel des aktuellen Zustands führen, indem am Ende der aktivierten Methode die Referenz auf das State-Objekt in der Komponente Protocol durch eine Referenz auf eine anderes State-Objekt ersetzt wird.

Abbildung 5 zeigt einen Ausschnitt aus dem Klassendiagramm (nach OMT) des Conduits+ Frameworks. Die 'uses'-Beziehung  zwischen Conduit und Visitor besteht eigentlich vielmehr zwischen den Subklassen von Conduit und Visitor. In umgekehrter Richtung benutzen Subklassen von Visitor ebenfalls Subklassen von Conduit.

Abbildung 5:
Ausschnitt aus dem Klassendiagramm des Conduits+ Frameworks nach OMT

3.3 Interaktionen der Frameworkkomponenten zur Laufzeit

Um das Zusammenwirken der Komponenten eines Frameworks zu verstehen sowie die potentiellen Variationen durch dynamische Bindung besser zu erkennen, ist es meist erforderlich, die Interaktionen zwischen den Objekten zur Laufzeit aufzuzeigen. Dies kann jedoch nur exemplarisch, an einigen wenigen interessanten Szenarien gemacht werden.

Nehmen wir also an, dass in einem Conduit-Graphen wie in Abbildung 3 gezeigt, ein Protocol-Objekt bereits installiert worden ist und sich im Zustand 'Connected' befindet. Nun empfängt der Adapter eine 'Release'-Meldung vom Netzwerk her. Die dabei involvierten Objekte sowie deren konstant  (F,G) und zeitweise (P) untereinander bestehenden Referenzen zeigt Abbildung 6.

Abbildung 6:
Objektdiagramm eines  MsgTransporter/ReleaseMsg der via Mux zu einem Protocol gelangt

Das dazugehörende Interaktionsdiagramm ist in Abbildung 7 dargestellt. Der vom Adapter erzeugte MsgTransporter trägt eine ReleaseMsg mit. Der MsgTransporter gelangt zuerst zum Mux, wo auf Grund einer Protokoll- oder Session-Id des ReleaseMsg entschieden wird, zu welchem Protocol-Objekt er demultiplexiert (Request 2.2) wird.

Beim Protocol-Objekt angelangt, holt sich der MsgTransporter (Request 3.1) den aktuellen State, den ConnectedState, vom Protocol-Objekt und übergibt diesen an den ReleaseMsg, der die der Transition entsprechende Methode 'release' (Request 3.2.1) des ConnectedState-Objekts aktiviert. Die Transition verursacht schliesslich einen Zustandswechsel, indem das bisherige ConnectedState-Objekt im Protocol-Objekt durch das ReleasingState-Objekt ersetzt wird.

Um jedem Conduit die grösstmögliche Autonomie zu gewähren, wird der Kontrollfluss jeweils in der Methode 'accept' unterbrochen. Ein einfacher Scheduler sorgt für die Aktivierung  derjenigen Conduits, die einen MsgTransporter zu verarbeiten haben.

Abbildung 7:
Interaktionsdiagramm eines MsgTransporters/ReleaseMsg  der via  Mux zu einem Protocol gelangt

4. Entwurfsmuster

Viele Studien zeigen, dass es, ohne sehr viel Erfahrung auf einem Gebiet, praktisch unmöglich ist, ein gutes Framework zu bauen. Ein wiederverwendbares Framework kristallisiert sich erst nach mehreren Entwicklungszyklen heraus. Das bedeutet, dass meistens zuerst drei bis fünf Anwendungen damit realisiert werden müssen, bevor ein Framework wirklich einen hohen Nutzen durch Wiederverwendung erbringen kann.

Wesentlich für die Entwicklung solcher Framework-Software ist, potentielle Variationen im voraus zu erkennen und den Entwurf an diesen Stellen flexibel zu gestalten. Dazu existiert heute ein Katalog von 'Design Patterns' (Gamma 1995), in welchem für viele Entwurfssituation wertvolle Hinweise zur Gestaltung flexibler, objektorientierter Software festgehalten sind. Solche Entwurfsmuster beschreiben eine Lösung zu einem Problem in einer bestimmten Situation.

Conduits+ verwendet viele der dort vorgestellten Entwurfsideen:

5. Erfahrungen bei der Entwicklung und beim Einsatz des Frameworks

Die Entwicklung des Frameworks wurde im Rahmen eines OO Pilotprojekt Mitte 1993 gestartet. Die Zielsetzung des OO Pilotprojektes war die Untersuchung, ob objektorientierte Analyse, Design und Programmierung geeigneter seien, als die vorgängig aufgeführten Methoden. Speziell wurde die Eignung zur Implementierung von Zustandsmaschinen untersucht, welche die Basis von Protokollen sind. In einem ersten Schritt wurde eine objektorientierte Software des Signalisierungsprotokoll Q.2931 implementiert. Eine lauffähige Version war innerhalb von einigen Monaten demonstrationsbereit und wurde durch ein Team von 3 Personen erstellt. Darauf basierend wurde ein OO-Framework entwickelt.

In einem weiteren Schritt wurde, basierend auf dem OO-Framework, der SAAL entwickelt. Diese Arbeit wurde durch ein zweites Projektteam durchgeführt, was eine gewisse Einarbeitung in das Framework bedingte. Andererseits wurde durch dessen Rückfragen, die Qualität des Framework erhöht. Einige Vorteile durch die Wiederverwendung des OO-Frameworks seien hier aufgeführt:

Natürlich ergeben sich beim Einsatz eines OO-Frameworks auch gewisse Nachteile. Speziell in dem beschriebenen Fall, da die Entwicklung des OO-Frameworks noch nicht abgeschlossen war, als das zweite Projektteam seine Arbeit startete. Im folgenden sind einige Nachteile durch die Wiederverwendung des Frameworks dargestellt:

Die positiven Punkte überwiegen, wobei je nach Einsatzort der Applikation die Codegrösse sich nachteilig auswirken kann. Speziell im Bereich `Consumer Electronic`, in welcher die Codegrösse kritisch ist, muss genau überprüft werden, ob sich ein Einsatz lohnt. Über die Effizienz der Applikation basierend auf dem OO-Framework können zur Zeit noch keine publizierbaren Angaben gemacht werden.

Im Rahmen dieses Artikels konnte leider nicht sehr tief auf die recht komplexe Materie des objektorientierten Entwurfs von Kommunikationssoftware eingegangen werden. Die Architektur von Conduits+ sowie die angewendeten Design Patterns wurden in einem Beitrag zur Konferenz OOPSLA-95 unter dem Titel A Framework  for Network Protocol Software publiziert.

Literaturverzeichnis

Die Autoren

Beat Keller, <keller_b@tech.ascom.ch>, ist Leiter der Kompetenzgruppe Signalling Software bei der Firma Ascom Tech AG, <http://www.tech.ascom.ch/CP/KSK.html>, in Bern. Die Kompetenzgruppe gehört zur 'Strategic Technology Unit' (STU), ist massgebend für die Entwicklung von ATM Protokollen verantwortlich und beteiligt sich aktiv in Europäischen Forschungsprojekten (RACE/ACTS).

Hermann Hüni, <hueni@glue.ch>, ist Mitbegründer und Geschäftsleiter der Firma GLUE Software Engineering AG, <http://www.glue.ch/>, in Bern, welche seit 1992 im Bereich OOD und C++ Coaching, Schulung, Beratung  und SW Entwicklung anbietet. Seit 1986 beschäftigt er sich intensiv mit objektorientierter Technologie und erhielt eine Auszeichnung für seine Arbeiten über Delegation. Er ist Autor von Publikationen an der OOPSLA 1992 und 1995.