Accettare più connessioni in ingresso su server ethernet

pablos:
Janos io non ti capisco... sono 2 settimane che ti disperi su questo forum. Apri 2 treadh al giorno quasi e +/- chiedi le stesse cose... fanne uno e continua quello. :astonished:

Ora se tu dicessi vorrei fare questo quello e quell'altro con parole tue, sarebbe più facile.
Ho una pagina web vorrei che quando si collega uno blabla, quando si collega un altro bla bla.... Con richieste scritte così non finirai mai il tuo progetto te lo garantisco, fai degli esempi.

Sto creando una libreria per gestire una comunicazione ModbusTCP. Anzi, ne sto creando due, una per gestire la comunicazione ModbusTCP lato server e una per il lato client. Le informazioni che sto chiedendo sono perché cercando a giro non ho trovato risposta, vedi che deve essere messo a HIGH il pin 4 per non far bloccare la shield quando si inserisce una SD (dove altro avrei potuto chiederlo? e per di più, serviva sapere che sto facendo una libreria per modbus per avere una risposta?), oppure che non si riesce a ricevere dati da client diversi sulla stessa porta. E comunque, quando trovo una soluzione da solo l'ho comunque condivisa con il resto del forum (Come utilizzare la ethernet? - #9 by system - Software - Arduino Forum), che anche per questo esempio non era spiegato granché bene come fare, quindi se mi spieghi cosa sto facendo di male per il forum (anzi, se me lo fai dire da un mod) la smetto qui...

P.S. Che post dovrei aprire? "Tutti i problemi di Janos..."? Apro un post per ogni argomento, no?

Tornando in topic, riporto qui la funzione server.available() presa dal file EthernetServer.cpp:

EthernetClient EthernetServer::available()
{
  accept();

  for (int sock = 0; sock < MAX_SOCK_NUM; sock++) {
    EthernetClient client(sock);
    if (EthernetClass::_server_port[sock] == _port &&
        (client.status() == SnSR::ESTABLISHED ||
         client.status() == SnSR::CLOSE_WAIT)) {
      if (client.available()) {
        // XXX: don't always pick the lowest numbered socket.
        return client;
      }
    }
  }

  return EthernetClient(MAX_SOCK_NUM);
}

Se aggiungessi un metodo alla classe per testare il singolo socket invece di cercare fra tutti i socket? Ovvero così:

EthernetClient EthernetServer::available(uint8_t socket)
{
  accept();

  EthernetClient client(socket);
  if (EthernetClass::_server_port[socket] == _port &&
      (client.status() == SnSR::ESTABLISHED ||
       client.status() == SnSR::CLOSE_WAIT)) {
    if (client.available()) {
      // XXX: don't always pick the lowest numbered socket.
      return client;
    }
  }

  return EthernetClient(MAX_SOCK_NUM);
}

Potrebbe funzionare? Purtruppo non ho l'Arduino a disposizione...

FUNZIONA!!!!!!!!!!!!!!!!

A chi potesse interessare, ecco i file modificati. Li ho presi dall'IDE 1.5.2 dalla sezione per AVR.

Il metodo che ho incluso è:

server.available(uint8_t socket)

Con il quale è possibile verificare se un client è collegato ad uno specifico socket. Qui di seguito riporto anche la modifica fatta alla classe EthernetServer:

EthernetClient EthernetServer::available(uint8_t socket)
{
  accept(socket);

  if (socket < MAX_SOCK_NUM) {
    EthernetClient client(socket);
    if (EthernetClass::_server_port[socket] == _port &&
        (client.status() == SnSR::ESTABLISHED ||
         client.status() == SnSR::CLOSE_WAIT)) {
      if (client.available()) {
        // XXX: don't always pick the lowest numbered socket.
        return client;
      }
    }
  }

  return EthernetClient(MAX_SOCK_NUM);
}

Ho dovuto aggiungere anche la funzione privata accept(unit8_t socket), qui di seguito ne riporto il codice:

void EthernetServer::accept(uint8_t socket)
{
  int listening = 0;

  if (socket < MAX_SOCK_NUM) {
    EthernetClient client(socket);

    if (EthernetClass::_server_port[socket] == _port) {
      if (client.status() == SnSR::LISTEN) {
        listening = 1;
      } 
      else if (client.status() == SnSR::CLOSE_WAIT && !client.available()) {
        client.stop();
      }
    } 
  }

  if (!listening) {
    begin();
  }
}

EthernetServer.h (489 Bytes)

EthernetServer.cpp (2.68 KB)

