Englisch version   English version


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.

In­halts­ver­zeich­nis

Download

Verwendung

Verbindungsauf- und -abbau, Verbindungszustand

Daten versenden

Daten einlesen

Ereignisbehandlung

Referenz

Eigenschaften

Remote-Endpoint

Zeitangaben

Verbindungszustand

Zeichensatz

Fehlerbehandlung

Methoden

Ereignisse

 

Shared Client (UrsAI2SharedTcpClient)

 

Beispiele

URS TCP Client Test

URS Shared TCP Client Test

Werkzeuge

Download

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.

Verwendung

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.

Verbindungsauf- und -abbau, Verbindungszustand

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.

Daten versenden

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.

Daten einlesen

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.

Ereignisbehandlung

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.

Referenz

Eigenschaften

Remote-Endpoint

RemoteAddress
Hostname oder IP-Adresse des Servers.
RemotePort
Port für die TCP-Verbindung.
Hinweis: Die Vorbelegung ist leer. Wenn kein Wert eingetragen ist meldet AI2: "is not a legal integer".

Zeitangaben

ConnectionTimeout
Zeit in Millisekunden, die für den Verbindungsaufbau zur Verfügung steht. Der Standardwert ist 1000 ms.
IoTimeout
Zeit in Millisekunden, die für Ein- und Ausgabeoperationen zur Verfügung steht. Der Standardwert ist 1000 ms.
ConnectionAbortedDelay
Zeitverzug in Millisekunden zwischen dem Ereignis ConnectionStateChanged:Aborted und DelayedConnectionAborted. Der Standardwert ist 3000 ms.

Verbindungszustand

ConnectionState
Aktueller Verbindungszustand.
  0: Disconnected (getrennt)
  1: Connecting (Verbindungsaufbau)
  2: Connected (verbunden)
  3: Disconnecting (Verbindungsabbau)
  4: Aborted (Verbindungsabbruch)
ConnectionStateName
Bezeichnung des aktuellen Verbindungszustands.
IsConnected
Liefert true, wenn eine TCP-Verbindung zum Server besteht (ConnectionState == Connected).
IsDisconnected
Liefert true, wenn keine TCP-Verbindung zum Server besteht (ConnectionState == Disconnected oder ConnectionState == Aborted).
StateDisconnected
Konstante für den den Zustand Disconnected.
StateConnecting
Konstante für den den Zustand Connecting.
StateConnected
Konstante für den den Zustand Connected.
StateDisconnecting
Konstante für den den Zustand Disconnecting.
StateAborted
Konstante für den den Zustand Aborted.

Zeichensatz

Charset
Aktuell eingestellter Zeichensatz. Möglich sind "US-ASCII", "UTF-8", "ISO-8859-1" (ISO-Latin-1), "UTF-16BE", "UTF-16BL" und "UTF-16".
Diese Angabe wird nur beim Verbindungsaufbau ausgewertet und bleibt während der gesamten Dauer der Verbindung bestehen.
Hinweis: Die Zeichensatz wird über die Java-Methode Charset.forName ermittelt, die stark von den Implementierung im zugrunde liegenden Betriebssystem abhängt. Es kann also sein, dass andere Zeichensätze gültig sind. Es werden alle Zeichensätze akzeptiert, für die Charset.forName gültige Ergebnisse liefert.
Charset_USASCII
Konstante für den den Zeichensatz US-ASCII.
Charset_UTF8
Konstante für den den Zeichensatz UTF-8.
Charset_ISO_Latin_1
Konstante für den den Zeichensatz ISO-8859-1 (ISO-Latin-1).
Charset_UTF16BE
Konstante für den den Zeichensatz UTF-16BE.
Charset_UTF16BL
Konstante für den den Zeichensatz UTF-16BL.
Charset_UTF16
Konstante für den den Zeichensatz UTF-16.

Fehlerbehandlung

LastAction
Bezeichnung der zuletzt ausgeführten Aktion, z.B. "Connect".
LastErrorCode
Fehler-Code des zuletzt aufgetretenen Fehlers (s.u.).
LastErrorMessage
Text zum Code, z.B. "Invalid State."
LastExecptionCause
Text der Java Exception, die den Fehler ausgelöst hat.
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.

Methoden

Connect()
Versucht eine TCP-Verbindung mit den bei den Eigenschaften RemoteAddress und RemotePort angegebenen Daten herzustellen.
ConnectTo(RemoteAddresse, RemotePort)
Versucht eine TCP-Verbindung mit den bei den Parametern RemoteAddress und RemotePort angegebenen Daten herzustellen.
Disconnect()
Baut eine bestehende Verbindung ab.
Write(Message)
Schreibt den angegebenen Text in den Ausgabepuffer. Das sofortige Versenden der Nachricht ist nicht garantiert.
Writeln(Message)
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.

Ereignisse

ConnectionStateChanged(State, StateName)
Der Verbindungszustand hat sich geändert.
DelayedConnectionAborted()
Wird nach einer einstellbaren Zeit (s. ConnectionAbortedDelay) nach ConnectionStateChanged:Aborted ausgelöst.
MessageReceived(Message)
Eine Nachrichtenzeile wurde empfangen.
ErrorOccured(ActionName, ErrorCode, ErrorMessage)
Es ist ein Fehler aufgetreten.

Shared Client (UrsAI2SharedTcpClient)

Englisch version   English version

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.


Beispiele

URS TCP Client Test

  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.

URS Shared TCP Client Test

  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.

Werkzeuge

Für die Erstellung eigener Extensions habe ich einige Tipps zusammengestellt: AI2 FAQ: Extensions entwickeln.

Die Diagramme wurden mit PlantUML erstellt.