Arduino Playground is read-only starting December 31st, 2018. For more info please look at this Forum Post

TCP Command Server

This is a work in-progress to create a TCP-based command server similar to Telnet which accepts commands from up to MAX_SOCK_NUM clients at the same time.

Requirements:

  • Support up to MAX_SOCK_NUM clients at the same time
  • Allows commands to be processed even if they are sent over multiple packets (ie. fragmented)
  • Do not block while waiting for data from any clients (no readBytesUntil)
  • Stability

Headers and Ethernet configuration
  1. #include <SPI.h>
  2. #include <Ethernet2.h>
  3.  
  4. byte mac[] = { 0x90, 0xA2, 0xDA, 0x10, 0x14, 0xFF };
  5. IPAddress ip(192, 168, 10, 99);
  6. IPAddress dnsServer(192, 168, 10, 1);
  7. IPAddress gateway(192, 168, 10, 1);
  8. IPAddress subnet(255, 255, 255, 0);

Configuration Settings
  1. const int portNumber = 23;
  2. const int bufferSize = 50;
  3. const char commandTerminator = '\r';

Global variables and setup method
  1. byte buffer[MAX_SOCK_NUM][bufferSize];
  2. int bufferLength[MAX_SOCK_NUM];
  3.  
  4. EthernetServer server(portNumber);
  5.  
  6. void setup() {
  7.   Ethernet.begin(mac, ip, dnsServer, gateway, subnet);
  8.  
  9.   // Clear all buffers (by setting the current length to zero)
  10.   for (int i = 0; i < MAX_SOCK_NUM; i++)
  11.     bufferLength[i] = 0;
  12.  
  13.   server.begin();
  14. }

We need to track each unique client. EthernetClient identifies each client using a socket number from 0 to MAX_SOCK_NUM - 1. However, as clients connect and disconnect, these numbers get reused.

Therefore, I'm not sure how safe it is to identify them using this method below?

  1. int GetClientNumber(EthernetClient client)
  2. {
  3.   // Loop through all possible sockets (0 to MAX_SOCK_NUM) to find the socket (client) number  
  4.   for (int i = 0; i < MAX_SOCK_NUM; i++)
  5.     if (EthernetClient(i) == client)
  6.       return i;
  7.  
  8.   // This should be impossible...
  9.   return MAX_SOCK_NUM;
  10. }

We need to clear the buffer when a client disconnects so the next client has an empty buffer. There aren't any events fired when a client disconnects so this is the best method I was able to think of. Unfortunately it has to be called every loop iteration which seems very inefficient.

  1. void ResetBuffers()
  2. {
  3.   // If any clients are no longer connected then clear their buffer
  4.   for (int i = 0; i < MAX_SOCK_NUM; i++)
  5.     if (!EthernetClient(i).connected())
  6.       bufferLength[i] = 0;
  7. }

  1. void ProcessCommand(byte bytes[], int length)
  2. {
  3.   // TODO: Depends on specific implementation of command structure.
  4. }

Here is the loop that processes the clients and incoming data.

The code is supposed to work like this:

  1. Get a client with data to read.
  2. Retrieve the unique id (socket number) of the client.
  3. Read one character at a time from the receive buffer.
  4. If the character is a command terminator (carriage return) then process the entire command stored in the buffer.
  5. If the character is not a command terminator, add it to the buffer.
  6. If the buffer becomes full with no line terminator then something is wrong with the client so disconnect it.

  1. void loop() {
  2.   EthernetClient client = server.available();
  3.  
  4.   if (client)
  5.   {
  6.     // Get the client (socket) number
  7.     int clientNumber = GetClientNumber(client);
  8.  
  9.     // This should be impossible...
  10.     if (clientNumber == MAX_SOCK_NUM)
  11.     {
  12.       client.stop();
  13.       return;
  14.     }
  15.  
  16.     // Loop while we have data to read
  17.     while (client.available() > 0)
  18.     {
  19.       // Read a byte from the receive buffer
  20.       byte b = client.read();
  21.  
  22.       // Is this the end of a command?
  23.       if ((char)b == commandTerminator)
  24.       {
  25.         // Send the command to ProcessCommand() and reset the buffer.
  26.         ProcessCommand(buffer[clientNumber], bufferLength[clientNumber]);
  27.         bufferLength[clientNumber] = 0;
  28.  
  29.         client.println("Command received.");
  30.         break;
  31.       }
  32.  
  33.       // Byte received wasn't the end of a command, so add it to our buffer if we have room
  34.       if (bufferLength[clientNumber] < bufferSize)
  35.         buffer[clientNumber][bufferLength[clientNumber]++] = b;
  36.       else
  37.         client.stop(); // this client was about to buffer overrun; something is wrong so disconnect it
  38.     }
  39.   }
  40.  
  41.   // Clear out the buffers for any disconnected clients
  42.   // NOTE: This seems inefficient to have to check this every time.  I wonder if I can detect a client disconnect in a different way?
  43.   ResetBuffers();
  44. }