capisco, available ritorna SEMPRE il primo client trovato in stato connesso o in attesa di chiusura di connessione. ciò vuol dire che ritorna sempre il primo client.

Quindi in realtà io farei una cosa differente: una flag in EthernetClient, che di default è a false.

Quando l'available cerca un client, lo cerca con la flag a false, e prima di ritornarlo mette la flag a true. In questo modo NON giochi con i socket lato sketch, la modifica è molto meno evidente (una flag pubblica in EthernetCliente una condizione in più nell'if del server available)

ps. in realtà lui prima di ritornare il client fa

if (client.available()) {

ciò vuol dire che ottenuto un client, prima svuoti il suo buffer di lettura e POI fai un altra available...

quindi SENZA modifiche alle liobrerie, riadatto il mio codice che hai riadattato :slight_smile:

#define NUMERO_CLIENT 4

#include <Ethernet.h>
#include <SPI.h>

EthernetClient client[NUMERO_CLIENT];
EthernetServer server(502);
byte mac[] = { 0x90, 0xA2, 0xDA, 0x0D, 0x97, 0xFB };
IPAddress ip(10,10,10,157);

void setup() {
  pinMode(4, OUTPUT);
  digitalWrite(4, HIGH);
  Ethernet.begin(mac, ip);
  SPI.setClockDivider(SPI_CLOCK_DIV2);
  Serial.begin(115200);
};

void loop(){
EthernetClient temp = server.available();
 for (int i=0;i<NUMERO_CLIENT;i++){
   if ( NULL != client[i] || client[i]){ 
       while (client[i].available()) {
          Serial.print(client[i].read());
       }
       if (!client[i].connected()) client[i] = NULL;
   }else{
       //trovato un posto vuoto, usalo per il nuovo client!
       client[i] = temp;

       //modifica di Lesto
       while (client[i].available()) {
         Serial.print(client[i].read());
       }
       //FINE modifica di Lesto

       temp=server.available(); //accetta l'eventuale prossima connessione
       if (temp) {
         Serial.print("Client ");
         Serial.print(i+1);
         Serial.println(" connesso");
       };
   }
 }
}

però in realtà resta il richio che tra la while che svuota i caratteri e la prossima available() arrivi un carattere e tutto salti di nuovo... bel casino! provo ad aprire un issue

Janos:
Lesto, sei la mia unica speranza.... =( =( =(

Aiutami, Obi Wan Kenobi, sei la nostra unica speranza... :grin: :grin: :grin:

PaoloP:

Janos:
Lesto, sei la mia unica speranza.... =( =( =(

Aiutami, Obi Wan Kenobi, sei la nostra unica speranza... :grin: :grin: :grin:

come to the dark side... we have cookies!

(ho pesantemente editato il mio intervento precedente)

PaoloP:
Aiutami, Obi Wan Kenobi, sei la nostra unica speranza... :grin: :grin: :grin:

:sweat_smile: :sweat_smile: :sweat_smile:

@lesto
Ci avevo pensato anche io ai flag ma così facendo mi tocca modificare di più la libreria, no? Devo settare il flag quando la server.available trova una connessione, ma non mi fidavo tanto su quando resettarlo... Ho visto che non è proprio banale tutta la gestione ed ho voluto essere il meno invasivo possibile...

Dopo opportuno degub sarebbe interessante se gli sviluppatori dell'IDE prendessero in considerazione la mia soluzione o una equivalente, altrimenti è impossibile accettare connessioni da più client...

aperto un issue, ho proposto:

  1. il metodo available + flag
  2. il metodo available + indice del socket da user
  3. il metodo 1. ma in una funzione differente (per non rompere i vecchi codici)

Si si, quello che ho fatto io è un overload della funzione esistete, non ho modificato quella che gia c'è... :wink:

Ma non riesci a fare più connessioni simultanee senza modificare la lib?
Di norma i bus hanno un identificativo per riconoscere chi fa la richiesta e quindi distinguerli.

Perchè questa modifica, a cosa serve ?

Perché la server.available è fatta così:

EthernetClient EthernetServer::available()
{
  accept();

  for (int sock = 0; sock < MAX_SOCK_NUM; sock++) {
    EthernetClient client(sock);
    if (EthernetClass::_server_port[sock] == _port &&
        (client.status() == SnSR::ESTABLISHED ||
         client.status() == SnSR::CLOSE_WAIT)) {
      if (client.available()) {
        // XXX: don't always pick the lowest numbered socket.
        return client;
      }
    }
  }

  return EthernetClient(MAX_SOCK_NUM);
}

Ovvero lui va a vedere su tutti i socket se è disponibile una connessione in ingresso e ritorna un oggetto client che punta alla prima che trova, quindi se ci sono più connessioni in ingresso ti è impossibile gestire la seconda connessione. Se esegui questo codice:

Ethernet client1 = server.available();
Ethernet client2 = server.available();
Ethernet client3 = server.available();
Ethernet client4 = server.available();

In realtà tutti i 4 gli oggetti client lavorano sullo stesso socket...

si ok, ma i clients che si collegano al server si identificano tipo

pippo#messaggio
piero#messaggio
gino#messaggio
ugo#messaggio

non posso smistarli lo stesso sul server?

Mmmm forse ho capito, tu non vuoi tanto separare le connessioni in entrata, ma indirizzare le risposte solo al destinatario, anzichè spedirlo a tutti, separando le connessioni in entrata spedisci solo la risposta a chi ti ha fatto la richiesta .... una cosa così intendi?
Se è così mi sembra che non sia più il principio fondamentale di un bus

Esatto. Se volessi scrivere un messaggio a tutti i client connessi c'è l'apposita funzione server.print() (Ethernet - Arduino Reference). La client.print() (Ethernet - Arduino Reference), invece, risponde solo al client in questione, ma se non c'è un modo per separare i vari client è inutile...

più in particolare usando il seguente codice si ottiene SEMPRE il primo client connesso:

Ethernet client1 = server.available();
Ethernet client2 = server.available();
Ethernet client3 = server.available();
Ethernet client4 = server.available();

per ottenerli tutti, invece, bisogna fare come il seguente codice SPERANDO che in questo momento NESSUNO stia scrivendo (manca il check che clientX sia valido):

Ethernet client1 = server.available();
while(client1.available()){
   client1.read();//svuota il buffer
}
Ethernet client2 = server.available();
while(client2.available()){
   client2.read();//svuota il buffer
}
Ethernet client3 = server.available();
while(client3.available()){
   client3.read();//svuota il buffer
}
Ethernet client4 = server.available();
while(client4.available()){
   client4.read();//svuota il buffer
}

Lesto, ho capito ora cosa intendevi per funzione che controlla il socket successivo... Vedo di buttare giù qualcosa e poi lo pubblico.

EDIT:
Non è così semplice, lascio perdere. Dovrei farmi un array di flag per sapere se una connessione è già attiva, nel qual caso deve essere saltata...

Janos:
Si si, quello che ho fatto io è un overload della funzione esistete, non ho modificato quella che gia c'è... :wink:

come scritto sulla issue questa modifica di scegliere i socket ha il problema dell'overflow dell'array.

Ora che ci penso, in oltre esporrebbe l'utente all'idea di credere che al numero 1 ci sia sempre lo stesso client (che invece, se chiuso, può essere rimpiazzato da un'altro), e infine si crea e distrugge spesso l'oggetto EthernetClient, cosa che non mi piace :slight_smile:

preferisco l'idea di un metodo "getNextClient()" che lavora con la storia dei flag (a livello Socket, a questo punto), che restituisce un client una e una sola volta.

Così l'utente si fa un bell'array di client, quando necessarrio distrugge uno vecchio per fare posto ad uno nuovo, etc..

Infatti se io volessi fare un server che "parla per primo" (es. un server mail deve rispondere per primo con un messaggio HELO) non puoi, perchè se usi la server.print scrivi anche agli altri client che magari sono in altre fasi della comunicazione (e quindi rompoendo lo standard), e la available NON restituirà mail il cliecnt in quanto client.available() è sempre falso!

La faranno gli sviluppatori una getNextClient appropriata, per il mio utilizzo va benissimo la available(socket). Comunque perché dici che c'è il rischio di andare in overflow? Ho messo il controllo:

EthernetClient EthernetServer::available(uint8_t socket)
{
  accept(socket);

  if (socket < MAX_SOCK_NUM) {
    ...
    ...
  }

  return EthernetClient(MAX_SOCK_NUM);
}

EDIT: Forse è meglio mettere anche a accept(socket) dentro l'IF

ok allora direi che va bene

Fatti restituire gli ip di chi ti fa la richiesta e spedisci i pacchetti agli slave sempre tramite ip.

eeeek no!

esempio: per fare i test io farei partire 2 telent dal mio PC. risulato: casino! MAI fidarsi del puro ip, tutta le rete FastWeb + reti aziendali + 99%dei router NAT mostrano l'IP della NAT/Gateway...