This thread is in response to a question raised in this other thread: Send data from Arduino yun to particular IP address
I'm making a different thread to prevent clutter in the original thread, because this discussion is basically the opposite of what was discussed in that other thread: it talked about making an outgoing connection to a server, while this one is talking about being a server and accepting multiple incoming connection. This discussion may be triggered by that thread, but it's really a different topic.
This is the original question:
zerox12:
Great post! Your example is exactly what I was looking for. I just want simple communication of data between Yuns.However, how can the Yun Server keep multiple TCP connections open at once? My project could have up to 12 Yun clients that all need to send data to a "master" Yun. Any idea how to do this?
This was my original response:
ShapeShifter:
I do have an example sketch that supports two client connections. But since writing that sketch I have learned that one must clean up connections after the client closes down, or else YunServer stops accepting clients after 256 connections are made.Give me a chance to clean up the code a little bit and do better housekeeping, and I will post the example.
Just be aware that RAM on the '32U4 is limited, and each connection will need a chunk of memory. It might not scale up well. If you're getting into a bunch of connections (like the dozen you need) you might be better off managing the network communications on the Linux side. This is especially true if the Yun is acting as data concentration point where it's storing the data from the 12 clients in an SD card file or database. Someone more experienced on the Linux side might have good suggestions.
And this is the example sketch I came up with:
// Yun Multi Client Example
#include <Bridge.h>
#include <YunServer.h>
#include <YunClient.h>
// Definition of the port that the server uses to listen
// for incoming connections. Set to port 23 for now so
// a standard Telnet client can make a connection. For
// actual deployment, a different application specific
// port might be a better choice since this is really
// just a generic TCP server, not a Telnet server (which
// implies an additional terminal protocol riding above
// the TCP stream.)
#define PORT 23
// The one global server object listening for incoming connections.
YunServer Server(PORT);
// Define an array of simultaneous clients
#define NUM_CLIENTS 12
YunClient clients[NUM_CLIENTS];
void setup()
{
Bridge.begin();
Serial.begin(19200);
while (!Serial)
; // Wait until a Serial Monitor is connected.
Serial.print("Multi client test sketch listening on port ");
Serial.println(PORT);
Serial.print("Supports ");
Serial.print(NUM_CLIENTS);
Serial.println(" simulataneous client connections");
Server.noListenOnLocalhost(); // Listen for external connections
Server.begin();
}
void loop()
{
// There are two interesting flags in the client:
// opened - read by converting the client object to a bool.
// connected - read by calling the connected() method
// This gives four combinations:
// closed and no connection - Try to accept a new connection.
// closed and connected - Not possible.
// open and no connection - Connection just closed, stop the client to clean up.
// open and connected - Ready to go, process the connection
// Process every client
for (byte i = 0; i<NUM_CLIENTS; i++)
{
if (clients[i].connected()) // Is it connected?
process(i); // It's connected, process the client
else
{
if ((bool)clients[i]) // Not connected. Is the client open?
{
// The client is not connected, but is currently open.
Serial.print("Lost connection to client ");
Serial.println(i);
clients[i].stop(); // Close and clean up the old connection
}
// At this point, the client is not connected, and is closed.
clients[i] = Server.accept(); // Try to establish a connection.
if (clients[i].connected()) // Was a connection made?
{
Serial.print("New connection on client ");
Serial.println(i);
}
}
}
}
void process(byte index)
{
// Read and echo all available data from the client.
String str = "";
// Read all available data, append to string
while (clients[index].available())
str = str + (char)clients[index].read();
// Trim off non-printable characters.
str.trim();
// Got something? If so, print out what was recevied, send client ID back to other side
if (str.length() > 0)
{
Serial.print("Received from client ");
Serial.print(index);
Serial.println(": received \"" + str + "\"");
clients[index].print("Client ");
clients[index].println(index);
}
}
This example handles up to 12 simultaneous connections. The number of connections can be easily changed by redefining NUM_CLIENTS. Each client takes 83 bytes of RAM, so it adds up fast and you shouldn't define more than you need. This doesn't scale well, since 12 clients already takes up 40% of the available RAM. Like I said in my original reply, handling as much of this as possible on the Linux side is probably a better solution.
There is a single global YunServer instance. Only one is needed: it listens for incoming connections, and if one is established it creates a YunClient instance to handle the connection, and goes back to listening for more connections. It is currently listening on port 23, which makes it easy to test using a Telnet client. But in a real application you would want to use a unique port number to prevent confusion with well known services: like how I arbitrarily chose port 255 in the example in the other thread.
There is an array of YunClient instances, one for each supported simultaneous connection. The code in loop() manages the array of clients, and prints out messages to the serial port when new connections are made, or established connections are closed. If your application needs to do anything special when connections are made or broken, your code would go in there. However, if you make changes, it's critical that the call to the client's stop() method not be removed, as it will eventually stop accepting connections without it.
The process() function is called for each client that has an active connection. It simple looks to see if there is any incoming data from the client, and if so, it prints that data to the serial port and sends a message back to the client. Of course, a real application would do something much more interesting here.
The sketch starts out by waiting for a serial port connection, so that you can see the initial messages. A real application won't want to do this, or else it will never run if you don't have the micro-USB port connected to a computer with the serial monitor open.
When making connections, which client number receives the connection is random: it is constantly looping through clients and trying to accept a connection for each one that doesn't already have a connection. It's just a matter of where it was in the loop when when the connection came in.
If all of the clients are busy with connections, and you try to make another connection, the Linux side will accept the connection, but nothing will happen until a client is freed up (its connection is closed.) At that point, the client will accept the connection and establish communications. So if you get the situation where it looks like the connection is established, but there is no communications, you've likely run out of client objects to handle the connection.