Version | Anpassungen |
---|---|
1.0 (2020-11-23) | Initiale Version |
2020-12-02 | Extension UrsAI2SharedTcpClient hinzugefügt |
1.1 (2020-12-02) | Bei einem Sendefehler wurde der Zwischenzustand 'Disconnecting' entfernt. |
1.2 (2021-01-09) | Write und Writeln haben nicht abgebrochen, wenn keine Verbindung besteht. |
Inhaltsverzeichnis
Verbindungsauf- und -abbau, Verbindungszustand
Shared Client (UrsAI2SharedTcpClient)
Das ZIP-Archiv UrsAI2TcpClient zum Download. Das Archiv enthält den Quellcode, das kompilierte Binary zum Upload in den App Inventor und eine Beispiel-Anwendung.
Die Extension UrsAI2TcpClient ermöglicht es, TCP-Verbindungen zu einem Server zu errichten und zu betreiben. Es können Textnachrichten per TCP versendet werden. Der Versand erfolgt Zeilenweise. Ein Zeile wird durch eine Zeilenende-Kennung (CR, LF, CRLF) abgeschlossen. Wird beim Empfang eine solche Kennung erkannt, werden die bis dahin empfangenen Zeichen per Ereignis an die Applikation gemeldet.
Die Methode Connect stellt eine Verbindung mit dem Server her, der über die Eigenschaften RemoteAddress und RemotePort festgelegt ist. ConnectTo verwendet diese Eigenschaften nicht enthält, sondern verwendet die als Parameter übergebenen Endpunktdaten.
In beiden Methoden wird zunächst geprüft, dass der Verbindungszustand Disconnected oder Aborted ist. Ist dies der Fall, geht die Extension in den Zustand Connecting über und das Ereignis ConnectionStateChanged wird ausgelöst. Intern wird nun versucht die TCP-Verbindung herzustellen und den Thread zu starten, der auf eingehende Daten lauscht. War der Verbindungsaufbau erfolgreich, geht die Extension in den Zustand Connected über andernfalls in den Zustand Aborted. Zum Schluss wird wieder das Ereignis ConnectionStateChanged ausgelöst.
Lässt der Zustand der Extension nicht zu, eine neue Verbindung aufzubauen, wird das Ereignis ErrorOccurred mit der Fehlernummer 4 ("Already connected.") ausgelöst.
Die Trennung einer Verbindung wird über die Methode Disconnect gestartet. Der Zustand der Extension muss Connected sein, ansonsten wird das Ereignis ErrorOccurred mit der Fehlernummer 3 ("Not connected to a server.") ausgelöst. Besteht eine Verbindung geht die Extension zunächst in den Zustand Disconnecting über. Danach wird die bestehende TCP-Verbindung abgebaut und die Extension geht in den Zustand Disconnected über. Zum Schluss wird das Ereignis ConnectionStateChanged ausgelöst.
Nach dem Abbruch einer Verbindung wird von der Anwendung i.d.R. versucht werden, eine neue Verbindung aufzubauen. Wenn dies sofort versucht wird und wiederholt scheitert ergibt sich eine hohe Netzwerkbelastung. Man wird den Versuch, die Verbindung wieder herzustellen, deshalb erst nach einer gewissen Zeit starten. Das Ereignis DelayedConnectionAborted kann hierfür genutzt werden. Es wird erst nach einer einstellbaren Zeit (s. ConnectionAbortedDelay) nach einem Verbindungsabbruch ausgelöst.
Code | Zustand | Bedeutung |
---|---|---|
0 | Disconnected | Der Client ist nicht mit einem Broker verbunden. |
1 | Connecting | Der Client versucht eine Verbindung zum Server herzustellen. |
2 | Connected | Es besteht eine Verbindung zu einem Server. |
3 | Disconnecting | Der Client baut die Verbindung zum Server ab. |
4 | Aborted | Die Verbindung konnte auf Grund eines Fehlers nicht hergestellt werden oder wurde unterbrochen. Fehlerursache kann über die Eigenschaft LastErrorCode und LastErrorMessage abgerufen werden. |
Zum Versenden stehen die Methoden Write und Writeln zur Verfügung. Die Zeichen-Codierung, d.h. die Umwandlung der Zeichenkette in eine Bytefolge, wird über die Eigenschaft Charset festgelegt.
Write schreibt den angegebenen Text in den Ausgabepuffer. Das sofortige Versenden der Nachricht ist nicht garantiert.
Writeln schreibt den angegebenen Text in den Ausgabepuffer und fügt eine Zeilenendekennung an. Diese ist abhängig von der Eigenschaft LineDelimiterCrLf. Bei true wird CRLF (0x0D+0x0A, für Server auf Windows-Basis) und bei false nur LF (0x0A) angehängt. Anschließend wird intern die Methode OutputStream.flush() aufgerufen, die einen Versand der bis dahin noch nicht gesendeten Daten erzwingt.
Bei beiden Methoden wird zunächst überprüft, ob der Zustand Connected ist. Ist dies nicht der Fall, wird die Anweisung ignoriert und das Ereignis ErrorOccured mit dem ErrorCode 3 ("Not connected to a server.") ausgelöst.
Tritt beim Versenden ein Fehler auf, z.B. weil die Verbindung abgebrochen ist, wird zunächst das Ereignis ErrorOccured mit dem ErrorCode 5 ("Connection fault.") ausgelöst. Danach wird die Verbindung mit der Ereignisfolge ConnectionStateChanged:Disconnecting und ConnectionStateChanged:Aborted abgebaut.
Das einlesen der Daten erfolgt in einer (Endlos-) Schleife in einem separatem Thread. Die eingehenden Bytes werden anhand der Eigenschaft Charset in Texte decodiert und gesammelt, bis eine Zeilenendekennung (CR, 0x0D, LF, 0x0A oder CRLF, 0x0D+0x0A).) empfangen wird. Die Eigenschaft LineDelimiterCrLf wird hier nicht ausgewertet. Wenn eine Zeilenendekennung empfangen wurde, werden die bis dahin gesammelten Zeichen ohne die Zeilenendekennung über das Ereignis MessageReceived an die App gemeldet.
Tritt in dem Thread ein Fehler auf, wird die Verbindung mit dem Ereignis ConnectionStateChanged:Aborted abgebaut.
In der Android-Umgebung müssen sämtliche Netzwerk-Funktionen in einem separatem Thread ausgeführt werden. Dies ist notwendig, damit die Benutzeroberfläche bei lange andauernden Operation, z.B. wegen schlechter Verbindungen, nicht einfriert. Das hat Auswirkungen auf das Auslösen von Ereignissen. Sie werden nicht direkt, sondern über ein Handler-Instanz ausgelöst. Der Umweg über Handler.post(...) verlagert die Ausführung aus dem separatem Thread in den GUI-Thread.
Das hat hat zur Folge, das der aktuelle AI2-Block zu Ende ausgeführt wird, bevor die Ereignisse ausgelöst werden. In der folgenden Konstellation
wird also zuerst die Prozedur DoSomething aufgerufen, bevor eines der beiden Ereignisse ausgelöst wird, das den Erfolg des Aufrufs von Connect zurück meldet. Die Prozeduren HandleErrorOccured bzw. HandleConnectionStateChanged werden also nach doSomething ausgeführt.
Das Analoge gilt für alle Ereignisse dieser Extension.
Code | Bedeutung | Text |
---|---|---|
0 | Kein Fehler | |
1 | Die angegebene Port-Nummer ist nicht gültig. | Port number is invalid. |
2 | Verbindungsaufbau nicht möglich. | Could not connect to server. |
3 | Nicht mit einem Server verbunden. | Not connected to a server. |
4 | Es besteht bereits eine Verbindung. | Already connected. |
5 | Verbindungsfehler | Connection fault. |
Version | Anpassungen |
---|---|
1.0 (2020-12-02) | Initiale Version |
1.1 (2021-01-09) | Write und Writeln haben nicht abgebrochen, wenn keine Verbindung besteht. |
Hinweis: Die Benutzung von UrsAI2SharedTcpClient ist im Companion nicht möglich. Companion stellt nicht alle Funktionalitäten zur Verfügung, die zum Betrieb dieser Extension notwendig sind.
Diese Variante der Extension (UrsAI2SharedTcpClient, im Download enthalten) benutzt eine statische (Java static) Verbindungskomponente, d.h. alle Instanzen der Extension in verschiedenen Screens einer App nutzen die selben Objekte. D.h. auch, es kann in der App nur eine einzige Verbindung geben. Die Verwendung von mehreren Instanzen auf dem selben Screen führt zu Fehlern beim Auslösen der Ereignisse.
Der Vorteil dieser Extension ist, dass sich nicht jeder Screen um den Verbindungsaufbau kümmern muss. Man kann z.B. in Screen1 eine Verbindung aufbauen und diese in weiteren Screens nutzen. Bei Verwendung der oben beschrieben Extension UrsAI2TcpClient ist das nicht möglich. Wenn ein weiterer Screen mit dem gleichen Endpunkt (gleiche IP und gleicher Port) kommunizieren will, müsste, bevor der zweite Screen geöffnet wird, die Verbindung im ersten Screen getrennt werden. Im zweiten Screen muss sie dann erneut geöffnet werden. Vor dem Schließen des zweiten Screen muss die Verbindung wieder getrennt und bei der Rückkehr in den ersten Screen wieder geöffnet werden. Dies alles ist bei der Verwendung des UrsAI2SharedTcpClient nicht notwendig. Die Verbindung wird dem zweiten Screen in dem Zustand zur Verfügung gestellt, wie sie im ersten Screen verlassen wurde und umgekehrt.
Wenn der erste Screen eine Methode zur Wiederherstellung einer abgebrochenen Verbindung implementiert (siehe das im Download enthaltenen Beispiel), ist keine Wiederholung dieser Funktionalität im zweiten Screen notwendig. Die Funktion im ersten Screen ist auch nach dem Öffnen eine zweiten Screens aktiv (leider jedoch nicht umgekehrt).
Es stehen keine Designer-Eigenschaften zur Verfügung. Designer-Eigenschaften sind nicht sinnvoll. Eine zweite Instanz der Extension (in einem zweiten Screen) würde die eingestellten Werte der ersten Instanz überschreiben. Man müsste also sicher stellen, dass in allen Screens zu jeder zeit die gleichen Werte im Designer eingetragen wären. Da i.d.R. nur der die IP-Adresse (oder der Hostname) des Servers sowie der zu verwendende Port eingestellt werden müssen und das auch nur im ersten Screen, ist der Zusatzaufwand gering.
Die Ereignisse ConnectionStateChanged und DelayedConnectionAborted werden an alle Instanzen der Extension auf allen Screens weiter geleitet. MessageReceived und ErrorOccured werden nur von der Instanz auf dem aktuell sichtbaren Screen ausgelöst.
![]() |
Mit dieser kleinen App kann man sich mit einem Server verbinden und Textnachrichten versenden und empfangen. |
Hinweis: Im Designer müssen noch passende Endpunktdaten eingetragen werden.
![]() |
Mit dieser kleinen App kann man sich mit einem Server verbinden und Textnachrichten versenden und empfangen. Sie demonstriert außerdem die Verwendung der Extension auf mehreren Screens. |
Dies ist ein Screenshot vom zweiten Screen. Man sieht, der Screen wurde mit dem Verbindungszustand Connected eröffnet (erste Zeile des Logs). Beim Schreiben war die Verbindung jedoch gestört (Server war ausgeschaltet). Die Extension meldet dies über das Ereignis ErrorOccured (Zeilen 2 und 3 des Logs). Daraufhin wird die Verbindung abgebaut (aufgeräumt). Dies wird über das Ereignis ConnectionStateChanged gemeldet (Zeilen 4 und 5 des Logs).
Der erste Screen implementiert zum Ereignis DelayedConnectionAborted eine Funktion zur Wiederherstellung der Verbindung. Nach kurzer Zeit erscheint deshalb ohne weiteres Zutun die Meldung "State: Connecting" (Zeile 6). Der Server war wieder eingeschaltet, weshalb die Verbindung wieder hergestellt werden konnte (Zeile 7).
Zum Ausprobieren des Beispiels muss in Screen1 beim Ereignis Initialize noch die IP-Adresse bzw. der Hostname des Servers und der zu verwendende Port eingetragen werden.
Für die Erstellung eigener Extensions habe ich einige Tipps zusammengestellt: AI2 FAQ: Extensions entwickeln.
Die Diagramme wurden mit PlantUML erstellt.