Problème Ethernet Shield HTTP

Bonjour à tous,

je dispose d'un Arduino Mega 2560 connecté à un Ethernet Shield.

Je souhaite récupérer quelques informations sur un serveur Web, pour cela je me connecte au serveur avec un client.connect()
puis après l'envoi de la requête GET, je récupère les informations qui m'intéressent avec des client.read().

Seulement voilà, dès la deuxième connexion, en plein milieu de la réception des en-têtes, l'ethernet shield me donne le même caractère en boucle.

Exemple :

HTTP/1.1 200 OK
...
Connection: close
Cacheeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee...

S'agit-il d'un problème de programmation récurrent, ou encore d'un problème avec la librairie Ethernet ?

J'ai tenté de stopper la connexion lors de la réception après un délais trop important, réinitialiser le shield avec un Ethernet.begin et reconnecter pour demander la même page ; le même problème se reproduit sur le même caractère.

Y a-t-il besoin de vider un buffer de réception ?

Merci.

Peut-être que cela peut t'aider :

http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1282763701/all

Merci pour cette réponse rapide.

J'avais déjà regardé ce topic, je suis quasiment sûr de ne pas avoir de problème de mémoire vive car en utilisant une fonction de test de ram disponible, je vois que j'ai à chaque connexion quasiment 5ko.

Normalement je n'ai pas de problème de synchronisation avec le serveur car j'utilise une boucle d'attente de réponse.
Pas de problème non plus de la connexion précédente mal fermée, j'utilise HTTP/1.0 (pas de connexion persistante) et je vérifie à chaque connexion que la connexion précédente est bien fermée.

Enfin en gros je sèche...

voilà le code de ma fonction de connexion qui envoie la requête et lit les en-têtes de manière à laisser l'objet client prêt à lire le contenu de la page :

/* Connexion et envoi requete GET */
/* Retour : true si erreur */
bool connexion(char *adresse_connexion)
{
        Serial.print(" libre : ");
        Serial.println(memoryTest());
        deconnexionClient();
        
        if (client.connect())
        {
                delay(100);
                client.print("GET /");
                client.print(adresse_connexion);
                client.println(" HTTP/1.0");
                client.println();
                
                //attente serveur
                Serial.print("att.");
                unsigned long debut_tentative = millis();
                while (client.available() == 0)
                {
                        delay(10);
                        if (!client.connected())
                        {
                        	erreur(30);
                        	client.stop();
                        	return true;
                        }
			else if ((millis() - debut_tentative) > TIMEOUT)
			{
				erreur(35);
				return true;
			}
                }
                Serial.println("ok");
                
                //passage entetes
                char carac;
                bool clrf = false;
                debut_tentative = millis();
                Serial.println("Entetes...");
                while (true)
                {
                	if ((millis() - debut_tentative) > LONG_TIMEOUT)
			{
				erreur(39);
                                reinitShield();
				return true;
			}
                	
                        if (client.available())
                	{
		        	carac = client.read();
                                Serial.print(carac);
		        	if (carac == '\n')
		        	{
		        		//si deux retours à la ligne de suite lus, entetes passés
		        		if (clrf) break;
		        		else clrf = true;
		        	}
		        	else if (carac != '\r') clrf = false;
                	}
                        else if ((millis() - debut_tentative) > TIMEOUT)
			{
				erreur(36);
				return true;
			}
                	else if (!client.connected())
                	{
                		erreur(30);
                		return true;
                	}
                }
                Serial.println("ok");
                return false;
        }
        else
        {
                erreur(36);
                return true;
        }
}

//s'assurer que la connexion est fermée
void deconnexionClient()
{
        while (client.connected()) client.flush();
        client.stop();
}

//réinitialisation de l'ethernet shield
void reinitShield()
{
        client.stop();
        Serial.println("reinit reseau");
        delay(1000);
        Ethernet.begin(mac, ip, gateway, subnet);
        delay(1000);
        Serial.println("ok");
}

(je numérote les erreurs pour économiser de la mémoire)

