Yep, comments are often useful when we are not that lazy to include them
BTW I've found a topic telling about modifying the Eth library to get the Source IP, and being then able to support multiple clients. I don't know if it really helps.
Also found this where the guy says that W5100 supports 4 sockets (so max 4 clients) but the Eth lib can NOT distinguish them if they are on the same port (might be a a workaround, to open 4 port instead of one, ie 5003,5004,5005,5006). He's finally submitted a patch against the Eth lib to allow 4 different sockets on the same port.... But the more interesting seems to be a comment by "Gene" who has posted complete sketch showing how to connect multiple telnet clients . Here is his code:
/* exampleMultiTelnetServer.ino */
/*
* Created: May 21, 2015
*
* Created by Gene Reeves to demonstrate how the W5100 eithenet handles multiple connections
* using a single server object instance.
*
* once the sketch is loaded on your arduino and you see the start up banner display in the
* Serial Monitor, fire up your favorite telnet client and connect a few instances.
*
* from the serial monitor you can send text to one or all clients. Use '*" for the ID to send
* to all, otherwise use the ID you wish to send to, using this format to send,
* "ID:TEDXT_TO_SEND". That should make everything as clear as mud :-)
*
* Dependencies:
*
* uses LinkedList class from here https://github.com/ivanseidel/LinkedList
* to store list of connected clients.
*
* uses StringStream class from here https://gist.github.com/cmaglie/5883185
* Not sure why this was not a part of the StringObject to start with..
*
* uses elapsedMillis class from here http://playground.arduino.cc/Code/ElapsedMillis
* much improved version (for Teensy brand mcu's) here https://www.pjrc.com/teensy/td_timing_elaspedMillis.html
* Unless I am mistaken, I beleive this code was originated by Paul Stoffregen of www.pjrc.com
* If you are not famialar with Paul or his Teensy microcontroller, you would do yourself a
* solid to spend some time reviewing his code and you can't beat the Teensy 3.1 for
* price/performance/support!!!
* Teensy 3.1 - 32bit-ARM mcu @ 72MHz (overclockable to 168MHz) w/ 256K flash / 64K ram / USB
* for under $20. https://www.pjrc.com/teensy/index.html
*
* BTW, I am in no way affilaited with Paul or PJRC, I just think he built an awesome mcu.
*
*/
#include <SPI.h>
#include <LinkedList.h>
#include <elapsedMillis.h>
#include <StringStream.h>
#include <Arduino.h>
#include <EthernetUdp.h>
#include <EthernetServer.h>
#include <EthernetClient.h>
#include <Ethernet.h>
#include <Dns.h>
#include <Dhcp.h>
#include <IPAddress.h>
#include <Server.h>
#include <Client.h>
/* DEFINES */
/*****************************************************/
/* Change the following to suite your environment. */
#define TELNET_SERVER_PORT 23
#define TELNET_SERVER_MAC 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED
#define TELNET_SERVER_IP 192, 168, 1, 177
#define TELNET_SERVER_GATEWAY 192, 168, 1, 1
#define TELNET_SERVER_NETMASK 255, 255, 255, 0
#define SERIAL_BAUD 115200
#define WELCOME_BANNER_STRING "Welcome to MultiTelnetServer.\r\nType 'exit' to disconnect\r\n"
#define GOODBYE_BANNER_STRING "Come Back Again, Goodbye.."
#define APP_BANNER_STRING "\r\n\r\n\tMultiTelnetServer Example\r\n This example application strives to\r\ndemonstrate using the Wiznet W5x00\r\nEthernet Shield's Arduino library usage\r\nwhen serving mulitple concurrent\r\nconnects using a single 'server object'\r\ninstance."
#define SERVER_LISTENING_STRING "\tServer Listening on"
/******************************************************/
/*
* This is required to add RemoteIP, RemotePort support to the EthernetClient class
* I stole it from here http://forum.arduino.cc/index.php?topic=210857.0
*/
//namespace ethernet_fix
//{
#include <utility/w5100.h>
template<typename Tag, typename Tag::type M>
struct AccessMember{
friend typename Tag::type get(Tag){ return M; }
};
struct EthernetClient_Socket{
typedef uint8_t EthernetClient::*type;
friend type get(EthernetClient_Socket);
};
template struct AccessMember < EthernetClient_Socket, &EthernetClient::_sock > ;
IPAddress RemoteIP(EthernetClient &c){
byte remoteIP[4];
W5100.readSnDIPR(c.*get(EthernetClient_Socket()), remoteIP);
return (remoteIP);
}
uint16_t RemotePort(EthernetClient &c){
return W5100.readSnDPORT(c.*get(EthernetClient_Socket()));
}
//} // namespace "ethernet_fix"
/*
* EthernetClientEx - Because there is still no RemoteIP or RemotePort
* properties built in to th current EthernetClient!!
*
*/
class EthernetClientEx :
public EthernetClient
{
protected:
uint8_t _sock; // hack to get access to socket #
public:
EthernetClientEx(const EthernetClient &orig) : EthernetClient(orig) {}
IPAddress remoteIP() { return RemoteIP((EthernetClient &)(*this)); }
int16_t remotePort() { return RemotePort((EthernetClient &)(*this)); }
bool isSameSock(const EthernetClientEx &c) { return (_sock == c._sock); }
};
/*
* ClientItem is class to wrap EthernetClient for storage on linked list
* we will use it to add a ts (millis at connection time) and an index.
*
*/
class ClientItem
{
public:
unsigned long timestamp;
int index;
size_t recv_cnt;
String recv_buffer;
EthernetClientEx *client;
ClientItem() { timestamp = millis(); index = -1; client = (EthernetClientEx *)false; }
virtual ~ClientItem() { delete client; /* release memory */}
unsigned long elapsed(void) { unsigned long ts = millis(); return (ts - timestamp); }
};
/*
* ClientList is linkedlist for storing connected clients.
* For now, we'll just use it to store our clients, but should
* expand this close to include testing all clients for disconnects,
* testing all clients for pending recv data and sending data
* to all connected clients.
*/
class LinkedClientList :
public LinkedList<ClientItem*> //ClientList;
{
public:
//EthernetServer *server;
ClientItem *getClientItem(int idx)
{
for (ListNode<ClientItem *> *n = root;n;n=n->next)
{
if (n->data->index == idx)
return n->data;
}
return (ClientItem *)0;
}
bool exists(const EthernetClientEx &ece)
{
for (ListNode<ClientItem *> *n = root; n; n = n->next)
if (n->data->client->isSameSock(ece))
return true;
return false;
}
void drop(int idx)
{
ClientItem *cli;
int real_idx = -1;
int ci = 0;
for (ListNode<ClientItem *> *n = root; n; n = n->next)
if (n->data->index != idx)
ci++;
else
{
cli = n->data;
real_idx = ci;
break;
}
if (real_idx != -1)
{
if (cli->client->connected())
cli->client->stop();
cli->index = -1;
remove(real_idx);
}
}
size_t send(const String &s, int idx)
{
size_t ret = 0;
ClientItem * cli = getClientItem(idx);
if (cli)
{
ret = cli->client->print(s);
cli->client->flush();
}
return ret;
}
size_t send2All(const String &s)
{
size_t ret = 0;
for (ListNode<ClientItem *> *n = root; n; n = n->next)
{
ret += n->data->client->print(s);
n->data->client->flush();
}
return ret;
}
};
LinkedClientList ClientList;
// Enter a MAC address and IP address for your controller below.
// The IP address will be dependent on your local network.
// gateway and subnet are optional:
byte mac[] = { TELNET_SERVER_MAC };
IPAddress ip(TELNET_SERVER_IP);
IPAddress gateway(TELNET_SERVER_GATEWAY);
IPAddress subnet(TELNET_SERVER_NETMASK);
// buffer to hold input from console
StringStream con_buffer("");
// telnet defaults to port 23
EthernetServer server(TELNET_SERVER_PORT);
/* forward's */
void server_check_new_connections(void);
void server_check_client_recv(void);
int server_accept(EthernetClientEx *enetcli);
bool clientExists(const EthernetClientEx &c);
bool test_for_cmd(const String &itm, const String &src);
size_t console_recv(void);
void proc_line(StringStream &ss);
size_t client_recv(ClientItem *cli);
void client_disconnecting(ClientItem *cli);
void client_disconnected(ClientItem *cli);
size_t send_to(int idx, const String &str);
size_t send_to_all(const String &str);
/*********************************************************/
void setup()
{
/* add setup code here */
// reserve some space for input buffer
con_buffer.reserve(256);
// Open serial communications and wait for port to open:
Serial.begin(SERIAL_BAUD);
delay(3000); // 3 second delay for Serial to connect.
pinMode(10, OUTPUT);
digitalWrite(10, HIGH);
pinMode(4, OUTPUT);
digitalWrite(4, HIGH);
// initialize the ethernet device
Ethernet.begin(mac, ip, gateway, subnet);
// start listening for clients
server.begin();
// Print "App Banner" and "Server Listening" notice.
Serial.println(F(APP_BANNER_STRING));
Serial.println(F(SERVER_LISTENING_STRING));
Serial.print(F("\t\t"));
Serial.print(ip);
Serial.print(':');
Serial.println(TELNET_SERVER_PORT);
Serial.println(F("\r\n\r\n"));
}
void loop()
{
/* add main program code here */
// check for any new clients
server_check_new_connections();
// give up a timeslice
yield();
// check to see if any of the clients
// have recv data pending
if (ClientList.size() > 0)
server_check_client_recv();
// give up a timeslice
yield();
// check to see if console has any
// pending recv data.
if (Serial.available())
console_recv();
// give up a timeslice
yield();
}
/*********************************************************/
size_t console_recv(void)
{
size_t ret = 0;
int c = 0;
while ((c = Serial.read()) >= 0)
{
if (c == 10)
{
proc_line(con_buffer);
con_buffer = String("");
con_buffer.begin();
continue;
}
if (c != 13)
{
con_buffer += (char)c;
ret++;
}
}
return ret;
}
void proc_line(StringStream &ss)
{
// Console input should be in the for of
// "ID:string_literal"
// where:
// ID is client index to send to or "*" (all)
// string_literal is string to send
//
// check to ensure 2nd char is a ":"
if ((ss.length() < 3) || (ss.charAt(1) != ':'))
{
Serial.print(F("Invalid Console Input \""));
Serial.print(ss);
Serial.print(F("\"\r\nPlease use the following format:\r\n\t"));
Serial.println(F("\"ID:string_literal\"\r\n"));
Serial.flush();
return;
}
int idx = -1;
Serial.print(F(" first char is '"));
Serial.print(ss.charAt(0));
Serial.print(F("', "));
if (ss.charAt(0) != '*')
{
idx = ss.parseInt();
}
Serial.print(F("idx="));
Serial.print(idx);
Serial.print(F(", indexOf(':')="));
Serial.println(ss.indexOf(':'));
ss = ss.substring(1 + ss.indexOf(':'));
ss += F("\r\n");
if (idx < 0)
ClientList.send2All(ss);
//send_to_all(ss);
else
ClientList.send(ss, idx);
}
size_t console_print_connected_count(void)
{
size_t ret =
Serial.print(F("\t\t")) +
Serial.print(ClientList.size()) +
Serial.println(F(" connected client(s).\r\n"));
Serial.flush();
return ret;
}
bool test_for_cmd(const String &itm, const String &src)
{
String tst(src);
tst.toLowerCase();
return tst.startsWith(itm);
}
void server_check_new_connections(void)
{
EthernetClient obj = server.available();
if (obj)
{
// convert to EthernetClientEx
EthernetClientEx new_client(obj);
// is it a new connection?
if (!ClientList.exists(new_client))
{
// accept new connection
server_accept((EthernetClientEx *)&new_client);
// send welcome banner
new_client.println(F(WELCOME_BANNER_STRING));
new_client.flush();
}
}
}
int server_accept(EthernetClientEx *enetcli)
{
// add itm to list
ClientItem *itm = new ClientItem();
itm->timestamp = millis();
itm->index = ClientList.size();
itm->client = new EthernetClientEx((*enetcli));
ClientList.add(itm);
// print notice on console
Serial.print(F("Accepted new connection ("));
Serial.print(itm->index);
Serial.print(F(") from "));
Serial.print(enetcli->remoteIP());
Serial.print(':');
Serial.println(enetcli->remotePort());
Serial.println();
Serial.flush();
// print connected count
console_print_connected_count();
// return items index
return itm->index;
}
void server_check_client_recv(void)
{
if (ClientList.size() == 0) return;
ClientItem *itm;
for (int idx = ClientList.size() - 1; idx >= 0; idx--)
{
itm = ClientList.get(idx);
if (itm->client->connected()==0)
{
client_disconnected(itm);
continue;
}
if (itm->client->available())
{
size_t rc = client_recv(itm);
if (rc > 0)
{
// echo to Serial port
Serial.print(F("Client #"));
Serial.print(itm->index);
Serial.print(F(" sent \""));
Serial.print(itm->recv_buffer);
Serial.println('\"');
Serial.flush();
}
// rc will == 0 if nothing received
// rc will == -1 if client entered "exit"
}
yield();
}
}
size_t client_recv(ClientItem *cli)
{
int c;
EthernetClientEx *cIn = (EthernetClientEx *)cli->client;
cli->recv_buffer = "\0";
cli->recv_cnt = 0;
while ((c = cIn->read()) >= 0)
{
cli->recv_buffer += (char)c;
cli->recv_cnt++;
}
// check to see if they typed "exit"
if (test_for_cmd(String(F("exit")), cli->recv_buffer))
{
client_disconnecting(cli);
delay(1); // give up timeslice
cli->client->stop();
Serial.print(F("called client->stop() for #")); Serial.println(cli->index); Serial.flush();
return -1;
}
return cli->recv_cnt;
}
void client_disconnecting(ClientItem *cli)
{
Serial.print(F("Client #"));
Serial.print(cli->index);
Serial.println(F(" is disconnecting."));
// send Goodbye Banner
send_to(cli->index, String(F(GOODBYE_BANNER_STRING)));
Serial.flush();
delay(1);
cli->client->stop();
}
void client_disconnected(ClientItem *cli)
{
// print notice on console
Serial.print(F("Client #"));
Serial.print(cli->index);
Serial.print(F(" was connected for "));
Serial.print(cli->elapsed() / 1000);
Serial.print(F(" seconds, and has disconnected."));
Serial.flush();
// remove from list of clients
ClientList.drop(cli->index);
(*cli) = (*(ClientItem *)(unsigned long)0);
// print connected count
console_print_connected_count();
// this causes seg fault ??
//delete cli;
}
size_t send_to(int idx, const String &str)
{
return ClientList.send(str, idx);
}
size_t send_to_all(const String &str)
{
return ClientList.send2All(str);
}
Bingo ? Sounds good?
Again, sorry my C++ knowledge is very limited...
HTH