Version | Anpassungen |
---|---|
1.0 (2021-04-15) | Initiale Version |
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.
Inhaltsverzeichnis
1. Versand der Zeilenendekennung mit Verzögerung
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.
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.
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.
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.
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.
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.
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.
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.
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. |
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.
Für die Erstellung eigener Extensions habe ich einige Tipps zusammengestellt: AI2 FAQ: Extensions entwickeln.