Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

  1. #1
    Membre actif
    Ethernet : client.print() génère plein de petits paquets
    Bonjour,

    J'ai un Arduino qui fonctionne en client web avec le shield Ethernet 2 Arduino officiel et la dernière version de la librairie Ethernet

    Je suis en train de mettre au point mon programme.

    J'ai analysé les échanges entre mon Arduino Client et le serveur avec Wireshark https://www.wireshark.org/

    Dans mon code, j'envoie la requête au serveur avec deux instructions client.print() :

    client.print(Requete);
    client.print(FPSTR(Requete2));

    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    #include <SPI.h>
     
    // DALLE TACTILE ************************************************************************************************************************************************
    // Calibration à faire avec 003_TFT_Touch_Calibrate_OK
    #include "URTouch.h"
    #define t_IRQ 5
    #define t_MISO 7 // La broche 4 est utilisée pour le lecteur SD de l'ethernet Shield
    #define t_MOSI 3
    #define t_CS 2
    #define t_SCK 1
    URTouch ts(t_SCK, t_CS, t_MOSI, t_MISO, t_IRQ);
     
    // ECRAN TFT ****************************************************************************************************************************************************
    #include <PDQ_GFX.h>
    #include "PDQ_ILI9341_config.h" // C'est dans le fichier PDQ_ILI9341_config.h qu'on définit les broches CS, DC et RST
    #include <PDQ_ILI9341.h>  
    PDQ_ILI9341 tft;      
    #define TFT_PWM_LED 6 // Un transistor PNP permet de piloter le rétroéclairage de l'écran TFT
     
    // ETHERNET *****************************************************************************************************************************************************
    #include <Ethernet.h>
    byte mac[] = {
      0xA8, 0x61, 0x0A, 0xAE, 0x75, 0xCA //Voir étiquette collée sous le Shield
    };
    IPAddress ip(192, 168, 123, 177); //Choisir une IP fixe compatible avec le réseau local ou bien utiliser DHCP
    EthernetClient client;
    byte serverIP[] = { 192, 168, 123, 32 }; // Adresse IP du PC Fixe
     
    // VARIABLES GLOBALES *******************************************************************************************************************************************
     const char Requete2[] PROGMEM = " HTTP/1.1\r\nHost:\r\nConnection: close\r\n\r\n";
     #define FPSTR(pstr_pointer) (reinterpret_cast<const __FlashStringHelper *>(pstr_pointer))
     
    char Buffer_DispXY[15];
    char Requete[50];
    char WebPage[300];
     
    // SETUP ********************************************************************************************************************************************************
    void setup() {
      analogWrite(TFT_PWM_LED,128);
     
      tft.begin();
      tft.setRotation(0);
     
      ts.InitTouch(PORTRAIT);
      ts.setPrecision(PREC_EXTREME);
      tft.fillScreen(ILI9341_BLACK);
     
      tft.setTextColor(ILI9341_RED, ILI9341_BLACK);
      tft.setTextSize(2);
     
      // You can use Ethernet.init(CSpin) to configure the CS pin
      //Ethernet.begin(mac, ip, myDns, gateway); //Pas besoin de spécifier le DNS et le GATEWAY si on se contente d'échanger avec un serveur local dont on connait l'IP
      Ethernet.begin(mac, ip);
     
      tft.setCursor(0, 25);
      tft.print(F("PRET  "));
    }
     
    // LOOP *********************************************************************************************************************************************************
    void loop() {
        int retro;
        int x, y;
        int nb;
        char c;
        int i;
        unsigned long debut; //gestion timeout serveur
        unsigned long duree; //mesure durée communication Ethernet
        unsigned long duree2; //mesure durée gestion écran
     
       // On demande à l'écran tactile s'il y a eu un appui :
        if (ts.dataAvailable()) {
          duree2=millis();
          ts.read();
          x = ts.getX();
          y = ts.getY();
     
          if ((x != -1) && (y != -1)) {
            // Le rétroéclairage, que l'on règle en fonction de la position tapée à l'écran tactile :
            retro=y-10;
            if (retro<0) {retro=0;}
            if (retro>255) {retro=255;}
            analogWrite(TFT_PWM_LED,retro);
     
            // Affichage des coordonnées à l'écran :
            snprintf_P(Buffer_DispXY, sizeof(Buffer_DispXY), PSTR("X=%3d Y=%3d"), x, y);
            tft.fillScreen(ILI9341_BLACK);
            tft.setCursor(55, 5);
            tft.print(Buffer_DispXY);
     
            duree=millis();
     
            // Connection au serveur web :
            // client.stop(); // L'arrêt du client prend beaucoup de temps, à faire après
            if (client.connect(serverIP, 80)) {
     
    		  //On fait la requête au serveur, en mettant dans l'URL les coordonnées tapées à l'écran tactile :
              snprintf_P(Requete, sizeof(Requete), PSTR("GET /?X=%d&Y=%d"), x, y);
              client.print(Requete);
              client.print(FPSTR(Requete2));
     
              //Attente de la page web
              debut = millis();
              nb=0;
              while(nb==0){ 
                nb = client.available();
                if (millis()-debut>2000) {break;} //Timeout indispensable sinon on bloque l'Arduino en cas de problème serveur
              }
     
              //Lecture de la page web et affichage
              if (nb==0) {
                tft.setCursor(0, 25);
                tft.print(F("TIMEOUT"));
              } else {
                //On cherche le début de la page, pour zapper la réponse HTTP :
                c=" ";
                while(c!='<'){ 
                  c=client.read();
                  nb--;
                }
                WebPage[0]=c;
                for (i=1;i<=nb;i++) {
                  WebPage[i]=client.read();
                  if (i>298) {break;} // Sécurité pour éviter un buffer overflow
                }
                // La chaine doit se terminer par un octet nul pour utilisation avec tft.print()
                WebPage[i]=0; // (normalement i+1 mais il y un saut de ligne à éliminer)
     
                tft.setCursor(160, 25);
                duree = millis()-duree;
                tft.print(duree); // Affichage du temps pris pour envoyer la requête au serveur et recevoir la réponse
                tft.print(F("ms"));
     
                tft.setCursor(0, 45);
                tft.print(WebPage);
     
                tft.setCursor(0, 25);
                tft.print(F("OK"));
     
                tft.setCursor(80, 25);
                tft.print(millis()-duree2-duree);  // Affichage du temps pris pour gérer l'appui sur l'écran tactile et l'affichage
                tft.print(F("ms"));
              }
            } else {
              tft.setCursor(0, 25);
              tft.print(F("ERREUR"));
            }
            //client.stop(); //L'arrêt du client prend beaucoup de temps (entre 1000ms et 1400ms) et ne sert à rien !
          }
      }
    }


    Problème : en analysant le traffic TCP/IP avec Wireshark, je vois que mon Arduino envoie un premier paquet contenant 16 caractères utiles, et tout le reste sous forme de paquets avec un seul octet.
    C'est de la communication lettre par lettre , sachant que chaque paquet fait 60 octets, le rendement est pas terrible De plus cela oblige le serveur à envoyer plusieurs accusés de réception.

    Le serveur (sur mon PC, créé en VB.NET avec HttpListener) lui envoie sa réponse d'un seul bloc (un paquet de 230 octets environ)

    Curieusement, sur mon Arduino, entre l’envoi de la requête et la réception du dernier octet de la page web envoyé par le serveur il ne se passe que 23 millisecondes, donc c'est quand même rapide

    Mais ce n'est pas normal que la requête soit envoyée de façon si fragmentée.

    Ce que je ne comprend pas c'est que j'ai pris la précaution d'envoyer ma requête avec juste deux instructions client.print(), aussi je ne devrais avoir que deux paquets et pas des dizaines de paquets

    Petite remarque : dans mon programme j'ai commenté l'instruction client.stop(), elle fait perdre entre 1000ms et 1400ms, et elle ne sert à rien.

    A bientôt

  2. #2

  3. #3
    Membre éprouvé
    Citation Envoyé par electroremy Voir le message
    Petite remarque : dans mon programme j'ai commenté l'instruction client.stop(), elle fait perdre entre 1000ms et 1400ms, et elle ne sert à rien.
    Il est important de fermer proprement les sockets UDP. ça envoie un message en SPI au composant pour libérer cette ressource. je ne sais pas comment c'est géré ensuite dans le composant mais vous risquez de vous retrouver à court de socket s'il n'y a pas une sorte de ramasse miette qui décide que cette socket est en fait "morte". De plus la bibliothèque elle même gère le nombre max de sockets dispos (MAX_SOCK_NUM) et donc vous risquez une famine sans doute sur cette ressource (il y a une demande d'amélioration postée "Automatically reclaim abandoned sockets" ouverte depuis 2 ans...)

    si vous regardez les constructeurs de la classe EthernetClient, on peut préciser en paramètre un time out qui est par défaut mis à 1s (1000ms).

    Comme la résolution est en ms, vous ne pourrez pas descendre en dessous de 1ms même en passant un paramètre plus petit et en plus - Pour le délai que cela génère - c'est un raccourci malheureux de programmation qu'ils ont effectué...

    Si vous regardez le code source vous verrez en ligne 137 qu'ils appellent Ethernet.socketDisconnect() mais si la fermeture de la socket UDP n'est pas instantanée (SnSR::CLOSED n'est pas encore valide) alors le do-while qui suit inclut (bêtement) un delay(1) --> d'où le comportement que vous voyez.

    Si ce timing d'une milliseconde vous gêne - vous pourriez remplacer dans la bibliothèque ce delay(1) par un delayMicrosecond(10) de façon à interroger le registre plus fréquemment et ne pas être bloqué toute une milliseconde. Vous pourriez même essayer de le virer complètement et le remplacer juste par un yield() mais je ne sais pas si le composant sous jacent est robuste à un bombardement permanent de requêtes SPI

    Ce que je ne comprend pas c'est que j'ai pris la précaution d'envoyer ma requête avec juste deux instructions client.print(), aussi je ne devrais avoir que deux paquets et pas des dizaines de paquets
    Oui si vous regardez le code ils implémentent l'interface Print en fournissant simplement la méthode virtuelle write(). ça a l'avantage de la simplicité mais ce que fait print avec une chaine en mémoire flash, alors c'est de morceler le message en octets et de les envoyer un par un avec write alors que les autres fonctions passent un buffer et une longueur à write() par exemple si vous aviez une instance de la classe String en paramètre ça prendrait cette méthode
    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    3
    4
     size_t Print::print(const String &s)
    {
      return write(s.c_str(), s.length());
    }

  4. #4
    Membre actif
    Bonjour,

    D'abord merci pour vos réponses.

    J'ai fait la modif pour utiliser un seul buffer en RAM pour la requête

    Il n'y a plus qu'un seul paquet pour la requête, et le temps d’exécution passe de 22ms à 11ms

    En revanche j'ai essayé de mettre 1 comme timeout dans le constructeur de EthernetClient, et de réactiver client.stop()

    Catastrophe, client.stop me passe le temps d’exécution à 1012ms (donc une seconde de pause)

    Du coup je me demande si le paramètre timeout dans le constructeur est-il en ms ou en secondes ?

    Voici le code :

    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
     
    #include <SPI.h>
     
    // DALLE TACTILE ************************************************************************************************************************************************
    // Calibration à faire avec 003_TFT_Touch_Calibrate_OK
    #include "URTouch.h"
    #define t_IRQ 5
    #define t_MISO 7 // La broche 4 est utilisée pour le lecteur SD de l'ethernet Shield
    #define t_MOSI 3
    #define t_CS 2
    #define t_SCK 1
    URTouch ts(t_SCK, t_CS, t_MOSI, t_MISO, t_IRQ);
     
    // ECRAN TFT ****************************************************************************************************************************************************
    #include <PDQ_GFX.h>
    #include "PDQ_ILI9341_config.h" // C'est dans le fichier PDQ_ILI9341_config.h qu'on définit les broches CS, DC et RST :
    //#define ILI9341_CS_PIN    0   // <= /CS pin (chip-select, LOW to get attention of ILI9341, HIGH and it ignores SPI bus)
    //#define ILI9341_DC_PIN    9   // <= DC pin (1=data or 0=command indicator line) also called RS
    //#define ILI9341_RST_PIN   8   // <= RST pin (optional)
    #include <PDQ_ILI9341.h>      // PDQ: Hardware-specific driver library
    PDQ_ILI9341 tft;      
    #define TFT_PWM_LED 6 // Un transistor PNP permet de piloter le rétroéclairage 
     
    // ETHERNET *****************************************************************************************************************************************************
    #include <Ethernet.h>
    // Ethernet Shield 2 : communicates with both the W5500 and SD card using the SPI bus (through the ICSP header).
    // This is on digital pins 10, 11, 12, and 13 on the Uno and pins 50, 51, and 52 on the Mega.
    // On both boards, pin 10 is used to select the W5500 and pin 4 for the SD card. These pins cannot be used for general I/O.
    // On the Mega, the hardware SS pin, 53, is not used to select either the W5500 or the SD card, but it must be kept as an output or the SPI interface won't work.
    byte mac[] = {
      0xA8, 0x61, 0x0A, 0xAE, 0x75, 0xCA //Voir étiquette collée sous le Shield
    };
    IPAddress ip(192, 168, 123, 177); //Choisir une IP fixe compatible avec le réseau local ou bien utiliser DHCP
    //IPAddress myDns(212, 27, 40, 240);
    //IPAddress myDns(192, 168, 123, 254); //DNS ou adresse du routeur  - Indispensable si IP Fixe (sans DHCP) et si on souhaite aller sur INTERNET (mais pas en local)
    //IPAddress gateway(192, 168, 123, 254); //Adresse du routeur - Indispensable si IP Fixe (sans DHCP) et si on souhaite aller sur INTERNET (mais pas en local)
    EthernetClient client(1);
    //char server[] = "www.google.com"; 
    byte serverIP[] = { 192, 168, 123, 32 }; // Adresse IP du PC Fixe
     
    // To open a port in the Windows 7 Firewall
    // On the Start menu, click Control Panel
    // Click on the System and Security category, then Windows Firewall. If you are not viewing by category, you can simply click on the Window Firewall item.
    // Click on the Advanced Settings link on the left hand side. This will open the Windows Firewall with Advanced Security Management Console application.
    // Click on Inbound Rules on the left hand menu. Under the Actions on the right hand side, click on New Rule. This opens the New Inbound Rule Wizard.
    // Select the Port option and click Next.
    // Select TCP and Specific local ports (80), then enter a comma-separated list of port numbers you want to open and click Next.
    // Select Allow the connection and click Next.
    // Check the boxes next to the desired network types and click Next.
    // Enter a name for your rule - and optionally a description - and click Finish.
    // Your inbound rule should now be displayed on the Inbound Rules list. To remove this rule, simply delete it from the list when you are done.
     
    // VARIABLES GLOBALES *******************************************************************************************************************************************
    // const static char Requete2[] PROGMEM = " HTTP/1.1\r\nHost:\r\nConnection: close\r\n\r\n";
    // const char Requete2[] PROGMEM = " HTTP/1.1\r\nHost:\r\nConnection: close\r\n\r\n";
    // #define FPSTR(pstr_pointer) (reinterpret_cast<const __FlashStringHelper *>(pstr_pointer))
     
    char Buffer_DispXY[15];
    char Requete[200];
    char WebPage[300];
     
     
     
    //int compteur;
     
    // SETUP ********************************************************************************************************************************************************
    void setup() {
      //compteur=-1;
      analogWrite(TFT_PWM_LED,128);
     
      tft.begin();
      tft.setRotation(0);
     
      ts.InitTouch(PORTRAIT);
      ts.setPrecision(PREC_EXTREME);
      tft.fillScreen(ILI9341_BLACK);
     
      tft.setTextColor(ILI9341_RED, ILI9341_BLACK);
      tft.setTextSize(2);
     
      // You can use Ethernet.init(CSpin) to configure the CS pin
      //Ethernet.begin(mac, ip, myDns, gateway);
      Ethernet.begin(mac, ip);
     
      tft.setCursor(0, 25);
      tft.print(F("PRET  "));
    }
     
    // LOOP *********************************************************************************************************************************************************
    void loop() {
        int retro;
        int x, y;
        int nb;
        char c;
        int i;
        unsigned long debut;
        unsigned long duree;
        unsigned long duree2;
     
       // On demande à l'écran tactile s'il y a eu un appui :
        if (ts.dataAvailable()) {
          duree2=millis();
          ts.read();
          x = ts.getX();
          y = ts.getY();
     
          if ((x != -1) && (y != -1)) {
            // Le rétroéclairage :
            retro=y-10;
            if (retro<0) {retro=0;}
            if (retro>255) {retro=255;}
            analogWrite(TFT_PWM_LED,retro);
     
            // Les coordonnées :
            snprintf_P(Buffer_DispXY, sizeof(Buffer_DispXY), PSTR("X=%3d Y=%3d"), x, y);
            tft.fillScreen(ILI9341_BLACK);
            tft.setCursor(55, 5);
            tft.print(Buffer_DispXY);
     
            duree=millis();
            // Connection au serveur web :
            client.stop();
            //if (client.connect(server, 80)) {
            if (client.connect(serverIP, 80)) {
     
              snprintf_P(Requete, sizeof(Requete), PSTR("GET /?X=%d&Y=%d HTTP/1.1\r\nHost:\r\nConnection: close\r\n\r\n"), x, y);
              client.print(Requete);
     
              //Attente de la page web
              debut = millis();
              nb=0;
              while(nb==0){ 
                nb = client.available();
                if (millis()-debut>2000) {break;}
              }
     
              //Lecture de la page web :
              if (nb==0) {
                tft.setCursor(0, 25);
                tft.print(F("TIMEOUT"));
              } else {
                //Il faut analyser la réponse pour voir si elle est bonne
                //Idée : convenir d'un "code" de début et de fin de données utiles avec le serveur
                //On ne commencer à stocker la WebPage que si on a le code de début
                //On valide la réponse si on a le code de fin (on évite ainsi les messages tronqués)
     
                //On élimine l'entête :
                c=" ";
                while(c!='<'){ 
                  c=client.read();
                  nb--;
                }
                WebPage[0]=c;
                for (i=1;i<=nb;i++) {
                  WebPage[i]=client.read();
                  if (i>298) {break;} // Sécurité pour éviter un buffer overflow
                }
                // La chaine doit se terminer par un octet nul pour utilisation avec tft.print()
                WebPage[i]=0; // (normalement i+1 mais il y un saut de ligne à éliminer)
     
    /*
                //Lecture de la page avec l'entête HTTP :
                for (i=0;i<nb;i++) {
                  WebPage[i]=client.read();
                  if (i>298) {break;}
                }
                WebPage[nb]=0; // La chaine doit se terminer par un octet nul pour utilisation avec tft.print()
    */
     
                tft.setCursor(160, 25);
                duree = millis()-duree;
                tft.print(duree); // Temps pris pour envoyer la requête au serveur et recevoir la réponse
                tft.print(F("ms"));
     
                tft.setCursor(0, 45);
                tft.print(WebPage);
     
                tft.setCursor(0, 25);
                tft.print(F("OK"));
     
                tft.setCursor(80, 25);
                tft.print(millis()-duree2-duree);  // Temps pris pour gérer l'appui sur l'écran tactile et l'affichage
                tft.print(F("ms"));
              }
            } else {
              tft.setCursor(0, 25);
              tft.print(F("ERREUR"));
            }
          }
      }
    }


    Petite remarque : au sujet de la nécessité de client.stop(), vous m'avez parlé d'UDP, or je travaille avec un client/serveur web en HTTP, donc j'utilise TCP, Wireshark le confirme.

    J'ai de nouveau supprimé l'instruction stop, et tapoter frénétiquement sur l'écran pour faire plusieurs dizaines de requêtes à la suite, je ne constate aucun plantage.
    Quelques remarques :
    - du fait du temps d’exécution de l'affichage, je n'arrive pas à dépasser 3 requêtes par seconde
    - dans les headers de la requête et dans la réponse du serveur, il y a "connection:close"

    J'ai refait un essai en supprimant les fonctions d'affichage qui prennent du temps (l’effacement complet avec fillScreen et l'affichage de la page web reçue)
    Le temps d’exécution total baisse à 60 ms ; en baladant le stylet sur l'écran tactile je peux faire beaucoup plus de requêtes ... j'arrive à avoir une erreur de temps en temps.

    A bientôt

  5. #5
    Membre actif
    Dans la référence le constructeur n'admet aucun paramètre :

    https://www.arduino.cc/en/Reference/EthernetClient

    En revanche, il y a la fonction client.SetConnectionTimeout mais elle contrôle à la fois le timeout de .connect() et de .stop()

    https://www.arduino.cc/en/Reference/...nectionTimeout

  6. #6
    Membre éprouvé
    Citation Envoyé par electroremy Voir le message
    Dans la référence le constructeur n'admet aucun paramètre :
    https://www.arduino.cc/en/Reference/EthernetClient
    Oui mais le code lui même dit le contraire...
    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    EthernetClient(uint8_t s) : sockindex(s), _timeout(1000) { }
    Cela dit en y regardant de plus près, le paramètre n'est pas pour le temps, c'est le nombre de sockets et 1000 est utilisé comme valeur par défaut pour le timeout.
    ==> donc en ne fonctionnant qu'avec 1 seul socket, il faut attendre qu'il soit libéré, il doit y avoir des contentions qui font que ça allonge les délais


    Petite remarque : au sujet de la nécessité de client.stop(), vous m'avez parlé d'UDP, or je travaille avec un client/serveur web en HTTP, donc j'utilise TCP, Wireshark le confirme.

    Bon point sur UDP versus TCP. J'ai tapé trop vite, c'est bien TCP dans votre cas. mea culpa.

    Je mentionnais cela parce que si vous regardez le code source de stop() pour le client ethernet ils ferment la socket sous jacente (avec Ethernet.socketDisconnect(sockindex);) avant d'attendre que son drapeau confirme la bonne fermeture ou un timeout
    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    	Ethernet.socketDisconnect(sockindex);
    	unsigned long start = millis();
     
    	// wait up to a second for the connection to close
    	do {
    		if (Ethernet.socketStatus(sockindex) == SnSR::CLOSED) {
    			sockindex = MAX_SOCK_NUM;
    			return; // exit the loop
    		}
    		delay(1);
    	} while (millis() - start < _timeout);
    et s'il y a eu timeout ils forcent la fermeture brutalement
    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    	// if it hasn't closed, close it forcefully
    	Ethernet.socketClose(sockindex);


    et si vous allez voir le code pour socketDisconnect()
    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    // Gracefully disconnect a TCP connection.
    //
    void EthernetClass::socketDisconnect(uint8_t s)
    {
    	SPI.beginTransaction(SPI_ETHERNET_SETTINGS);
    	W5100.execCmdSn(s, Sock_DISCON);
    	SPI.endTransaction();
    }
    il s'agit bien de terminer proprement une connexion TCP


    J'ai de nouveau supprimé l'instruction stop, et tapoter frénétiquement sur l'écran pour faire plusieurs dizaines de requêtes à la suite, je ne constate aucun plantage.
    Quelques remarques :
    - du fait du temps d’exécution de l'affichage, je n'arrive pas à dépasser 3 requêtes par seconde
    - dans les headers de la requête et dans la réponse du serveur, il y a "connection:close"

    J'ai refait un essai en supprimant les fonctions d'affichage qui prennent du temps (l’effacement complet avec fillScreen et l'affichage de la page web reçue)
    Le temps d’exécution total baisse à 60 ms ; en baladant le stylet sur l'écran tactile je peux faire beaucoup plus de requêtes ... j'arrive à avoir une erreur de temps en temps.
    intéressant comme test. Le "connection:close" dit simplement au client que le server va terminer la connexion après avoir envoyé la réponse mais s'il ne le fait pas ça ne va pas se faire tout seul du côté Arduino.
    Le navigateur cependant fermera de son côté,ça fera une "half close" de la connexion TCP et la stack côté arduino sera notifiée. ça peut mettre un peu le bazar dans des buffers en cas de pipelining ou multiple connexion mais pas dans les cas courant côté Arduino. Donc ça devrait bien libérer les resources au bout d'un moment. c'est sans doute ce que vous voyez.

  7. #7
    Membre actif
    Bonjour,

    Toutes vos explications tiennent la route, je ne les remets pas en cause.

    Mais, en pratique, mes tests auraient du faire planter le shield et/ou l'Arduino et ce n'est pas le cas... pourquoi ?

    Il faudrait voir le firmware du Shield Ethernet et aussi celui de sa puce W5500

    Comment se débarrasser de la pause d'une seconde générée par client.stop(), tout en ayant un programme respectueux ?
    Un compromis serait de forcer Ethernet.socketClose() tout de suite ?
    Ce n'est pas si brutal que ça car :
    - dans mon programme je prends la précaution d’attendre la page et de la télécharger entièrement;
    - le serveur s'attend à ce que le client se ferme après avoir reçu la page
    Quelle est la longueur maxi d'un packet TCP ? Je risque d'envoyer un page d'au plus 1 Ko (je suis avant tout limité par la quantité de RAM de l'Arduino)
    Mais il n'est pas impossible que j'utiliser des Arduinos Mega si au cours de mon développement je souhaite envoyer plus de données, mais en tout ca ça dépasserait pas quelques ko.
    En effet, je me demande s'il ne vaut pas mieux que mes arduinos clients soient des "terminaux écran TFT/ethernet" et que tout le traitement spécifique à mon projet soit dans l'Arduino serveur, ce cette façon je n'ai qu'à mettre à jour l'Arduino serveur en cas de nouvelles fonctionnalités
    Si le serveur est obligé d'envoyer la page en plusieurs paquets y a-t-il un risque que j'en manque un ?
    Il faut également voir si la fermeture brutale est "propre" vis à vis de la RAM et des ressources du Shield et de l'Arduino.

    Nos amis anglophones suggèrent de ne pas fermer le client et de garder toujours le même.
    Ce n'est pas une bonne idée car en pratique, mon projet final aura un client qui va enchainer les sollicitations (frappes sur l'écran tactile) puis ensuite plus rien du tout pendant un certain temps...

    A bientôt

  8. #8
    Membre éprouvé
    Mais, en pratique, mes tests auraient du faire planter le shield et/ou l'Arduino et ce n'est pas le cas... pourquoi ?
    Je ne sais pas - il y a sans doute "un peu de magie noire" dans la puce (réutilisation de sockets en fonction de l'origine/destination, timeout d'où certains délais, ...). Faudrait lire la spec du composant et ce que font les commandes SPI, j’ai jamais eu l’opportunité de creuser cela

    Pour tester aussi plus en détail il faudrait avoir de multiples requêtes en provenance / à destination de différents hôtes

    Quelle est la longueur maxi d'un packet TCP ?
    Un datagramme IP peut avoir une taille maximale de 65535 octets, mais c'est théorique et trop grand pour la plupart des réseaux.
    Il y a la notion de MTU (Maximum Transfert Unit) qui correspond à la taille maximale des données transportables par le réseau et dans ce cas le datagramme IP (headers compris ~ 20 octets TCP + 20 octets IP) aura comme taille maximale le MTU d'un réseau. Pour ethernet le MTU est de 1500 octets - souvent fixé à 1492 pour optimiser sa connexion (Plus grande valeur possible applicable à PPPoE) ou 1496

    Si un routeur sur le chemin pris par les données doit passer d'un réseau X vers un réseau Y et si les 2 réseaux ont des valeurs différentes de MTU (FDDI a un MTU de 4470) alors le routeur découpe le datagramme en sous datagrammes qui seront rassemblés plus tard.

    Donc la notion d'assemblage "de morceaux" n'est pas un truc hors de l'ordinaire.


    Si le serveur est obligé d'envoyer la page en plusieurs paquets y a-t-il un risque que j'en manque un ?
    le serveur ne s'en soucie pas. C'est géré par le protocole - on écrit autant de données qu'on veut dans la socket, elle s'occupera de les découper en paquets TCP qui seront émis, réceptionnés, rassemblés et réordonnés de l'autre côté.
    La perte d'un segment est gérée par TCP en utilisant un mécanisme de temporisation et de retransmission.
    Après l'envoi d'un segment, TCP va attendre un certain temps la réception du ACK correspondant avant de re-émettre ==> c'est ce qui crée une partie des délais que vous constatez quand vous envoyez des dizaines de paquets (caractère par caractère), il faudra attendre cet ACK pour chacun des caractères versus un seul ACK pour tout un paquet et si le temps retenu est trop court, ça entraîne un grand nombre de retransmissions inutiles mais un temps trop long ralentit la réaction en cas de perte d'un segment.
    il y a une notion de Round-trip delay time (RTT) moyen qui rentre en ligne de compte pour gérer cela (Comme cette valeur peut varier dans le temps, des échantillons sont prélevés à intervalle régulier et le module en calcule une moyenne pondérée plus une marge de sécurité). vous pouvez lire des trucs sur l'Algorithme de Karn par exemple si ça vous intéresse

    Nos amis anglophones suggèrent de ne pas fermer le client et de garder toujours le même.
    A mon avis c'est généralement une mauvais idée car bien souvent dans ce cas vous ne prendrez pas en compte des déconnexions possibles (vous penserez que le client est là pour toujours) et la reprise après panne ne se fera pas. Pour un test sur un coin de table pourquoi pas, pour un truc amener à tourner des mois c'est plus ennuyeux

  9. #9
    Membre actif
    C'est amusant, tout ce qui est réseau et systèmes ne m'intéressait pas du tout lorsque j'étais étudiant, alors que tout ce qui touche à l'électronique et à l'informatique me passionnait et me passionne toujours.

    Il aura fallu que je me lance dans ce projet domotique à base d'Arduino et de lecteur MP3 Raspberry Pi pour que je m'intéresse sérieusement à ce sujet !

    Bizarre cette histoire de client.stop... Faudra que je fasse une demande sur GitHub pour avoir un client.stop() rapide et propre.

    Citation Envoyé par Jay M Voir le message

    le serveur ne s'en soucie pas. C'est géré par le protocole - on écrit autant de données qu'on veut dans la socket, elle s'occupera de les découper en paquets TCP qui seront émis, réceptionnés, rassemblés et réordonnés de l'autre côté.
    Je sais que l'expéditeur (le serveur) ne se préoccupe pas forcément que la page soit reçue.

    Ma question concernait le client.

    Y a t il un risque que le client puisse recevoir une page web incomplète sans le savoir ?

    Afficher des pages incomplètes, les navigateurs web le font régulièrement mais je sais déjà que les navigateurs sont très tolérants aux erreurs (ce qui est pénible quand on cherche à créer un serveur et/ou un site web avec un comportement conforme)

    Ou alors le protocole gère cela correctement ?

    De toute façon, pour mes échanges je compte ajouter "ma couche".

    Le code HTML et les requêtes que les Arduinos vont s'échanger entre eux sera en fait une liste de caractères ayant une signification précise.
    Il y aura un caractère de début, la longueur, et enfin un CRC sur un octet.
    Ce type de message sera compact et efficace : genre un octet pour définir l'instruction à exécuter, puis ensuite les données, et ensuite une autre instruction... un peu sur le modèle du code Assembleur, j'aurais 256 instructions possibles.
    Et je pourrais vérifier par moi même qu'un message est complet et sans erreur.

    Uniquement pour l'interface avec un PC ou un smartphone, le serveur Arduino fera l'effort de lire des requêtes standard et d'envoyer en retour des pages web "normales".

    A bientôt

  10. #10
    Membre éprouvé
    Cherchez “TCP packet loss” c’est trop long à décrire ici. Mais en gros TCP est fait pour ne pas en perdre mais si une ligne a une mauvaise qualité de service alors au bout d’un moment le client ou le serveur abandonnera

    Y a t il un risque que le client puisse recevoir une page web incomplète sans le savoir ?
    La encore il y a bcp de lecture sur le protocole HTTP et ses évolutions.

    C’est très structuré comme protocole. Le header se termine par une ligne vide donc si vous la recevez vous savez que le header est complet. Ensuite Si une requête ou une réponse contient un message, il est nécessaire pour le destinataire d'en connaître la taille afin d'identifier la fin du message HTTP. C’est ce à quoi sert l’en-tête Content-Length (taille en octets du message).

    C’est nécessaire car le mécanisme HTTP du pipelining permet de soumettre plusieurs requêtes (et donc de recevoir plusieurs réponse) sur une même connexion TCP et donc il faut être capable de délimiter les requêtes d'une part et les réponses d'autre part.

    Il est cependant possible de ne pas fournir l'en-tête Content-Length en utilisant le «*chunked transfer encoding*» - en gros on balance des paquets dont on fournit la taille et le transfert se termine par un bloc final au contenu nul. C’est ce qui permet à l’autre partie de savoir qu’il a bien reçu tout.

    De toute façon, pour mes échanges je compte ajouter "ma couche".

    Le code HTML et les requêtes que les Arduinos vont s'échanger entre eux sera en fait une liste de caractères ayant une signification précise.
    Il y aura un caractère de début, la longueur, et enfin un CRC sur un octet.
    ce n’est pas vraiment nécessaire. Vous pouvez aussi juste faire une requête de type POST avec un JSON ou similaire. Il n’y a pas besoin d’envoyer de page HTML à proprement parler.

    Mais vous pouvez aussi vous passer de HTTP et juste utiliser les sockets ou utiliser la classe EthernetServer en passant le No du port sur lequel vous écoutez
    Code :Sélectionner tout -Visualiser dans une fenêtre à part
     EthernetServer server = EthernetServer(NumPort);
    et EthernetClient de l’autre côté

  11. #11
    Membre actif
    Citation Envoyé par Jay M Voir le message
    Mais vous pouvez aussi vous passer de HTTP et juste utiliser les sockets ou utiliser la classe EthernetServer en passant le No du port sur lequel vous écoutez
    Code :Sélectionner tout -Visualiser dans une fenêtre à part
     EthernetServer server = EthernetServer(NumPort);
    et EthernetClient de l’autre côté
    Bonjour,

    je crois que je procéde déjà ainsi

    voici le code de test d'un Arduino Sever :

    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    #include <SPI.h>
     
    // DALLE TACTILE ************************************************************************************************************************************************
    // Calibration à faire avec 003_TFT_Touch_Calibrate_OK
    #include "URTouch.h"
    #define t_IRQ 5
    #define t_MISO 7 // La broche 4 est utilisée pour le lecteur SD de l'ethernet Shield
    #define t_MOSI 3
    #define t_CS 2
    #define t_SCK 1
    URTouch ts(t_SCK, t_CS, t_MOSI, t_MISO, t_IRQ);
     
    // ECRAN TFT ****************************************************************************************************************************************************
    #include <PDQ_GFX.h>
    #include "PDQ_ILI9341_config.h" // C'est dans le fichier PDQ_ILI9341_config.h qu'on définit les broches CS, DC et RST :
    //#define ILI9341_CS_PIN    0   // <= /CS pin (chip-select, LOW to get attention of ILI9341, HIGH and it ignores SPI bus)
    //#define ILI9341_DC_PIN    9   // <= DC pin (1=data or 0=command indicator line) also called RS
    //#define ILI9341_RST_PIN   8   // <= RST pin (optional)
    #include <PDQ_ILI9341.h>      // PDQ: Hardware-specific driver library
    PDQ_ILI9341 tft;      
    #define TFT_PWM_LED 6 // Un transistor PNP permet de piloter le rétroéclairage 
     
    // ETHERNET *****************************************************************************************************************************************************
    #include <Ethernet.h>
    // Ethernet Shield 2 : communicates with both the W5500 and SD card using the SPI bus (through the ICSP header).
    // This is on digital pins 10, 11, 12, and 13 on the Uno and pins 50, 51, and 52 on the Mega.
    // On both boards, pin 10 is used to select the W5500 and pin 4 for the SD card. These pins cannot be used for general I/O.
    // On the Mega, the hardware SS pin, 53, is not used to select either the W5500 or the SD card, but it must be kept as an output or the SPI interface won't work.
    byte mac[] = {
      0xA8, 0x61, 0x0A, 0xAE, 0x75, 0xCA //Voir étiquette collée sous le Shield
    };
    IPAddress ip(192, 168, 123, 177); //Choisir une IP fixe compatible avec le réseau local ou bien utiliser DHCP
    EthernetServer server(80);
     
    // VARIABLES GLOBALES *******************************************************************************************************************************************
    char Buffer_DispXY[15];
    int sensorReading;
    int dernierx;
    int derniery;
     
    // SETUP ********************************************************************************************************************************************************
    void setup() {
      int radius = 4;
      dernierx=-1;
      derniery=-1;
     
      analogWrite(TFT_PWM_LED,128);
     
      tft.begin();
      tft.setRotation(0);
     
      ts.InitTouch(PORTRAIT);
      ts.setPrecision(PREC_EXTREME);
      tft.fillScreen(ILI9341_BLACK);
     
      tft.setTextColor(ILI9341_RED, ILI9341_BLACK);
      tft.setTextSize(2);
     
      tft.fillCircle(10, 10, radius, ILI9341_YELLOW);
      tft.fillCircle(10, 310, radius, ILI9341_YELLOW);
      tft.fillCircle(230, 310, radius, ILI9341_YELLOW);
      tft.fillCircle(230, 10, radius, ILI9341_YELLOW);
      tft.fillCircle(120, 160, radius, ILI9341_YELLOW);
     
      sensorReading = 555;
      snprintf_P(Buffer_DispXY, sizeof(Buffer_DispXY), PSTR("A = %d"), sensorReading);
      tft.setCursor(55, 5);
      tft.print(Buffer_DispXY);
     
      // You can use Ethernet.init(pin) to configure the CS pin
      //Ethernet.init(10);  // Most Arduino shields
      //Ethernet.init(5);   // MKR ETH shield
      //Ethernet.init(0);   // Teensy 2.0
      //Ethernet.init(20);  // Teensy++ 2.0
      //Ethernet.init(15);  // ESP8266 with Adafruit Featherwing Ethernet
      //Ethernet.init(33);  // ESP32 with Adafruit Featherwing Ethernet
     
      // start the Ethernet connection and the server:
      Ethernet.begin(mac, ip);
      // Check for Ethernet hardware present
      if (Ethernet.hardwareStatus() == EthernetNoHardware) {
        //Serial.println("Ethernet shield was not found.  Sorry, can't run without hardware. <img src="images/smilies/icon_sad.gif" border="0" alt="" title=":(" class="inlineimg" />");
        while (true) {
          delay(1); // do nothing, no point running without Ethernet hardware
        }
      }
      if (Ethernet.linkStatus() == LinkOFF) {
        //Serial.println("Ethernet cable is not connected.");
      }
      // start the server
      server.begin();
    }
     
    // LOOP *********************************************************************************************************************************************************
    void loop() {
        int retro;
        int x, y;
     
      // listen for incoming clients
      EthernetClient client = server.available();
      if (client) {
        //Serial.println("new client");
        // an http request ends with a blank line
        boolean currentLineIsBlank = true;
        while (client.connected()) {
          if (client.available()) {
            char c = client.read();
            //Serial.write(c);
            // if you've gotten to the end of the line (received a newline
            // character) and the line is blank, the http request has ended,
            // so you can send a reply
            if (c == '\n' && currentLineIsBlank) {
              // send a standard http response header
              client.println(F("HTTP/1.1 200 OK"));
              client.println(F("Content-Type: text/html"));
              client.println(F("Connection: close"));  // the connection will be closed after completion of the response
              client.println(F("Refresh: 5"));  // refresh the page automatically every 5 sec
              client.println();
              client.println(F("<!DOCTYPE HTML>"));
              client.println(F("<html>"));
              // output the value of each analog input pin
              for (int analogChannel = 0; analogChannel < 6; analogChannel++) {
                sensorReading = analogRead(analogChannel);
                client.print(F("analog input "));
                client.print(analogChannel);
                client.print(F(" is "));
                client.print(sensorReading);
                client.println(F("<br />"));
              }
              client.print(F("TFT X="));
              client.print(dernierx);
              client.print(F(" Y="));
              client.print(derniery);
              client.println(F("</html>"));
              break;
            }
            if (c == '\n') {
              // you're starting a new line
              currentLineIsBlank = true;
            } else if (c != '\r') {
              // you've gotten a character on the current line
              currentLineIsBlank = false;
            }
          }
        }
        // give the web browser time to receive the data
        delay(1);
        // close the connection:
        client.stop();
        //Serial.println("client disconnected");
      }
     
        while (ts.dataAvailable())
      {
        ts.read();
        x = ts.getX();
        y = ts.getY();
     
        if ((x != -1) && (y != -1))
        {
          dernierx=x;
          derniery=y;
          snprintf_P(Buffer_DispXY, sizeof(Buffer_DispXY), PSTR("X=%3d Y=%3d"), x, y);
          //delay(10);
          tft.setCursor(55, 5);
          tft.print(Buffer_DispXY);
          tft.setCursor(55, 25);
          tft.print(Buffer_DispXY);
          tft.setCursor(55, 45);
          tft.print(Buffer_DispXY);
          tft.setCursor(55, 65);
          tft.print(Buffer_DispXY);
          tft.setCursor(55, 85);
          tft.print(Buffer_DispXY);
          tft.setCursor(55, 105);
          tft.print(Buffer_DispXY);
          tft.setCursor(55, 125);
          tft.print(Buffer_DispXY);
          tft.setCursor(55, 145);
          tft.print(Buffer_DispXY);
          tft.setCursor(55, 165);
          tft.print(Buffer_DispXY);
          tft.setCursor(55, 185);
          tft.print(Buffer_DispXY);
          tft.setCursor(55, 205);
          tft.print(Buffer_DispXY);
          tft.setCursor(55, 225);
          tft.print(Buffer_DispXY);
          tft.setCursor(55, 245);
          tft.print(Buffer_DispXY);
          tft.setCursor(55, 265);
          tft.print(Buffer_DispXY);
          tft.setCursor(55, 285);
          tft.print(Buffer_DispXY);
          tft.setCursor(55, 305);
          tft.print(Buffer_DispXY);
          tft.setCursor(55, 325);
          tft.print(Buffer_DispXY);
          retro=y-10;
          if (retro<0) {retro=0;}
          if (retro>255) {retro=255;}
          analogWrite(TFT_PWM_LED,retro);
          //delay(10);
        }
      }
    }


    Ce code est dérivé d'un exemple.

    Il n'est pas optimisé (plusieurs instructions client.print) c'est juste un exemple pour valider la création d'un serveur avec Arduino.

    Ce code analyse la requête mais de façon minimaliste (juste trouver la fin représentée par une ligne vide)

    J'utilise bien
    Code :Sélectionner tout -Visualiser dans une fenêtre à part
     EthernetServer server = EthernetServer(NumPort);
    et je créé le header HTTP et le contenu HTML moi-même (cette exemple est fait pour fonctionner avec comme client un navigateur web)

    Du coup rien ne m'empêche de prendre de même code et, lorsque le client est un Arduino, envoyer une réponse ne comportant ni header HTTP ni code HTML.
    Je peux reconnaître que le client est un Arduino :
    - soit avec les IP (les Arduinos auront des IP fixes sur le réseau local)
    - soit dans la requête, en convenant d'un code identifiant un Arduino par rapport à un navigateur web classique.

    A bientôt

  12. #12
    Membre éprouvé
    Citation Envoyé par electroremy Voir le message
    je crois que je procéde déjà ainsi
    Je me suis mal exprimé. Vous gérez cela 'à la main' mais vous êtes dépendant donc du protocole HTTP. C'est OK si les clients de votre Serveur sont des navigateurs web, ils vous enverront effectivement du contenu qui suit le protocole HTTP.

    Mais si c'est entre 2 arduino vous pouvez sauter le formalisme HTPP et passer le contenu que vous voulez directement sur la socket

###raw>template_hook.ano_emploi###