Bibliothèque Ethernet ou pourquoi dans la nature il n'y a pas de serveurs sur Arduino

image

Dans cet article, je décrirai la situation que j'ai rencontrée lors du développement du projet Arduino Mega Server . L'essentiel est qu'il existe une telle bibliothèque Ethernet Arduino, écrite pour prendre en charge la carte réseau Ethernet Shield sur la puce W5100. Il s'agit d'une carte standard et d'une bibliothèque standard fournies avec l'environnement de développement Arduino depuis de nombreuses années.

Et cette bibliothèque est la base de tous les projets qui utilisent l'échange d'informations sur un réseau câblé. Il s'est donc avéré que cette bibliothèque est tout simplement inadaptée. Sur celui-ci, en principe, il est impossible de construire une interaction réseau normale. Vous ne pouvez "vous livrer" qu'à des demandes et des réponses uniques. Nous ne pouvons pas parler de la création de serveurs basés sur cette bibliothèque. Pourquoi?

Parce que cette bibliothèque a un «bogue» intégré qui suspend les demandes non uniques pendant une période de trois à dix secondes ou plus. Le bogue est intégré et l'auteur de la bibliothèque le savait, comme en témoignent ses notes dans la source (mais plus à ce sujet plus tard).

Ici, vous devez comprendre que la bibliothèque fournie avec l'environnement de développement officiel est un certain standard et si le projet ne fonctionne pas pour vous, vous chercherez le défaut n'importe où, mais pas dans la bibliothèque standard, car il a été utilisé pendant de nombreuses années par des centaines de milliers, voire des millions de personnes. Ne peuvent-ils pas tous se tromper?

Pourquoi dans la nature il n'y a pas de serveurs sur Arduino


Le développement du projet s'est déroulé comme il se doit et, finalement, il s'agit d'optimiser le code et d'augmenter la vitesse du serveur, et là, je suis confronté à la situation suivante: les requêtes entrantes du navigateur sont reçues et «suspendues» pour une durée de trois à dix secondes, en moyenne, jusqu'à vingt et vingt plus de secondes avec un échange plus intense. Voici une capture d'écran qui montre que le retard anormal dans les réponses du serveur «parcourt» diverses requêtes.

retard anormal

Quand j'ai commencé à comprendre, il s'est avéré que rien n'empêchait le serveur de répondre, mais, néanmoins, la demande a «suspendu» pendant plus de neuf secondes, et à une autre itération, la même demande avait déjà été suspendue pendant environ trois secondes.

De telles observations m'ont plongé dans une profonde réflexion et j'ai déterré tout le code du serveur (en même temps je me suis étiré) mais n'ai trouvé aucun défaut et toute la logique a conduit à la bibliothèque Ethernet Arduino "sacrée des saints". Mais l'idée séditieuse que la bibliothèque standard était à blâmer a été rejetée comme inadéquate. En effet, non seulement les utilisateurs travaillent avec la bibliothèque, mais aussi un grand nombre de développeurs professionnels. Ne voient-ils pas tous des choses aussi évidentes?

Pour l'avenir, je dirai que lorsqu'il s'est avéré que le problème se trouvait dans la bibliothèque standard, il est devenu clair pourquoi, dans la nature, il n'y a pas de serveurs (normaux) sur l'Arduino. Parce que sur la base de la bibliothèque standard (avec laquelle la plupart des développeurs travaillent), il est fondamentalement impossible de construire un serveur. Un retard d'une réponse de dix secondes ou plus fait sortir le serveur de la catégorie des serveurs et en fait un jouet simple (ringard).

Retrait intermédiaire. Ce n'est pas Arduino n'est pas adapté à la construction de serveurs, et la bibliothèque réseau met fin à une classe d'appareils très intéressante.

Anatomie du problème


Passons maintenant des paroles à une description technique du problème et de sa solution pratique. Pour ceux qui ne sont pas à jour, l'emplacement standard de la bibliothèque (sous Windows):

arduino \ bibliothèques \ Ethernet

Et la première chose que nous verrons est la fonction du fichier 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);
}

Quand j'ai surmonté la barrière psychologique (sous la pression de la logique) et commencé à chercher un défaut dans la bibliothèque Ethernet, j'ai commencé avec cette fonction. J'ai remarqué un commentaire étrange de l'auteur, mais n'y attache aucune importance. Après avoir pelleté toute la bibliothèque, j'ai de nouveau, mais après quelques jours et après avoir fait de grands progrès dans les technologies de réseau, je suis retourné à cette fonction parce que la logique suggérait que le problème était là et regardait de plus près le commentaire.


        // XXX: don't always pick the lowest numbered socket.


