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
#include <SPI.h>
#include <Ethernet2.h>
byte mac[] = { 0x90, 0xA2, 0xDA, 0x10, 0x14, 0xFF };
IPAddress ip(192, 168, 10, 99);
IPAddress dnsServer(192, 168, 10, 1);
IPAddress gateway(192, 168, 10, 1);
IPAddress subnet(255, 255, 255, 0);
Configuration Settings
const int portNumber = 23;
const int bufferSize = 50;
const char commandTerminator = '\r';
Global variables and setup method
byte buffer[MAX_SOCK_NUM][bufferSize];
int bufferLength[MAX_SOCK_NUM];
EthernetServer server(portNumber);
void setup() {
Ethernet.begin(mac, ip, dnsServer, gateway, subnet);
// Clear all buffers (by setting the current length to zero)
for (int i = 0; i < MAX_SOCK_NUM; i++)
bufferLength[i] = 0;
server.begin();
}
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?
int GetClientNumber(EthernetClient client)
{
// Loop through all possible sockets (0 to MAX_SOCK_NUM) to find the socket (client) number
for (int i = 0; i < MAX_SOCK_NUM; i++)
if (EthernetClient(i) == client)
return i;
// This should be impossible...
return MAX_SOCK_NUM;
}
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.
void ResetBuffers()
{
// If any clients are no longer connected then clear their buffer
for (int i = 0; i < MAX_SOCK_NUM; i++)
if (!EthernetClient(i).connected())
bufferLength[i] = 0;
}
void ProcessCommand(byte bytes[], int length)
{
// TODO: Depends on specific implementation of command structure.
}
Here is the loop that processes the clients and incoming data.
The code is supposed to work like this:
- Get a client with data to read.
- Retrieve the unique id (socket number) of the client.
- Read one character at a time from the receive buffer.
- If the character is a command terminator (carriage return) then process the entire command stored in the buffer.
- If the character is not a command terminator, add it to the buffer.
- If the buffer becomes full with no line terminator then something is wrong with the client so disconnect it.
void loop() {
EthernetClient client = server.available();
if (client)
{
// Get the client (socket) number
int clientNumber = GetClientNumber(client);
// This should be impossible...
if (clientNumber == MAX_SOCK_NUM)
{
client.stop();
return;
}
// Loop while we have data to read
while (client.available() > 0)
{
// Read a byte from the receive buffer
byte b = client.read();
// Is this the end of a command?
if ((char)b == commandTerminator)
{
// Send the command to ProcessCommand() and reset the buffer.
ProcessCommand(buffer[clientNumber], bufferLength[clientNumber]);
bufferLength[clientNumber] = 0;
client.println("Command received.");
break;
}
// Byte received wasn't the end of a command, so add it to our buffer if we have room
if (bufferLength[clientNumber] < bufferSize)
buffer[clientNumber][bufferLength[clientNumber]++] = b;
else
client.stop(); // this client was about to buffer overrun; something is wrong so disconnect it
}
}
// Clear out the buffers for any disconnected clients
// NOTE: This seems inefficient to have to check this every time. I wonder if I can detect a client disconnect in a different way?
ResetBuffers();
}