Englisch version   English version


Version Anpassungen
1.0 (2021-04-15) Initiale Version

Motivation

Will man Daten zwischen zwei Smartphones per TCP austauschen, geht das nur, wenn eines der Geräte die Server-Rolle übernimmt, d.h. Verbindungsanforderungen entgegen nimmt. Der (TCP-) Client meldet sich beim (TCP-) Server an (connect), der Server akzeptiert die Anfrage (accept). Danach besteht eine gültige TCP-Verbindung, die den Datenaustausch in beide Richtungen ermöglicht.


In­halts­ver­zeich­nis

Download

Verwendung

Übersicht

Zeichensatz

Verbindungsabbruch

1. Versand der Zeilenendekennung mit Verzögerung

2. Versand von Test-Nachrichten

Fehlerbehandlung

Referenz

Eigenschaften

Zeichensatz

Fehlerbehandlung

Methoden

Ereignisse

Beispiel

URS TCP Client Test

Werkzeuge

Download

Das ZIP-Archiv UrsAI2TcpServer zum Download. Das Archiv enthält den Quellcode, das kompilierte Binary zum Upload in den App Inventor und eine Beispiel-Anwendung.

Verwendung

Hinweis:

Die Extension klappt einwandfrei, wenn sich Smartphone und Gegenstation im gleichen (lokalen) Netzwerk befinden. Ist das Smartphone nur per Mobilfunknetz verbunden, ist es i.d.R. nicht erreichbar. Der Grund ist, dass das Smartphone nicht direkt mit dem Internet verbunden ist, sondern nur mit dem lokalen Netzwerk des Mobilfunkanbieters.

Mobilfunk-Verbindung

Ein Gerät im eigenen LAN kann man von außerhalb per Port-Weiterleitung (port porwarding) am eigenen Router erreichen (ggf. via DDNS). Den Router des Providers wird man nicht beeinflussen können. Also senden vom Smartphone an ein Gerät im LAN funktioniert, umgekehrt geht es nicht.

Das gleiche gilt übrigens auch für TCP. Wenn das Smartphone nur mit dem Mobilfunknetz verbunden ist, kann die Verbindung kann nur vom Smartphone aus aufgebaut werden. Das Gerät im LAN muss als Server agieren, wartet also auf eine eingehende Verbindungsanforderung. Das Smartphone agiert als Client, initiiert den Verbindungsaufbau. Ist die Verbindung einmal eingerichtet, haben sich die Router intern über Adressen und Ports abgestimmt. Der Datentransfer ist dann in beide Richtungen möglich.

Übersicht

Die Extension UrsAI2TcpServer ermöglicht es TCP-Clients, eine TCP-Verbindungen zu einem anderen Android-Device aufzunehmen. Es können Textnachrichten per TCP ausgetauscht 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 Start startet den Server. Er ist nun bereit, Verbindungsanforderungen der Clients auf dem über die Eigenschaft LocalPort spezifizierten Port entgegen zu nehmen. Die IP-Adresse des Server kann über die Eigenschaft LocalHost abgerufen werden. Stop stoppt den Server wieder. Wird Start bei laufendem Server aufgerufen, wird der Server zunächst gestoppt und dann mit den evtl. geänderten Einstellungen neu gestartet. Sämtliche Einstellungen zum Server werden beim Start übernommen. Eine Änderung der Konfiguration hat erst beim nächsten Aufruf von Start eine Auswirkung.

Die Ereignisse ServerStarted und ServerStopped geben Auskunft über Änderungen des Zustands. ServerStopped wird auch dann ausgelöst, wenn der Server auf Grund eines (Netzwerk-) Fehlers gestoppt wurde. Die Ereigniskombination ServerStarted und ServerStopped wird auch dann ausgelöst, wenn es wegen eines Fehlers gar nicht erst zum Start des Servers kommt. Die Eigenschaft IsRunning gibt Auskunft darüber, ob der Server aktiv ist.

Meldet sich ein Client beim Server an, wird das Ereignis ClientConnected ausgelöst. Übergeben wird eine numerische ID des Clients (ClientID) unter der er intern geführt wird und seine IP-Adresse (ClientIP). Trennt der Client die Verbindung wird das Ereignis, wird das Ereignis ClientDisconnected ausgelöst. Die Eigenschaft ConnectedClients liefert die Anzahl der aktuelle mit dem Server verbundenen Clients und die Funktion GetClientIDs liefert eine Liste mit deren IDs.

Eine eingehende Nachricht des Clients löst das Ereignis MessageReceived aus. Neben der eigentlichen Nachricht wird auch die ClientID übergeben, so dass dem Client direkt über die Funktionen Write und Writeln geantwortet werden kann. 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.