Amis, tout est écrit en texte clair. Dans une traduction gratuite, cela ressemble à ceci: "ça marche, mais pas toujours". Attendez une minute, que signifie «pas toujours»? Nous n'avons pas de club de loto du dimanche. Et quand ça ne marche pas, alors quoi? Mais lorsque «ne fonctionne pas» et que les problèmes commencent avec un délai de dix secondes.

Et l'auteur était certainement au courant de cela, comme en témoigne l'estime de soi de sa création - trois x. Sans commentaires. Cette bibliothèque est à la base de nombreux clones et, attention, ces trois X se déplacent d'un projet à l'autre. Si vous êtes développeur, vous ne pouvez pas remarquer ce problème une seule fois sans tester l'échange réseau. Pas de commentaire non plus.

Pour ceux qui connaissent mal le code, je vais expliquer l'essence du problème avec des mots simples. La boucle parcourt les sockets et, dès qu'elle trouve celle qui convient, renvoie le client et ignore simplement le reste. Et ils pendent pendant dix secondes, jusqu'à ce que "les cartes s'allongent favorablement".

Solution au problème


Ayant compris la cause du problème, nous ne nous arrêterons certainement pas là et tenterons de le résoudre. Tout d'abord, réécrivons la fonction pour que la réception ou la non réception d'une socket ne dépende pas de la volonté du cas et se produise toujours s'il y en a une. Cela résoudra deux problèmes:

  • les demandes ne seront pas bloquées
  • Les requêtes "séquentielles" se transformeront en "parallèles", ce qui accélérera considérablement le travail

Ainsi, le code de la nouvelle fonction:

EthernetClient EthernetServer::available_(int sock) {
  accept_(sock);
  EthernetClient client(sock);
  if (EthernetClass::_server_port[sock] == _port &&
      (client.status() == SnSR::ESTABLISHED ||
       client.status() == SnSR::CLOSE_WAIT)) {
    if (client.available()) {
      return client;
    }
  }
  return EthernetClient(MAX_SOCK_NUM);
}

Nous supprimons la boucle, spécifions explicitement la socket et ne perdons rien - si elle est gratuite, alors nous sommes garantis de recevoir un client (si cela nous convient).

Nous faisons le même «chaînage» avec le code de la fonction accept, supprimons la boucle et spécifions explicitement le socket.

void EthernetServer::accept_(int sock) {
  int listening = 0;
  EthernetClient client(sock);

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

  if (!listening) {
    //begin();
    begin_(sock); // added
  }
}

Et n'oubliez pas de corriger le fichier EthernetServer.h

class EthernetServer : 
public Server {
private:
  uint16_t _port;
  //void accept();
  void accept_(int sock);
public:
  EthernetServer(uint16_t);
  //EthernetClient available();
  EthernetClient available_(int sock);
  virtual void begin();
  virtual void begin_(int sock);
  virtual size_t write(uint8_t);
  virtual size_t write(const uint8_t *buf, size_t size);
  using Print::write;
};

C'est tout. Nous avons apporté des modifications à la bibliothèque standard et le comportement du serveur a radicalement changé. Si auparavant tout fonctionnait très lentement, au-delà de toute idée de convivialité, maintenant la vitesse de chargement des pages a considérablement augmenté et est devenue tout à fait acceptable pour une utilisation normale.

augmentation de la vitesse de téléchargement

Faites attention à la réduction du délai de 3 à 5 fois pour différents fichiers et à la nature complètement différente du téléchargement, ce qui est très visible dans la pratique.

Code complet pour EthernetServer.cpp modifié
/*
Mod for Arduino Mega Server project
fix bug delay answer server
*/

#include «w5100.h»
#include «socket.h»
extern «C» {
#include «string.h»
}

#include «Ethernet.h»
#include «EthernetClient.h»
#include «EthernetServer.h»

EthernetServer::EthernetServer(uint16_t port) {
_port = port;
}

void EthernetServer::begin() {
for (int sock = 0; sock < MAX_SOCK_NUM; sock++) {
EthernetClient client(sock);
if (client.status() == SnSR::CLOSED) {
socket(sock, SnMR::TCP, _port, 0);
listen(sock);
EthernetClass::_server_port[sock] = _port;
break;
}
}
}

