Tcp Server

Table of Contents

A Tcp Server example to create a non-blocking Tcp interface that manages Tcp Client connections, receives commands and sends telemetry using basic packet header definitions.

Tcp Server

At its core, a Tcp Server creates a Tcp Listener, waits for a client to connect, reads and enqueues the client command to a desintation target which then parses and handles the message before writing telemetry back to the client.

The issue with the built-in LabVIEW Tcp functions are that the VIs block execution. The goal of this configuration allows the Tcp connections to be handled by an independant thread without affecting the execution of the target loop. This Tcp Server example is most commonly used with queued state machines.

For the sake of this article, all functions in the snippet below are flattened into diagram for training purposes only. Typically, each function (Open, Read CMD, Write TLM, Close) should be encapsulated in its own SubVI.

Tcp Server

Download VI (LV2021)

Tcp Server - Open

When the Tcp Server starts, a single element queue (1.) is created to share the current tcp client connection, if the queue is empty it means there are no clients connected. The target queue (2.) specifies where the client commands and error messages are to be enqueued. A string queue is recommended since the Tcp Read returns a string, this way the message and data can easily be concatenated without additional flattening/unflattening.

Note: At this point, the Tcp Server Open function would typically launch a listener daemon thread (i.e. Run a VI asynchronously via VI Server) to launch a separate loop but for this example the loop is embedded in the diagram above the target loop.

Tcp Server - Thread

When the thread is launched, the Server Port (3.a.), Command Interrupt message (3.b.) and Error Interrupt message (3.c.) would be passed to the listener daemon. For this example we’ll use constants. The Server Port is the local machine port on which the Tcp Listener waits for a client connection. The Command and Error Interrupts are the message prefixes for each command and/or error sent to the target queue. The Interrupts allow different Tcp Server interfaces/ports to be enqueued to different states, such as to handle different header definitions. The Interrupts are verbose, make sure the add the message delimiter (i.e. Comma “,") at the end of the interrupt (if the target queue expects “message,data..")

In the Tcp Listener thread, the Tcp Create Listener (4.) opens a local port for clients to connect. Listener ports (or any Tcp port for that matter) cannot be reopened until a set amount of time has passed (typically 2 minutes). If the listener tries to reconnect on a ‘TCP_WAIT’ port status, the Tcp Create Listener VI will return Error 63 - Connection Refused. If any error occurs while creating the listener, the entire thread is aborted and the connection single element queue is destroyed (escentially killing the Tcp Server interface).

The Tcp Listener loop then Waits for Listener (5.) until a client connects. It only waits for 100 ms each time, so that if the target exits, this thread shuts down promptly. If any error occurs while waiting for a client to connect, the error is ignored and the connection single element queue is flushed to signal that no client is connected. If the Tcp Listener or Connection single element queue become invalid, the thread shuts down.

Once a client connects (5.), the client tcp connection reference is lossy enqueued to replace the connection in the single element queue. This does two things:

  1. Notifies that a client is connected (queue not empty) and
  2. Allows the connection to be shutdown/restart from the target loop (which is used when closing the Tcp Server instance).

Also note, the single element queue is updated prior to the Wait for command loop. This is on purpose to make sure anybody that has access to the connection SEQ, can force quit the Tcp Read VI since they share the same tcp reference.

Command Read

With the new client connected, the Tcp Listener loop then goes into the Wait for command loop which waits indefinitely to Tcp Read (7.) the packet’s header bytes. This blocks execution until the number of header bytes are available at the server port (maximizing performance by idling until a new command is recieved). The command header is then parsed and the remaining bytes are read from the port (8.). In this instance, the header contains 6 bytes:

   1. LENGTH : I32  // 4 byte integer - total packet length
   2. ID : U16      // 2 byte unsigned-integer - unique packet id

Every packet header must have a length and unique id parameter to distinguish between different command & telemetry packets. The remaining bytes to read need to be offset by the number of bytes already read from the header. In this case, we read 6 bytes at the first Tcp Read so the bytes remaining to read are:

REMAINING_BYTES_TO_READ = TOTAL_LENGTH - HEADER_BYTE_SIZE

Since there are already bytes at the port, only wait up to 5 seconds for the remaining bytes to arrive, if not ERROR 56 - Timed out will occur, the loop will stop and the client connection will close.

The completed packet is then enqueued to the target (9.), including the prefixed Command Interrupt. In this example, TCP_CMD, is prefixed with every command enqueued.

Internal Error

If an error occurs at any point in the Wait for command loop, the loop stops and the current Tcp client connection is closed (10.). Any error is enqueued to the target (11.) aside from the following ignored errors:

  • ERROR 1: Parameter invalid (i.e. Queue invalid)
  • ERROR 66: Connection closed

The loop then iterates and waits for a new client connection. When an error occurs, the loop waits for a second to avoid any loop run-away situations.

Thread Shutdown

Lastly the connection single element queue is flushed, resetting the client connection. The Tcp listener loop stops if the listener, connection SEQ or target queue become invalid. If that happens the listener connection and connection SEQ are closed (13.).

Tcp Server - Target

Let’s say the Tcp Listener thread is running, a client is connected, and has just sent the first “Hello World” command. The target loop is where the client command gets enqueued to be unflattened and handled.

When a new client command is enqueued, the target loop dequeues (14.) the command and parses the Command Interrupt. In this case our command interrupt is TCP_CMD, where we split the message by the comma.

Note: The target loop could be handling other commands, any only gets notified when a new client command is received. This is what I mean by a “non-blocking” interface. The primary loop (i.e. target loop) can handle other messages without sitting and wait for the tcp connection to return data.

Unflatten Command

Next, in the TCP_CMD case, the command header is parsed (15.) to strip off the header bytes and return the Command ID (i.e. HDR_ID). This tells us which command packet was sent by the client.

The remaining command data is unflattened (16.) into meaningful LabVIEW data types (i.e. clusters) and handled here or passed to another target to be executed (17.).

Write Telemetry

Telemetry is typically written at a periodic interval but for this demonstration we are going to send telemetry back to the client right after command (id = 1) is handled. Similar to how we unflattened the command data, writing telemetry does the opposite. Specify the Telemetry ID and telemetry data (i.e. cluster) to send back to the client (18.).

Note: Each Telemetry Id must be unique or the client won’t know how to parse the data.

This is where the connection single element queue comes into play. Preview connection SEQ with a timeout of zero to return the current client tcp connection reference. If the SEQ is empty or the Tcp refnum is invalid, there are no clients connected and don’t send the telemetry (because nobody’s listening). If a client is connected, build the telemetry packet definition including the header and write the telemetry to the tcp connection (20.).

Note: The reason why we pass the tcp reference in a SEQ (rather than back to the Tcp Thread) is to ensure that data is sent to the port in a deterministic manor. Writing directly to the port ensures that the data is put on the Network Interface stack prior to continuing. This is important if there are critical/hazardous operations that need precise sequenced responses.

Tcp Server - Close

Shutting down the Tcp Server is as simple as closing the connection single element queue (21.). Ensure that any client connections are closed (22.) so the ports doesn’t remain active after the software shuts down.

Summary

Hopefully this example sheds light on how to configure a basic (but powerful) Tcp Server with:

  • Auto-reconnecting clients
  • Non-blocking interfaces
  • Unique command & telemetry identifiers and
  • Multiple packet definitions

As your applications grow, you’ll find that robust external interfaces are key to scaling. I’ve gotten some questions as to why I don’t use Shared Variables, Network Streams or Tunnels, to that I’d respond: Proprietary is problematic, Keep It Simple Stupid and Don't fix it if it ain't broke. Tcp’s been arround since the beginning of computers, it’ll outlast any “new” tech on the block.

Best Regards!