Deutsche Version   Deutsche Version


Version Adjustment
1.0 (2020-11-23) Initiale Version
--    2020-12-02 Extension UrsAI2SharedTcpClient added
1.1 (2020-12-02) In the event of a send error, the intermediate state 'Disconnecting' was removed.
1.2 (2021-01-09) Write and Writeln did not abort if there was no connection.
1.3 (2021-04-03) Default of RemotePort is 0. Avoids error messages in App Inventor.
1.4 (2021-04-16) TestConnection, CrLfDelay, IgnoreTestChar added.

Table of Contents

Download

Usage

Connection establishment and termination, connection state

Sending data

Receiving data

Event handling

Character set

Disconnection

1. Sending the line end identifier with a delay

2. Sending test messages

Reference

Properties

Remote-Endpoint

Timing

Connection state

Character set

Error handling

Methods

Events

 

Shared Client (UrsAI2SharedTcpClient)

 

Examples

URS TCP Client Test

URS Shared TCP Client Test

Tools

Download

The UrsAI2TcpClient ZIP archive for download. The archive contains the source code, the compiled binary for uploading to the App Inventor and a sample application.

Usage

The extension UrsAI2TcpClient allows to set up and operate TCP connections to a server. Text messages can be sent via TCP. The transmit takes place line by line. A line is terminated by an end-of-line identifier (CR, LF, CRLF). If such an identifier is recognized upon receipt, the characters received up to that point are reported to the application via an event.

Connection establishment and termination, connection state

The Connect method establishes a connection to the server that is specified by the RemoteAddress and RemotePort properties. ConnectTo does not use these properties, but uses the endpoint data passed as parameters to this function.

Both methods first check that the connection state is Disconnected or Aborted. If this is the case, the extension changes to the Connecting state and the ConnectionStateChanged event is triggered. Now an internally attempt is made to establish the TCP connection and to start the thread that listens for incoming data. If the connection was established successfully, the extension changes to the Connected state, otherwise to the Aborted state. Finally, the ConnectionStateChanged event is triggered again.

If the state of the extension does not allow a new connection to be established, the ErrorOccurred event is triggered with error number 4 ("Already connected.").

The disconnection of a connection is started using the Disconnect method. The state of the extension must be Connected, otherwise the event ErrorOccurred is triggered with error number 3 ("Not connected to a server."). If there is an active connection, the extension first changes to the Disconnecting state. The existing TCP connection is then terminated and the extension changes to the Disconnected state. Finally, the ConnectionStateChanged event is triggered.

After a connection was broken down, the application will usually try to establish a new connection. If this is tried immediately and repeatedly fails, the result is a high network load. The attempt to re-establish the connection should therefore only be started after a delay. The DelayedConnectionAborted event can be used for this. It is only triggered after an adjustable time (see ConnectionAbortedDelay) after a connection breakdown.

Code Zustand Bedeutung
0 Disconnected The client is not connected to a broker.
1 Connecting The client is currently trying to connect to the server.
2 Connected The cleint is connected to a server.
3 Disconnecting The cleint is currently clearing the connection.
4 Aborted The connection could not be established due to an error or the connection was interrupted. The cause of the error can be looked up via the LastErrorCode and LastErrorMessage properties.

Sending data

The Write and Writeln methods are used for sending messages. The character encoding, ie the conversion of the character string into a byte sequence, is specified by the Charset property.

Write writes the specified text into the output buffer. It is not guaranteed that the message is transmitted immediately.

Writeln writes the specified text into the output buffer and adds a line end identifier. The type of the end-of-line identifier depends on the LineDelimiterCrLf property. If true, CRLF (0x0D + 0x0A, for Windows-based servers) is appended and if false, only LF (0x0A) is appended. Then the OutputStream.flush() method is called internally, which forces the pending data to be sent.

Both methods first check whether the state is Connected. If this is not the case, the instruction is ignored and the ErrorOccured event is triggered with ErrorCode 3 ("Not connected to a server.").

If an error occurs while sending, e.g. because the connection has been broken, the ErrorOccured event is triggered with ErrorCode 5 ("Connection fault."). The connection is then terminated with the event sequence ConnectionStateChanged:Disconnecting and ConnectionStateChanged:Aborted.

Receiving data

The data is read in in an (endless) loop in a separate thread. The incoming bytes are decoded into texts using the Charset property and collected until a line end identifier (CR, 0x0D, LF, 0x0A or CRLF, 0x0D + 0x0A).) is received. The LineDelimiterCrLf property is not evaluated here. If an end-of-line identifier has been received, the characters collected up to that point are reported to the app without the end-of-line identifier via the MessageReceived event.

Tritt in dem Thread ein Fehler auf, wird die Verbindung mit dem Ereignis ConnectionStateChanged:Aborted abgebaut.

If the receiving thread terminates because of an error, the connection is terminated with the ConnectionStateChanged:Aborted event.

Event handling

In the Android environment, all network functions must be carried out in a separate thread. This is necessary so that the user interface does not freeze during long-term operations, e.g. because of bad connections. This has an impact on the triggering of events. They are not triggered directly, but via a Handler instance. The detour via Handler.post(...) shifts the execution from the separate thread to the GUI thread.