Bon, j'ai rajouté des delay() un peu partout, j'ai maintenant 4 ou 5 connexions qui s'effectuent correctement avant de planter et j'ai des erreurs de déconnexion (erreur 30 pendant l'attente du serveur).

Une idée ? je suppose que les délais améliorent les choses pour les tampons de réception mais je ne vais pas mettre un délai de 2 seconde à chaque fois que j'utilise l'objet client...

Salut,

si je peux me permettre, ça me parait bien tordu comme code :~
Il n'y a pas moyen de faire plus simple ? Par exemple en récupérant la réponse dans un buffer, puis seulement après l'analyser pour trouver l'octet de début des données intéressantes.
J'ai beau essayer de comprendre le bout de code que tu nous fournis, ça reste trop obscure.

Qu'as-tu comme serveur en face ?
Peux-tu nous donner un exemple de ce que le serveur renvoi comme data en réponse de la requête GET ? (en utilisant un sniffer de datagramme comme wireshark par exemple)

Gromain

Pour être tordu ça l'est c'est vrai...

À vrai dire c'est le troisième bout de code servant à faire la même chose que j'écris, mais de toute évidence dans chacun de mes codes il y a le même genre d'erreur.

Le serveur auquel j'accède avec la requête GET est un Apache.

Voici une copie des Serial.print lorsque ça marche :

 libre : 4862
att.ok
Entetes...!HTTP/1.1 200 OK
Date: Tue, 14 Jun 2011 12:37:07 GMT
Server: Apache/2.2.17 (Ubuntu)
X-Powered-By: PHP/5.3.5-1ubuntu7.2
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Pragma: no-cache
Vary: Accept-Encoding
Content-Length: 20
Connection: close
Content-Type: text/html

Et en voilà une autre quand ça plante :

 libre : 5057
att.Erreur 30
 libre : 5057
att.Erreur 30
 libre : 5057
att.Erreur 30
 libre : 5057
att.Erreur 30
 libre : 5057
att.Erreur 30
 libre : 5057
att.ok
Entetes...!HTTP/1.1 200 OK
Date: Tue, 14 Jun 2011 12:37:54 GMT
Server: Apache/2.2.17 (Ubuntu)
X-Powered-By: PHP/5.3.5-1ubuntu7.2
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cachee-e-e-e-e-e-e-e-e-e-e-e-e-e-e-e-e-e-e-e-e-e-e-e-e-e-e-e-e-e-e-e-e-(plein de "-e-")Erreur 39
reinit reseau
ok
 libre : 5057
att.ok
Entetes...!HTTP/1.1 200 OK
Date: Tue, 14 Jun 2011 12:38:18 GMT
Server: Apache/2.2.17 (Ubuntu)
X-Powered-By: PHP/5.3.5-1ubuntu7.2
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cachee-e-e-e-e-e-e-e-e-e-e-e-e-e-e-e-e-e-e-e-e-e-e-e...Erreur 39
reinit reseau
ok

Et le résultat est complètement aléatoire (varie entre les erreur de déconnexion et les caractères en boucle, et parfois une requête qui passe correctement) dès que ça a planté.

Sinon je me demande aussi d'où sort le point d'exclamation avant la réponse (!HTTP/1.1 200 OK)

Avec wireshark je vois des belles requetes HTTP telle que

GET /mapage HTTP/1.0
[
ack
continuations or non-http,
parfois TCP port reused
TCP windows update
1028 > http [RST]
]
HTTP/1.1 200 ok (text/html)

Mais quand ça se met à planter le module envoie un peu n'importe quoi :

GET /mapageET /mapageGET /mapage HTTP/1.0\r\n

Et là le serveur renvoie des 400 Bad Request.

Je vais remettre un autre code pour retrouver les erreurs sur des fonctions plus simplement codées...

Je retire ma question pour le point d'exclamation avant le HTTP, j'avais mis le serial.print() avant le client.read() (et en plus j'avais corrigé cette erreur en postant le code dans le forum :roll_eyes:)...

Et je précise que si j'utilise ce système de lecture des données après la fonction connexion c'est pour lire ligne par ligne des pages Web éventuellement grosse sans allouer un buffer de 1000 octets ; cela dit je crois que je vais le faire tout de même puisque j'ai suffisamment de ram disponible.

Après actualisation de la fonction pour enregistrer le contenu de la réponse dans un buffer, j'ai 4 connexions qui se passent sans problème puis j'ai à nouveau le problème de la boucle sur les deux caracères "-e" de l'entete Cache-control (souvent précédé d'une erreur de déconnexion pendant l'attente du serveur).

Avec wireshark, la requête get semble bonne la plupart du temps, mais quelque fois le module envoie des requête erronées (caractères spéciaux qui apparaissent).

La première erreur de boucle est souvent précédée d'un "DUP ACK" de la part de l'aduino.

essaye avec ça :

/*
  Web client
 
 This sketch connects to a website (http://www.google.com)
 using an Arduino Wiznet Ethernet shield. 
 
 Circuit:
 * Ethernet shield attached to pins 10, 11, 12, 13
 
 created 18 Dec 2009
 by David A. Mellis
 
 */


#include <Ethernet.h>
#include <PString.h>

// Enter a MAC address and IP address for your controller below.
// The IP address will be dependent on your local network:
byte mac[] = {  
  0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };
byte ip[] = { 
  192,168,0,177 };
byte server[] = { 217,107,214,2 }; // jfsgeneva.com

// Initialize the Ethernet client library
// with the IP address and port of the server 
// that you want to connect to (port 80 is default for HTTP):
Client client(server, 80);
String stringOne = "";
long echec=0;

void setup() {
  // start the Ethernet connection:
  Ethernet.begin(mac, ip);
  // start the serial library:
  Serial.begin(9600);
  // give the Ethernet shield a second to initialize:
  delay(1000);
  // Serial.println("connecting...");

  // if you get a connection, report back via serial:

}


void loop(){
  

  Serial.println("connecting...");
  if (client.connect()) {
   // client.flush();
   // Serial.flush();
    Serial.println("connected");
    // Make a HTTP request:
    client.print("GET http://monsite.com/test/?turlututu");
    client.println(" HTTP/1.1");
    client.print("Host: monsite.com\n"); 
    client.print("Referer: http://monsite.com\n\n"); 
    client.println();
    delay(200);
  } 
  else if (!client.connected()){
    // if you didn't get a connection to the server:
    Serial.println("connection failed");

  }


  // if there are incoming bytes available 
  // from the server, read them and print them:
  if (client.available()!=0) {
    int b=client.available();
    for (int a=0; a < b+1000;a++){
      char c = client.read();
     // Serial.print(c);
     
     String stringOne += c;
     // Serial.flush();
      

    }
    Serial.println(String stringOne);
   // client.flush();
    Serial.flush();
    //client.stop();
  }

  // if the server's disconnected, stop the client:
  if (!client.connected()) {
    Serial.println();
    Serial.println("disconnecting.");
    Serial.println();
    Serial.println();
    client.flush();
    Serial.flush();
    client.stop();

    // do nothing forevermore:
    // for(;;)
    //   ;
  }
  delay(2000);
}

Bonjour, Jean-Francois je me permet d'ajouter mon grain de sel ^^"
Dans le code que tu fourni il faut ajouter : #include <SPI.h>
Et remplacer :

     String stringOne += c;
    Serial.println(String stringOne);

par :

     stringOne += c;
    Serial.println(stringOne);

Voila c'était mon grain de sel du jour ^^"
Sinon le webclient sans pstring (parce que les strings c'est le mal (enfin uniquement en prog ^^"") ) :

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

byte mac[] = {  0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };
byte ip[] = { 192,168,1,177 };
byte server[] = { 209,85,147,103 }; // Google.com

Client client(server, 80);

void setup() {
  Ethernet.begin(mac, ip);
  Serial.begin(9600);
  delay(1000);
  Serial.println("connecting...");

  if (client.connect()) {
    Serial.println("connected");
    client.println("GET /search?q=arduino HTTP/1.0");
    client.println();
  } 
  else {
    Serial.println("connection failed");
  }
}

void loop()
{
  if (client.available()) {
    char c = client.read();
    Serial.print(c);
  }

  if (!client.connected()) {
    Serial.println();
    Serial.println("disconnecting.");
    client.stop();

    for(;;);
  }

skywodd:
Bonjour, Jean-Francois je me permet d'ajouter mon grain de sel ^^"

Sans soucis ... même pas mal XD

Bonmatin à vous,

j'ai essayé votre code, pas de problème, je l'ai modifié pour récupérer la page en boucle, pas de problème non plus. j'ai donc repris mon code après l'avoir vidé (j'avais 20ko d'autres choses) pour ne laisser que ma fonction de récupération HTTP avec un appel dans loop(), ça fonctionne...

Pourtant dans le reste du code je ne touche pas au réseau, étrange.
Peut-être est-ce le LCD qui est en route ? Mais il ne prend pas de ports utilisées par le shield normalement, il est connecté sur les pins 2, 3, 4, 5, 8 et 9.

Petite précision : si je remet le code qui plante dans l'arduino, que j'attends le premier plantage après 4 ou 5 connexions, et qu'après avoir planté je reprogramme l'arduino avec votre code, cela plante toujours, même avec un reset, il faut que je débranche et rebranche l'usb pour que ça reprenne correctement.

Peut-être est-ce le LCD qui est en route ?

C'est la première fois que tu mentionnes l'utilisation d'un LCD non ? C'est le genre de détails qui peuvent expliquer ton problème pourtant...

cela plante toujours, même avec un reset, il faut que je débranche et rebranche l'usb pour que ça reprenne correctement.

Si tu es obligé de couper c'est qu'il y a certainement corruption de données. Probablement coté wiznet. J'ai le cas lorsque son buffer (circulaire) de réception est saturé. Donc soit tu ne vides pas assez vite/souvent son buffer, soit tu reçoit de trop gros paquets d'un coup. Le buffer de réception est configuré à 2ko par défaut (8 ko partagés entre les 4 sockets disponibles du wiznet).
Vois pour charger des pages plus light, décharger le buffer au plus vite (pas de delay() qui neutralise l'arduino trop longtemps !) ou, en dernier recours, augmenter la taille du buffer au détriment des autres sockets.

Gromain

Bon, même avec le code simplifié je fini par planter (cette fois après quelques minutes de réussite pour une page par seconde tout de même).
Je dois pouvoir laisser l'arduino fonctionner sans avoir à le redémarrer manuellement, si encore je pouvais réinitialiser totalement le W5100 quand ça plante pour que ça reprenne correctement, ça me conviendrait, mais je ne vois pas.

Je viens de regarder du côté de wireshark, les requêtes GET sont toujours erronées lorsque ça plante, mais cela signifie que le buffer d'émission dépasse et pas celui de reception ?

Est-il possible que le buffer de réception déborde sur celui d'émission ? je croyais qu'il s'agissait de buffers circulaires.

Le premier plantage cette fois commence toujours par une déconnexion pendant l'attente de réponse.

les requêtes GET sont toujours erronées lorsque ça plante, mais cela signifie que le buffer d'émission dépasse et pas celui de reception ?

en émission, il peut probable que tu satures le buffer sur un simple GET

Est-il possible que le buffer de réception déborde sur celui d'émission ?

Je dirais que non.

Le premier plantage cette fois commence toujours par une déconnexion pendant l'attente de réponse.

selon moi, et surtout selon TCP/IP, il faut systématiquement faire une connexion avant l'envoi de la requête, puis fermer celle-ci à la fin de la réponse. Si j'ai bien compris, tu ouvres la connexion au niveau du setup, puis tu fais tes requêtes. Ça pourrait expliquer pourquoi ça merdouille quand tu subis une déconnexion ?

Le serveur est chez toi ?

Gromain

Pourtant je fais tout dans la même fonction, ma fonction de récupération HTTP actuelle est conçue selon le schéma suivant :

if (!client.connect()) return erreur;

client.println("GET /magepage?mesvariables HTTP/1.0");
client.println();

while (!client.available())
{
    delay(10);
    if (!client.connected() || test timeout)
    {
         client.stop();
         return erreur;
    }
}

while (client.connected())
{
    if (client.available()) client.read();
    else if (test timeout)
    {
        client.stop();
        return erreur;
    }
}

client.stop();
return ok;

Et oui pour l'instant le serveur est sur mon PC et l'arduino y accède depuis le réseau local.

Me revoici avec mes conclusions si ça peut aider quelqu'un...

J'ai repris la fonction pour faire l'attente de réponse et les lectures dans une seule boucle, comme suit :

while (client.connected())
{
    if (client.available())
    {
        //test si entetes déjà passées, si c'est le cas remplissage du tampon
    }

    if (timeout) erreur;
}

Apparemment cela règle le problème de dépassement lors de la réception.

Et il a fallu également pour résoudre le problème de GET anarchique remplacer :

client.println("GET ... HTTP/1.0");
client.println();

par :

client.print("GET ... HTTP/1.0\r\n\r\n");

Là en revanche je n'ai pas compris pourquoi puisque le println ne fait rien d'autre qu'ajouter "\r\n".

Voilà tout, merci et bonne soirée.