void EthernetServer::begin_(int sock) {
EthernetClient client(sock);
if (client.status() == SnSR::CLOSED) {
socket(sock, SnMR::TCP, _port, 0);
listen(sock);
EthernetClass::_server_port[sock] = _port;
}
}

/*

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

for (int sock = 0; sock < MAX_SOCK_NUM; sock++) {
EthernetClient client(sock);

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

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

*/

void EthernetServer::accept_(int sock) {
int listening = 0;
EthernetClient client(sock);

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

if (!listening) {
//begin();
begin_(sock); // added
}
}

/*

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);
}

*/

EthernetClient EthernetServer::available_(int sock) {
accept_(sock);
EthernetClient client(sock);
if (EthernetClass::_server_port[sock] == _port &&
(client.status() == SnSR::ESTABLISHED ||
client.status() == SnSR::CLOSE_WAIT)) {
if (client.available()) {
return client;
}
}
return EthernetClient(MAX_SOCK_NUM);
}

size_t EthernetServer::write(uint8_t b) {
return write(&b, 1);
}

size_t EthernetServer::write(const uint8_t *buffer, size_t size) {
size_t n = 0;
//accept();

for (int sock = 0; sock < MAX_SOCK_NUM; sock++) {
accept_(sock); // added
EthernetClient client(sock);

if (EthernetClass::_server_port[sock] == _port &&
client.status() == SnSR::ESTABLISHED) {
n += client.write(buffer, size);
}
}
return n;
}

Code complet pour EthernetServer.h modifié
/*
Mod for Arduino Mega Server project
fix bug delay answer server
*/

#ifndef ethernetserver_h
#define ethernetserver_h

#include «Server.h»

class EthernetClient;

class EthernetServer:
public Server {
private:
uint16_t _port;
//void accept();
void accept_(int sock);
public:
EthernetServer(uint16_t);
//EthernetClient available();
EthernetClient available_(int sock);
virtual void begin();
virtual void begin_(int sock);
virtual size_t write(uint8_t);
virtual size_t write(const uint8_t *buf, size_t size);
using Print::write;
};

#endif



Problèmes restants


Sous cette forme, le serveur de la démonstration de l'idée entre dans la catégorie des choses qui peuvent être utilisées dans la vie quotidienne, mais certains problèmes subsistent. Comme vous pouvez le voir sur la capture d'écran, il n'y a toujours pas de délai fondamental, mais un délai désagréable de trois secondes, qui ne devrait pas l'être. La bibliothèque est écrite de telle manière qu'il y a beaucoup d'endroits où le code ne fonctionne pas comme il se doit, et si vous êtes un développeur qualifié, votre aide pour déterminer la cause du retard de trois secondes sera très précieuse. À la fois pour le projet Arduino Mega Server et pour tous les utilisateurs d'Arduino.

Dernier moment


Puisque nous avons changé le code de la bibliothèque standard, nous devons appeler ses fonctions d'une manière légèrement différente. Ici, je donne le code qui fonctionne vraiment et qui a fourni AMS dans la capture d'écran ci-dessus.

  for (int sock = 0; sock < MAX_SOCK_NUM; sock++) {
    EthernetClient sclient = server.available_(sock);
    serverWorks2(sclient);
  }

Ici, la tâche de tri des prises a été transférée au niveau de l'esquisse du client et, plus important encore, et quel est le sens de tout ce qui précède, il n'y a pas de «gel» des demandes. Et la fonction du serveur lui-même:

void serverWorks2(EthernetClient sclient) {
...
}

Vous pouvez vous familiariser avec le code serveur complet en téléchargeant le kit de distribution sur le site officiel du projet Arduino Mega Server . Et vous pouvez poser vos questions sur le forum . Il reste à résoudre le dernier problème d'un délai de trois secondes et nous aurons un vrai serveur rapide sur Arduino. Soit dit en passant, une nouvelle version d'AMS sera bientôt publiée avec toutes les corrections et améliorations dans lesquelles l'un des problèmes les plus urgents a été résolu - le travail hors ligne sans prise en charge du serveur MajorDoMo.

Arduino Mega Server

Et cela est devenu possible en grande partie grâce aux corrections de la bibliothèque Ethernet Arduino standard dont je viens de vous parler.

Addition . Une chaîne Youtube est ouverte et voici une vidéo promo du Arduino Mega Server, qui montre comment travailler avec un vrai système.

Source: https://habr.com/ru/post/fr382565/


All Articles