Zeichensatz

Der Versand der Daten erfolgt als Byte-Folge. Dazu muss der Text entsprechend umcodiert werden. Je nach verwendetem Zeichensatz wird eine andere Codierung verwendet. Wichtig ist, dass Sender und Empfänger die gleiche Codierung (den gleichen Zeichensatz) verwenden. Der verwendete Zeichensatz wird über die Eigenschaft Charset festgelegt. Es ist die Bezeichnung des Zeichensatzes anzugeben. Zur Vermeidung von Schreibfehlern die gängigen Bezeichnungen über die Eigenschaften Charset_... abgerufen werden.

Verbindungsabbruch

Eigentlich sollte bei einer TCP-Verbindung garantiert sein, dass Daten entweder korrekt versendet werden oder ein Fehler gemeldet wird. Dies ist bei der Java-Implementierung -zumindest der auf meinem Smartphone (Java 7)- leider nicht der Fall. Verbindungsabbrüche werden erst dann erkannt, wenn zwei Schreiboperationen mit genügend großen zeitlichen Abstand ausgeführt werden (siehe java-detect-lost-connection). Dies gilt auch für den Fall, dass die Gegenstelle die Verbindung ordnungsgemäß schließt (disconnect, close, ...). Der zeitliche Abstand ist notwendig, weil der sich der Nagle-Algorithmus nicht abschalten lässt (s. Java Socket Option TCP_NODELAY). Daran ändert auch der Aufruf von OutputStream.flush() nichts, der eigentlich den sofortigen Versand auslösen soll.

Diese Extension bietet zwei Möglichkeiten, Verbindungsabbrüche mit hoher Wahrscheinlichkeit zu erkennen.

1. Versand der Zeilenendekennung mit Verzögerung

Die Methode Write sendet i.d.R. die übergebenen Daten nicht sofort, sondern puffert die Daten. Erst bei Writeln wird die Übertragung erzwungen. Hat die Eigenschaft CrLfDelay den Wert 0, werden keine weiteren Maßnahmen getroffen. Die eingestellte Zeilenendekennung wird angehängt und die Nachricht versendet. Ist der Wert größer als 0, wird zunächst nur die Nachricht versendet. Die Zeilenendekennung wird erst mit einer Verzögerung versendet, die durch CrLfDelay bestimmt ist. Dadurch löst bei unterbrochener Verbindung der Versand der Zeilenendekennung einen Fehler und damit das Ereignis ClientDisconnected aus. Bei meinem Smartphone war eine Zeitverzögerung von 200 ms ausreichend.

Dieser Mechanismus reduziert natürlich den Datendurchsatz und erhöht die Latenzzeiten. Die Entscheidung muss zwischen Sicherheit und Performance getroffen werden.

2. Versand von Test-Nachrichten

Die Funktion TestConnection sendet mit zeitlicher Verzögerung (s.o.) zwei Zeichen, die über die Eigenschaft IgnoreTestChar spezifiziert wurden. Das Testzeichen muss vom Empfänger ignoriert werden. Üblicherweise nimmt man als Testzeichen ein unsichtbares Zeichen. Das Zeichen wird als numerischer Zeichencode angegeben. Die Voreinstellung ist 6 (ACK, Acknowledge).

Der Versand des zweiten Zeichens löst bei unterbrochener Verbindung einen Fehler und damit das Ereignis ClientDisconnected aus.

Fehlerbehandlung

Fehler werden über Fehlernummern (ErrorCode) ausgewiesen. Fehler werden über die Ereignisse ErrorOccurred und ServerStopped gemeldet. Über die Eigenschaften LastError... können weitere Informationen zum Fehler abgerufen werden.

Code Bedeutung Text Anmerkung
0 Kein Fehler    
1 Der Server kann nicht gestartet werden. Cannot create server socket. Betroffene Funktion Start. I.d.R. ist der Port ungültig oder bereits belegt. Der Fehlercode wird über das Ereignis ServerStopped ausgeliefert. Details könne über die im Abschnitt Fehlerbehandlung beschriebenen Eigenschaften abgerufen werden.
2 Der Server wurde auf Grund eines Fehlers gestoppt. Server stopped due to an error. Der Fehlercode wird über das Ereignis ServerStopped ausgeliefert. Details könne über die im Abschnitt Fehlerbehandlung beschriebenen Eigenschaften abgerufen werden.
3 Daten konnten wegen eines Verbindungfehlers nicht übertragen werden. Connection fault. Betroffene Funktion Write, Writeln. Der Fehlercode wird über das Ereignis ErrorOccurred ausgeliefert. Details könne über die im Abschnitt Fehlerbehandlung beschriebenen Eigenschaften abgerufen werden.
4 Die angegebene Client-ID ist ungültig. Invalid ClientID. Betroffene Funktion Write, Writeln, TestConnection. Der Fehlercode wird über das Ereignis ErrorOccurred ausgeliefert.

