FEPCOS-J ist ein Programmiergerüst für Java, das die Entwicklung von zusammengesetzten, vernetzten Systemen, wie zum Beispiel Roboterschwärme, beschleunigt, indem es die benötigte Netzwerkprogrammierung automatisiert und die Spezifikation von Nebenläufigkeit vereinfacht. In diesem Artikel werden die Grundlagen und, mit einem einfachen Beispiel, die Anwendung von FEPCOS-J vorgestellt.
„Das Ganze ist mehr als die Summe seiner Teile.“ Aristoteles (384 v.Chr. – 322 v.Chr.). Dieses Prinzip ist auf ein vernetztes, technisches System, das aus Teilsystemen zusammengesetzt ist, übertragbar. Zum Beispiel ist bei einem Roboterschwarm der Schwarm das Ganze und die Roboter sind die Teile. Jeder Roboter kann für sich fahren. Der Schwarm kann mehr. Er kann zum Beispiel ausschwärmen, indem die einzelnen Roboter in verschiedene Richtungen fahren. Dies ist ein möglicher Mehrwert. Allerdings bekommt man diesen nicht geschenkt. Bei der Entwicklung eines vernetzten, technischen Systems, das ein aus Teilen zusammengesetztes Ganzes ist, ist mehr zu beachten als bei einem einzelnen System. Die Programmierung derartiger Systeme umfasst Netzwerkkommunikation und systemübergreifende Nebenläufigkeit, da die Teile kommunizieren und hochgradig parallel arbeiten. Dies ist ein Mehraufwand.
Im Rahmen des FEPCOS-Projekts[1] entsteht FEPCOS-J. FEPCOS steht für den englischen Ausdruck Framework-for-the-Programming-of-Composed-Systems. FEPCOS-J verringert den Mehraufwand, indem es die Programmierung der Netzwerkkommunikation automatisiert und die Spezifikation von Nebenläufigkeit vereinfacht. FEPCOS-J stellt anwendungsspezifische Java-Annotationen[2], den FEPCOS-J-Processor fjp und den FEPCOS-J-Exporter fjx zur Verfügung. Die Java-Annotationen erlauben es, derartige Systeme strukturiert zu programmieren. fjp wertet die Annotationen aus und generiert Code; fjx ermöglicht den Zugriff auf die Systeme innerhalb eines Netzwerks.
Grundlegende Begriffe
FEPCOS-J basiert auf dem in (Abb. 1) dargestellten Modell. Ein System besitzt mindestens eine Fähigkeit, sprich eine Aktivität, die es innerhalb einer endlichen Zeit ausführen kann. Eine Aktivität kann ein System manipulieren. Ein elementares System oder ein zusammengesetztes System ist ein System. Ein zusammengesetztes System ist ein Ganzes, das aus mindestens einem System bzw. Teil besteht. Ein System kann ein Teil von beliebig vielen Ganzen sein. Ein Systemnutzer greift über ein Internet-Socket innerhalb eines Netzwerks auf mindestens ein System zu. Ein Systemnutzer kann so das Ausführen einer Aktivität anstoßen und die resultierende Ausgabe empfangen. Auf ein System können beliebig viele Systemnutzer zugreifen. Eine Applikation oder ein zusammengesetztes System ist ein möglicher Systemnutzer.
Grundlegendes Modell von FEPCOS-J (Abb. 1)
Verwendungsmöglichkeiten von FEPCOS-J
Ein Anwender von FEPCOS-J, nachfolgend nur noch Anwender genannt, verwendet FEPCOS-J zum Programmieren einer Systemspezifikation, zum Erzeugen und Betreiben von Systemmodulen und zum Erzeugen einer Systemdokumentation (Abb. 2). Die Systemspezifikation spezifiziert ein System; die Systemmodule realisieren die Systemspezifikation. Das Systemexportmodul und das Systemimportmodul sind Systemmodule. Das Systemexportmodul exportiert die Fähigkeiten des Systems, das Systemimportmodul ermöglicht einen Netzwerkzugriff darauf. Die Systemdokumentation beschreibt das Systemimportmodul, sprich die Schnittstellen, mit denen ein Systemnutzer auf das Systemexportmodul und somit auf die Fähigkeiten des Systems zugreifen kann.
Verwendungsmöglichkeiten von FEPCOS-J (Abb. 2)
FEPCOS-J ermöglicht:
- die Spezifikation der Fähigkeiten eines Systems. Der Anwender legt diese fest. FEPCOS-J kümmert sich darum, dass auf diese innerhalb eines Netzwerks zugegriffen werden kann und automatisiert die dafür benötigte Netzwerkprogrammierung.
- die Spezifikation der Teile eines Systems. Der Anwender legt diese fest. FEPCOS-J kümmert sich um deren Verwaltung und sorgt dafür, dass die benötigten Netzwerkverbindungen automatisch auf- und abgebaut werden.
- einen blockierenden und einen nebenläufigen Zugriff auf die Fähigkeiten eines Systems. Der Anwender muss nur die Aktivität programmieren. FEPCOS-J stellt die entsprechenden Schnittstellen bereit und generiert die dazugehörigen JAVADOC-Kommentare.
FEPCOS-J benötigt zwingend:
- Java 11 oder höher, da es in Java-Version 11 geschrieben ist und auf dem Modulsystem aufbaut
- ein IPv4-Netzwerk, über das die Java VMs kommunizieren können
FEPCOS-J ist derzeit noch in Entwicklung und stellt noch keine Authentifizierungs- und Verschlüsselungsmechanismen bereit. Das bedeutet, dass jeder, der Zugriff auf das Netzwerk hat, über einen uneingeschränkten Zugriff auf die Fähigkeiten der sich darin befindenden Systeme verfügt. Es obliegt dem Anwender, das Netzwerk gegebenenfalls vor unbefugten Zugriffen abzusichern.
FEPCOS-J wird auf Debian-basierten Linux-Computern mit OpenJDK ab Version 11 entwickelt und getestet. Ob es mit anderen Betriebssystemen und Java-VMs funktioniert, muss sich zeigen.
Das Prinzip von FEPCOS-J
(Abb. 3) zeigt die prinzipielle Anwendung von FEPCOS-J, insbesondere des FEPCOS-J-Processors fjp und des FEPCOS-J-Exporters fjx.
Der Anwender
- programmiert die Systemspezifikation, die aus genau einer Systemdeklaration und mindestens einer Aktivitätsspezifikation besteht, innerhalb eines Systemspezifikationsmoduls unter Verwendung der Annotationen von FEPCOS-J. Er verwendet zum Programmieren der Systemdeklaration die in (Tab. 1) aufgelisteten Annotationen des Pakets fepcos.sy und zum Programmieren einer Aktivitätsspezifikation die in (Tab. 2) aufgelisteten Annotationen des Pakets fepcos.ay. Die beiden Pakete befinden sich im Java-Modul fepcos.annotations.
- erzeugt mit fjp aus dem Systemspezifikationsmodul das Systemexportmodul und das Systemimportmodul. Hierbei wertet fjp die Annotationen aus. Das Systemexportmodul enthält die vom Anwender programmierte Systemspezifikation und einen von fjp generierten Adapter. Das Systemimportmodul ist komplett von fjp generiert und enthält eine Systemschnittstelle, die S.b und S.c umfasst. S.b ermöglicht einen blockierenden Zugriff (BlockingAccess) auf das System und S.c erlaubt einen nebenläufigen Zugriff (ConcurrentAccess) darauf.
- exportiert das Systemexportmodul mit fjx.
- verwendet die Systemschnittstelle des Systemimportmoduls zum Programmieren eines Systemnutzers, welcher deshalb das Systemimportmodul benötigt.
Anwendung von FEPCOS-J (Abb. 3)
Für eine Systemdeklaration benötigte Annotationen aus dem Paket fepcos.sy (Tab. 1)
Für eine Aktivitätsspezifikation benötigte Annotationen aus dem Paket fepcos.ay (Tab. 2)
Der Anwender verteilt die Java-Module auf zwei verschiedene Ausführungsumgebungen auf denen FEPCOS-J installiert ist (Abb. 4a): Die eine enthält das Systemimportmodul und den Systemnutzer, die andere enthält das Systemexportmodul. Die beiden Ausführungsumgebungen sind über ein IPv4-Netzwerk verbunden und können so miteinander kommunizieren. Hierbei ist das Systemexportmodul über ein Internet-Socket eindeutig adressierbar. Ein Systemnutzer kann so über das Netzwerk auf die Systemspezifikation und so auf die vom Anwender programmierten Fähigkeiten des Systems zugreifen. Der Zugriff des Systemnutzers erfolgt hierbei über die Systemschnittstelle des Systemimportmoduls, über Module von FEPCOS-J, über das Internet-Socket, über Module von FEPCOS-J, über den Adapter des Systemexportmoduls auf die ebenfalls darin enthaltene Systemspezifikation. Ein Systemnutzer kann auf beliebig viele Systemimportmodule parallel zugreifen. Handelt es sich bei dem Systemnutzer um ein zusammengesetztes System, befindet sich in dessen Ausführungsumgebung ein Systemexportmodul SXM, und mindestens ein Systemimportmodul SIM (Abb. 4b). So kann ein Systemnutzer über das zu SXM gehörende Systemimportmodul SIM beim zusammengesetzten System das Ausführen einer Aktivität auslösen, worauf diese über SIM´ auf ein weiteres Systemexportmodul SXM´, sprich auf die Fähigkeiten eines weiteren Systems, zugreift. Die einzelnen Zugriffe erfolgen wie oben beschrieben.
Verteilung der Systemmodule und Zugriffsmöglichkeiten der Systemnutzer (Abb. 4)
Beispiel: Szenario – elementares System und Applikation
Nachfolgend wird die Anwendung von FEPCOS-J am Beispiel eines elementaren Systems hallo.system und einer Applikation hallo.app als Systemnutzer, beschrieben (Abb. 5). Der Anwender programmiert die Spezifikation von hallo.system mit dem Java-Modul hallo.system.spec unter Verwendung der Annotationen des Java-Moduls fepcos.annotations. Er erzeugt mit fjp, welcher die Annotationen auswertet, aus hallo.system.spec das Systemimportmodul hallo.system.imp und das Systemexportmodul hallo.system.exp. Er exportiert hallo.system.exp mit fjx. Schließlich programmiert und kompiliert er hallo.app unter Verwendung von hallo.system.imp.
Anwendung von FEPCOS-J am Beispiel eines elementaren Systems und einer Applikation (Abb. 5)
Der Anwender verteilt die Systemmodule und die Applikation, wie in (Abb. 6) dargestellt, auf zwei AU_system und AU_app genannte Ausführungsumgebungen, auf denen FEPCOS-J installiert ist. AU_system enthält hallo.system.exp. AU_app enthält hallo.app und hallo.system.imp. Es wird vorausgesetzt, dass die Ausführungsumgebungen über ein IPv4-Netzwerk miteinander kommunizieren können. In diesem Beispiel hat AU_app die IP-Adresse 10.0.0.1 und AU_system die IP-Adresse 10.0.0.2. hallo.system.exp stellt die Fähigkeiten des Systems über das Internet-Socket 10.0.0.2:22222 zur Verfügung, hallo.system.imp ermöglicht den Zugriff darauf. hallo.app kann so innerhalb des Netzwerks über die Systemschnittstelle von hallo.system.imp auf hallo.system.exp zugreifen – sprich Aktivitäten von hallo.system aufrufen und die Ergebnisse empfangen.
Beispielszenario: Zugriff Applikation auf elementares System (Abb. 6)
Nachfolgend wird die Umsetzung des Szenarios mit FEPCOS-J beschrieben. hallo.system entsteht auf AU_system im Projektverzeichnis «PVS». hallo.app entsteht auf AU_app im Projektverzeichnis «PVA» (Abb. 7).
Der Anwender
- erstellt «PVS» (Abb. 7a). Hierfür programmiert er die Java-Dateien, wie im nächsten Abschnitt beschrieben und speichert diese an der entsprechenden Stelle.
- erzeugt die Systemmodule und die Systemdokumentation (Abb. 7b). Hierzu ruft er im Verzeichnis «PVS» fjp auf. Der Processor erstellt einen build Ordner, den er für seine Arbeit benötigt und einen tgt Ordner, der die fertigen Systemmodule und die Systemdokumentation enthält (Abb. 7c). In hallo.system.exp.jar ist das Systemexportmodul, in hallo.system.imp.jar ist das Systemimportmodul und in hallo.system.imp-doc.zip ist die Systemdokumentation.
- startet den Export, indem er im Verzeichnis «PVS»fjx aufruft (Abb. 7d). Als Parameter übergibt er:
- das Verzeichnis tgt, welches das Exportmodul enthält,
- den Namen des Exportmoduls hallo.system.exp und
- den Port 22222 des Internet-Sockets.
Im Erfolgsfall wird eine entsprechende Meldung ausgegeben. Mit der Eingabe von exit kann das Exportieren beendet werden. Dies darf aber nicht vor Schritt 6 erfolgen, da sonst das Ausführen von hallo.app fehlschlägt.
- erstellt «PVA» (Abb. 7e). Hierfür kopiert er die im 2. Schritt erzeugte Datei hallo.system.imp.jar nach «PVA»/mlib, programmiert die Java-Dateien, wie im übernächsten Abschnitt beschrieben und speichert sie an der entsprechenden Stelle.
- kompiliert mit dem Befehl javac im Verzeichnis «PVA» die Applikation hallo.app (Abb. 7f). Hierbei enthält der Modulpfad MP: das Verzeichnis ${FEPCOS_HOME}/mlib, welches die Module von FEPCOS-J enthält, und das Verzeichnis mlib, das hallo.system.imp.jar enthält. Die Ausgabe erfolgt im Verzeichnis build.
- startet im Verzeichnis «PVA» mit dem Befehl java die Applikation (Abb. 7g). Der beim Kompilieren festgelegte Modulpfad MP ist hierbei um das Verzeichnis build erweitert, da sich darin die Applikation befindet. Im Erfolgsfall wird „Hallo JAVAPRO-Leser!“
Umsetzung des Beispiels (Abb. 7)
Programmierung – elementares System
Der nachfolgende Abschnitt beschreibt die Programmierung der Spezifikation von hallo.system. Der Anwender programmiert hierfür:
- den Moduldeskriptor module-info.java (Listing 1)
- die Systemdeklaration SYS mit der Datei SYS.java (Listing 2)
- die Aktivitätsspezifikation Grüße mit der Datei Grüße.java (Listing 3)
Er speichert die Dateien innerhalb des Verzeichnisses «PVS»/src wie in (Abb. 7a) dargestellt. Der Moduldeskriptor (Listing 1) legt fest, dass hallo.system im Modul hallo.system.spec spezifiziert wird (FEPCOS-J schreibt die Endung .spec vor) und dass hierfür die Annotationen aus dem Modul fepcos.annotations benötigt werden.
(Listing 1)
Die für SYS (Listing 2) benötigten Annotationen @SYDec und @Cap sind im Paket fepcos.sy und müssen importiert werden. @SYDec legt fest, dass SYS das System deklariert. @Cap legt fest, dass das System die Fähigkeit sagHallo (Name der Objektvariable) besitzt, die mit der Klasse Grüße spezifiziert wird.
(Listing 2)
Die für Grüße (Listing 3) benötigten Annotationen @AYSpec, @In, @Out und @Behavior sind im Paket fepcos.ay und müssen importiert werden. @AYSpec legt fest, dass Grüße eine Aktivität spezifiziert. @In legt fest, dass diese eine Eingabe benutzer vom Typ String erwartet. @Out legt fest, dass eine Ausgabe gruß vom Typ String erzeugt wird. @Behavior legt fest, dass die Methode go() das gewünschte Verhalten des Systems beschreibt. FEPCOS-J schreibt vor, dass der Name des Pakets, das die Systemspezifikation enthält, gleich dem Namen des Moduls ist. Folglich müssen SYS und Grüße im Paket hallo.system.spec sein.
(Listing 3)
(Abb. 8) zeigt die von fjp erzeugte Systemschnittstelle. S befindet sich im Systemimportmodul hallo.system.imp im Paket hallo.system und implementiert AutoCloseable. S hat einen Konstruktor, dem eine IP-Adresse ip und ein Port port übergeben werden muss. S.b ermöglicht den blockierenden Zugriff (BlockingAccess). S.c ermöglicht den nebenläufigen Zugriff (ConcurrentAccess). S.b und S.c enthalten, wie mit @Cap deklariert, die Methode sagHallo(…). Diese erwartet als Eingabe, wie mit @In deklariert, String benutzer und gibt als Ausgabe O_sagHallo bzw. java.util.concurrent.Future<O_sagHallo> zurück. O_sagHallo besitzt, wie mit @Out deklariert, eine Objektvariable für den Ausgabeparameter String gruß.
Die von fjp erzeugte Systemschnittstelle von hallo.system (Abb.8)
Programmierung – Applikation
Für die Applikation programmiert der Anwender:
- den Moduldeskriptor module-info.java (Listing 4)
- die Applikation Main mit der Datei Main.java (Listing 5).
Er speichert die Dateien innerhalb des Verzeichnisses «PVA»/src wie in (Abb. 7e) dargestellt.
Der Moduldeskriptor (Listing 4) legt fest, dass hallo.app das Systemimportmodul hallo.system.imp benötigt. Darin befindet sich die von fjp generierte Systemschnittstelle hallo.system.S.
(Listing 4)
Die Klasse Main (Listing 5) verwendet hallo.system.S. Da hallo.system.S das Interface AutoCloseable implementiert, wird sie innerhalb eines Try-With-Resource-Statements als Variable sy instanziiert, wobei dem Konstruktor die IP-Adresse 10.0.0.2 und der Port 22222 als Parameter übergeben werden. Die Applikation greift mit sy.b.sagHallo(…) blockierend über das Netzwerk auf die Fähigkeit sagHallo zu (siehe @Cap in (Listing 2)), wobei der Eingabeparameter benutzer (siehe @In in (Listing 3)) “JAVAPRO-Leser“ ist. Die Variable res enthält das Ergebnis des Aufrufs. Schließlich wird der in res.gruß gespeicherte Ausgabeparameter gruß (siehe @Out in (Listing 3)) ausgegeben.
(Listing 5)
Erweiterung – zusammengesetztes System
Nachfolgend wird das Beispielszenario um ein zusammengesetztes System hallo.proxy und eine Applikation hallo.test erweitert (Abb. 9). hallo.proxy verwendet hallo.system als Teil und kommt in eine weitere Ausführungsumgebung AU_proxy, welche die IP-Adresse 10.0.0.3 hat. hallo.test nutzt hallo.proxy und kommt in AU_app. hallo.test greift über das Internet-Socket 10.0.0.3:33333 auf die Fähigkeiten von hallo.proxy zu, welcher selbst über das Internet-Socket 10.0.0.2:22222 auf die Fähigkeiten von hallo.system zugreift.
Beispielszenario: Zugriff Applikation auf zusammengesetztes System, auf elementares System (Abb. 9)
Nachfolgend wird die Umsetzung der Erweiterung mit FEPCOS-J skizziert. Die Entwicklung von hallo.proxy erfolgt auf AU_proxy im Projektverzeichnis «PVP». Die Entwicklung von hallo.test erfolgt auf AU_app im Projektverzeichnis «PVT» (Abb. 10).
Der Anwender
- exportiert hallo.system (Abb. 7d).
- erstellt «PVP» (Abb. 10a). Hierfür kopiert er hallo.system.imp.jar nach «PVP»/mlib. Er programmiert den Moduldeskriptor module-info.java (Listing 6), die Systemdeklaration SYS2.java (Listing 7) und die Aktivitätsspezifikation Grüße2.java (Listing 8).
- Erzeugt die Systemmodule von hallo.proxy (Abb. 10b).
- exportiert hallo.proxy (Abb. 10c).
- erstellt «PVT» (Abb. 10d). Hierfür kopiert er das neu erstellte Systemimportmodul hallo.proxy.imp.jar nach «PVT»/mlib. Er programmiert den Moduldeskriptor module-info.java (Listing 9) und die Applikation Main2.java (Listing 10).
- kompiliert hallo.test (Abb. 10e).
- startet die Applikation (Abb. 10f). Im Erfolgsfall wird +++ Hallo JAVAPRO-Leser mit Verzögerungen ausgegeben. Aufgrund der systemübergreifenden Nebenläufigkeit kann die Anzahl der + Zeichen variieren.
Umsetzung der Erweiterung des Beispiels (Abb. 10)
Der Quellcode von hallo.proxy ist dem von hallo.system ähnlich. Von daher werden hier nur die wesentlichen Neuerungen erklärt. Der Moduldeskriptor (Listing 6) legt fest, dass hallo.system.imp benötigt wird.
(Listing 6)
Die Systemdeklaration (Listing 7) enthält die Annotation @Part. Diese legt fest, dass hallo.system ein Teil von hallo.proxy ist. Der Zugriff auf hallo.system erfolgt mit der Systemschnittstelle hallo.system.S über das Internet-Socket 10.0.0.2:22222. Der Name des Teils ist hs.
(Listing 7)
In der Aktivitätsspezifikation (Listing 8) erfolgt mit s.hs.b.sagHallo(…) ein blockierender Zugriff auf den Teil hs. Hierfür akzeptiert die Methode go(…) die Systemdeklaration als Parameter SYS2 s. Eventuell auftretende Exceptions werden mit throws Exception an FEPCOS-J weitergegeben. Zur Demonstration der Nebenläufigkeit verzögert Thread.sleep(…) das Ausführen.
(Listing 8)
(Abb. 11) zeigt die von fjp erzeugte Systemschnittstelle von hallo.proxy. S befindet sich im Modul hallo.proxy.imp im Paket hallo.proxy. Sowohl S.b, für den blockierenden Zugriff, als auch S.c, für den nebenläufigen Zugriff, enthält die Methode grüße(…) (siehe auch @Cap in (Listing 7)). Der Ausgabetyp der Methoden ist O_grüße, bzw. java.util.concurrent.Future<O_grüße>.
Die von fjp erzeugte Systemschnittstelle von hallo.proxy (Abb. 11)
Der Quellcode von hallo.test ist dem von hallo.app ähnlich. Auch hier werden nur die wesentlichen Neuerungen erklärt. Der Moduldeskriptor (Listing 9) legt fest, dass hallo.proxy.imp benötigt wird.
(Listing 9)
Die Applikation (Listing 10) erzeugt mit var pr = new hallo.proxy.S(…); eine Instanz der Systemschnittstelle von hallo.proxy. pr erlaubt einen Zugriff auf das Systemexportmodul hallo.proxy.exp, das am Internet-Socket 10.0.0.3:33333 exportiert wird. Main2 greift mit var res = pr.c.grüße(“JAVAPRO-Leser”); nebenläufig (deshalb pr.c statt pr.b) auf die Fähigkeiten von hallo.proxy zu, wobei die Variable res vom Typ java.util.concurrent.Future<O_grüße> ist. So kann Main2 in der while-Schleife + ausgeben, während hallo.proxy die Aktivität ausführt.
(Listing 10)
Fazit:
Mit FEPCOS-J sind Anwender in der Lage, Systeme zu programmieren, auf deren Fähigkeiten ein Systemnutzer über ein Netzwerk zugreifen kann. Der Anwender braucht sich um die Netzwerkprogrammierung nicht kümmern. FEPCOS-J generiert für ihn den Code und stellt ihm eine Schnittstelle für blockierende und nebenläufige Zugriffe zur Verfügung.
[2] Christian Heitzmann: „Deep-Dive into Annotations – Teil 2“, JAVAPRO 1-2019
Dipl.-Inf. Univ. Gerhard Fuchs hat 18 Jahre Erfahrung mit Java und entwickelt Programmierkonzepte und Netzwerkprotokolle. Er hat an der FAU Erlangen-Nürnberg u.a. NEtzwerkprogrammierung gelehrt und Programmierkonzepte für Robotersensornetze und die dafür benötigte Aufgabenverteilung erforscht. Derzeit arbeitet er am FEPCOS-Projekt.