This has the consequence that the current AI2 block is executed to the end before the events are triggered. In the following constellation

 the DoSomething procedure is called first before either of the two events is triggered, which report the success of the call to Connect. So the procedures HandleErrorOccured and HandleConnectionStateChanged are executed after DoSomething.

The same applies to all events of this extension.

Character set

The data is sent as a byte sequence. To do this, the text must be coded accordingly. The coding depends on the character set used. It is important that the sender and receiver use the same coding (the same character set). The character set used is determined by the Charset property. The name of the character set must be entered. To avoid spelling errors, the common names can be called up using the Charset_... properties.

Disconnection

With a TCP connection, it should actually be guaranteed that data is either sent correctly or an error is reported. Unfortunately, this is not the case with the Java implementation - at least the one on my smartphone (Java 7). Disconnections are only recognized after two write operations with a sufficiently long time interval (see  java-detect-lost-connection). This also applies when the remote terminal properly closes the connection (disconnect, close,...). The time interval is necessary because the Nagle's algorithm cannot be switched off (see Java Socket Option TCP_NODELAY). The call to OutputStream.flush() does not change this beavior, thow it should trigger an immediate dispatch.

This extension offers two possibilities to detect disconnections with a high probability.

1. Sending the line end identifier with a delay

Mostly the Write method does not send the data immediately, but buffers the data. The transfer is forced with Writeln. If the CrLfDelay property has the value 0, the end-of-line identifier is appended and the message is sent immediately. If the value is greater than 0, initially only the message is sent. The end-of-line identifier is sent after a delay that is determined by CrLfDelay. If the connection is interrupted, the sending of the end-of-line identifier triggers an error and thus the ClientDisconnected event is triggered. With my smartphone, a time delay of 200 ms was sufficient.

This mechanism reduces the data rate and increases the latency times. The decision must be made between security and performance.

2. Sending test messages

The TestConnection method sends two characters specified by the IgnoreTestChar property with a time delay (see above). The test character must be ignored by the recipient. Usually an invisible character is used. The character is specified as a numeric character code. The default setting is 6 (ACK, Acknowledge).

If the connection is interrupted, sending the second character triggers an error and thus the ClientDisconnected event.

Reference

Properties

Remote-Endpoint

RemoteAddress
Host name or IP address of the server.
RemotePort
Port for the TCP connection.
Note: The default value is empty. If no value is entered, AI2 reports: "is not a legal integer".

Timing

ConnectionTimeout
 Time in milliseconds that is available for establishing a connection. The default value is 1000 ms.
IoTimeout
 Time in milliseconds that is available for input and output operations. The default value is 1000 ms.
ConnectionAbortedDelay
 Time delay in milliseconds between the ConnectionStateChanged:Aborted and DelayedConnectionAborted event. The default value is 3000 ms.
CrLfDelay
Delay between the sending of the data and the end-of-line identifier (see Disconnection). The default setting is 200 milliseconds.

Verbindungszustand

ConnectionState
Current connection state.
  0: Disconnected
  1: Connecting
  2: Connected
  3: Disconnecting
  4: Aborted
ConnectionStateName
Name of the current connection state.
IsConnected
 Returns true if there is an active TCP connection to the server (ConnectionState == Connected).
IsDisconnected
Returns true if there is no active TCP connection to the server (ConnectionState == Disconnected or ConnectionState == Aborted).
LineDelimiterCrLf
Specifies whether the sent message is terminated with a single "Line Feed" character ("\ n", 0x0A) or the combination "Carriage Return" + "Line Feed" ("\r\n", 0x0D+0x0A) (see function Writeln). If true, CRLF (0x0D+0x0A, for Windows-based servers) is appended and if false, only LF (0x0A) is appended.
StateDisconnected
Constant for the Disconnected state.
StateConnecting
Constant for the Connecting state.
StateConnected
Constant for the Connected state.
StateDisconnecting
Constant for the Disconnecting state.
StateAborted
Constant for the Aborted state.

Character set

Charset
Currently the character set. "US-ASCII", "UTF-8", "ISO-8859-1" (ISO-Latin-1), "UTF-16BE", "UTF-16BL" and "UTF-16" are accepted.
This value is evaluated when the connection is established and remains in effect for the entire duration of the connection.
Note: The character set is determined using the Java method Charset.forName, which is highly dependent on the implementation in the underlying operating system. So it may be that other character sets are valid. All character sets for which Charset.forName supplies valid results are accepted.
Charset_USASCII
Constant for character set US-ASCII.
Charset_UTF8
Constant for character set UTF-8.
Charset_ISO_Latin_1
Constant for character set ISO-8859-1 (ISO-Latin-1).
Charset_UTF16BE
Constant for character set UTF-16BE.
Charset_UTF16BL
Constant for character set UTF-16BL.
Charset_UTF16
Constant for character set UTF-16.
IgnoreTestChar
Character to be ignored when using method TestConnection (see Verbindungsabbruch). The numeric character code has to be entered. The default setting is 6 (ACK, Acknowledge).