Referenz

Eigenschaften

ConnectedClients
Liefert die Anzahl der aktuell mit dem Server verbundenen Clients.
CrLfDelay
Verzögerung zwischen dem Versand der Daten und der Zeilenendekennung (s. Verbindungsabbruch). Die Voreinstellung ist 200 Millisekunden.
IgnoreTestChar
Zu ignorierendes Zeichen bei Benutzung von TestConnection (s. Verbindungsabbruch). Anzugeben ist der numerische Zeichencode. Die Voreinstellung ist 6 (ACK, Acknowledge).
IoTimeout
Zeit in Millisekunden, die für Ein- und Ausgabeoperationen zur Verfügung steht. Der Standardwert ist 1000 ms.
IsRunning
Gibt den Zustand des Servers an.
LineDelimiterCrLf
Legt fest, ob die versendete Nachricht durch ein einzelnes "Line Feed"-Zeichen ("\n", 0x0A) oder die Kombination "Carriage Return" + "Line Feed" ("\r\n", 0x0D+0x0A) abgeschlossen wird (s. Funktion Writeln). Bei true wird CRLF (0x0D+0x0A, für Server auf Windows-Basis) und bei false nur LF (0x0A) angehängt.
LocalHost
Gibt die lokale IP-Adresse zurück.
LocalPort
Legt fest, auf welchem Port der Server Verbindungsanforderungen entgegen nimmt.
NICList
Liefert eine Liste der IPv4-Adressen aller eingerichteten Netzwerkschnittstellen. In der Regel enthält die Liste nur einen Eintrag (s. LocalHost).

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. "Start".
LastErrorCode
Fehler-Code des zuletzt aufgetretenen Fehlers (s.u.).
LastErrorMessage
Text zum Code, z.B. "Invalid ClientID."
LastExecptionCause
Text der Java Exception, die den Fehler ausgelöst hat.

Methoden

GetClientIDs ()
Liefert eine Liste der IDs aller mit dem Server verbundenen Clients.
Start ()
Startet den Server. Wird diese Anweisung bei bereits laufendem Server ausgeführt, wird dieser zunächst gestoppt.
Stop ()
Stoppt den Server.
TestConnection (ClientID, Delay)
Prüft, ob die Verbindung zum angegeben Client noch besteht. Dazu wird im zeitlich Abstand von Delay (Angabe in Millisekunden) zweimal das unter IgnoreTestChar definierte Zeichen versendet. Besteht die Verbindung nicht mehr, wird beim Versand des zweiten Zeichens das Ereignis ClientDisconnected ausgelöst.
Ist Delay < 0 wird der Wert von CrLfDelay übernommen.
Write (ClientID, Message)
Schreibt den angegebenen Text in den Ausgabepuffer des angegebenen Clients. Das sofortige Versenden der Nachricht ist nicht garantiert.
Writeln (ClientID, 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. Das Versenden der Nachricht wird erzwungen.

Ereignisse

ErrorOccurred (ActionName, ErrorCode, ErrorMessage)
Es ist ein Fehler aufgetreten (s. Fehlerbehandlung).
ClientConnected (ClientID, ClientIP)
Ein neuer Client hat sich mit dem Server verbunden. Übergeben wird eine numerische ID des Clients unter der er intern geführt wird und seine IP-Adresse.
ClientDisconnected (ClientID, ClientIP)
Ein Client hat die Verbindung zum Server getrennt. Übergeben wird eine numerische ID des Clients unter der er intern geführt wurde und seine IP-Adresse.
MessageReceived (ClientID, Message)
Es wurde eine Nachricht vom angegeben Client (ClientID) empfangen.
ServerStarted ()
Der Server wurde gestartet.
ServerStopped (ErrorCode)
Der Server wurde gestoppt. Wenn der Server auf Grund eines Fehlers gestoppt wurde, hat ErrorCode einen Wert größer als 0 (s. Fehlerbehandlung).

Beispiel

URS TCP Server Test

Diese App richtet einen TCP-Server ein, der auf dem Port 8083 Verbindungsanforderungen entgegen nimmt. Clients können sich mit dem Server verbinden und Textnachrichten versenden und empfangen. Der Server sendet nach dem Empfang einer Nachricht den Text "Echo " plus die empfangenen Text an den Client zurück. Der Server kann mit der UrsAI2TcpClient Extension getestet werden.

 

Hinweis: Im Designer müssen noch passende Endpunktdaten eingetragen werden.

Werkzeuge

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