Error handling

LastAction
Name of the last action, e.g. "Connect".
LastErrorCode
Error code of the last error that occurred (see below).
LastErrorMessage
Text for the code, e.g. "Invalid State."
LastExecptionCause
Text of the Java exception that triggered the error.
Code Bedeutung Text
0 No error.  
1 The specified port number is not valid. Port number is invalid.
2 Connection establishment not possible. Could not connect to server.
3 Not connected to a server. Not connected to a server.
4  An active connection already exists. Already connected.
5 Connection failure. Connection fault.

Methods

Connect()
Attempts to establish a TCP connection with the data specified in the RemoteAddress and RemotePort properties
ConnectTo(RemoteAddresse, RemotePort)
 Attempts to establish a TCP connection with the data specified in the RemoteAddress and RemotePort parameters.
Disconnect()
Terminates an existing connection.
TestConnection (ClientID, Delay)
Checks whether the connection to the specified client still exists. For this purpose, the character defined under IgnoreTestChar is sent twice with a time interval specified by Delay (milliseconds). If the connection no longer exists, the ClientDisconnected event is triggered when the second character is sent. If Delay <0, the value of CrLfDelay is used.
Write(Message)
 Writes the specified text to the output buffer. It is not guaranteed that the message is transmitted immediately.
Writeln(Message)
Writes the specified text into the output buffer and adds a line end identifier. The type of the end-of-line identifier depends on the LineDelimiterCrLf property. If true, CRLF (0x0D + 0x0A, for Windows-based servers) is appended and if false, only LF (0x0A) is appended. Sending the message is forced.

Events

ConnectionStateChanged(State, StateName)
The connection state has changed.
DelayedConnectionAborted()
 Is triggered after an adjustable time (see ConnectionAbortedDelay) after ConnectionStateChanged:Aborted.
MessageReceived(Message)
A message (line) was received.
ErrorOccured(ActionName, ErrorCode, ErrorMessage)
An error has occurred.

Shared Client (UrsAI2SharedTcpClient)

Deutsche Version   Deutsche Version

Version Adjustment
1.0 (2020-12-02) Initial version
1.1 (2021-01-09) Write and Writeln did not abort if there was no connection.
1.2 (2021-04-16) TestConnection, CrLfDelay, IgnoreTestChar added.

Note: The use of UrsAI2SharedTcpClient is not possible in the Companion. Companion does not provide all functions that are necessary to operate this extension.

This variant of the extension (UrsAI2SharedTcpClient, included in the download) uses a static (Java static) connection component, i.e. all instances of the extension in different screens of an app use the same objects. This also means that there can only be one connection in the app. The use of several instances on the same screen leads to errors when triggering the events.

The advantage of this extension is that not every screen has to worry about establishing a connection. For example, you can establish a connection in Screen1 and use it in other screens. This is not possible when using the UrsAI2TcpClient extension described above. If another screen wants to communicate with the same end point (same IP and same port), the connection in the first screen would have to be disconnected before the second screen is opened. It must then be opened again on the second screen. Before closing the second screen, the connection must be disconnected and when you return to the first screen, it must be reopened. This is all unnecessary when using the UrsAI2SharedTcpClient. The connection is made available to the second screen in the state it was left in the first screen and vice versa.

If the first screen implements a method to restore a broken connection (see the example included in the download), this functionality does not need to be repeated in the second screen. The functionality in the first screen is also active after opening a second screen (unfortunately not the other way around).

There are no designer properties available. Designer properties don't make sense. A second instance of the extension (in a second screen) would overwrite the set values ​​of the first instance. So you have to make sure that the same values ​​are entered in the designer in all screens at all times. Since usually only the IP address (or the host name) of the server and the port to be used have to be set and that only in the first screen, the additional effort is minimal.

The ConnectionStateChanged and DelayedConnectionAborted events are forwarded to all instances of the extension on all screens. MessageReceived and ErrorOccured are only triggered by the instance on the currently visible screen.


Examples

URS TCP Client Test

  With this little app you can connect to a server and send and receive text messages.

 

Note: Appropriate endpoint data must be entered in the Designer.

URS Shared TCP Client Test

With this little app you can connect to a server and send and receive text messages. It also demonstrates the use of the extension on multiple screens.

 

This is a screenshot from the second screen. You can see that the screen was opened with the connection status Connected (first line of the log). When writing, however, the connection was disturbed (server was switched off). The extension reports this via the ErrorOccured event (lines 2 and 3 of the log). The connection is then cleared. This is reported via the ConnectionStateChanged event (lines 4 and 5 of the log).

The first screen implements a function to re-establish the connection in the DelayedConnectionAborted event. After a short time, the message "State: Connecting" (line 6) appears without any further action. In the meantime the server was switched on again, so the connection could be re-established (line 7).

To try out the example, the IP address or host name of the server and the port to be used must be entered in Screen1 at the Initialize event.

Tools

I have gathered some tips for creating your own extensions: AI2 FAQ: Developing extensions.

The diagrams were created with PlantUML.