Biblioteca Ethernet o por qué en la naturaleza no hay servidores en Arduino

imagen

En este artículo, describiré la situación que encontré durante el desarrollo del proyecto Arduino Mega Server . La conclusión es que existe una Biblioteca Arduino Ethernet, escrita para admitir la tarjeta de red Ethernet Shield en el chip W5100. Esta es una placa estándar y una biblioteca estándar que se ha incluido con el entorno de desarrollo Arduino durante muchos años.

Y esta biblioteca es la base de todos los proyectos que utilizan el intercambio de información a través de una red cableada. Entonces, resultó que esta biblioteca simplemente no es adecuada. En él, en principio, es imposible construir una interacción de red normal. Solo puede "satisfacer" solicitudes y respuestas individuales. No podemos hablar sobre la creación de servidores basados ​​en esta biblioteca. ¿Por qué?

Debido a que esta biblioteca tiene un "error" incorporado que suspende solicitudes no únicas por un período de tres a diez segundos o más. El error está incorporado y el autor de la biblioteca lo sabía, como lo demuestran sus notas en la fuente (pero más sobre esto más adelante).

Aquí debe comprender que la biblioteca que viene con el entorno de desarrollo oficial es un cierto estándar, y si el proyecto no funciona para usted, entonces buscará un defecto en cualquier lugar, pero no en la biblioteca estándar, porque ha sido utilizado durante muchos años por cientos de miles, si no millones de la gente. ¿No pueden estar todos equivocados?

¿Por qué en la naturaleza no hay servidores en Arduino?


El desarrollo del proyecto continuó como debería y, finalmente, llegó a optimizar el código y aumentar la velocidad del servidor, y aquí me enfrenté a la siguiente situación: las solicitudes entrantes del navegador fueron recibidas y "suspendidas" durante un período de tres a diez segundos, en promedio, hasta veinte y veinte más segundos con un intercambio más intenso. Aquí hay una captura de pantalla que muestra que el retraso anómalo en las respuestas del servidor "recorre" varias solicitudes.

retraso anormal

Cuando comencé a entender, resultó que nada impedía que el servidor respondiera, pero, sin embargo, la solicitud se "colgó" durante más de nueve segundos, y en otra iteración la misma solicitud ya había estado suspendida durante unos tres segundos.

Tales observaciones me sumergieron en una profunda reflexión y desenterré todo el código del servidor (al mismo tiempo me extendí), pero no encontré defectos y toda la lógica condujo a la Biblioteca Arduino Ethernet "santo de los santos". Pero el pensamiento sedicioso de que la biblioteca estándar tenía la culpa fue descartado como inadecuado. De hecho, no solo los usuarios trabajan con la biblioteca, sino también un gran número de desarrolladores profesionales. ¿No pueden todos no ver cosas tan obvias?

Mirando hacia el futuro, diré que cuando resultó que el asunto estaba en la biblioteca estándar, quedó claro por qué en la naturaleza no hay servidores Arduino (normales). Porque sobre la base de la biblioteca estándar (con la que trabajan la mayoría de los desarrolladores) es básicamente imposible construir un servidor. Un retraso de una respuesta de diez segundos o más saca al servidor de la categoría de servidores y lo convierte en un juguete simple (nerd).

Retiro intermedio. Esto no es Arduino no es adecuado para construir servidores, y la biblioteca de red pone fin a una clase de dispositivos muy interesante.

Anatomía del problema.


Ahora pasemos de la letra a una descripción técnica del problema y su solución práctica. Para aquellos que no están actualizados, la ubicación estándar de la biblioteca (en Windows):

arduino \ bibliotecas \ Ethernet

Y lo primero que veremos es la función del archivo 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);
}

Cuando superé la barrera psicológica (bajo la presión de la lógica) y comencé a buscar un defecto en la Biblioteca Ethernet, comencé con esta función. Noté un comentario extraño del autor, pero no le di importancia. Después de palear toda la biblioteca, nuevamente, pero después de un par de días y de haber hecho grandes avances en las tecnologías de red, volví a esta función porque la lógica sugirió que el problema estaba allí y examinó más de cerca el comentario.


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


Amigos, todo está escrito en texto claro. En una traducción gratuita, suena así: "funciona, pero no siempre". Espera un minuto, ¿qué significa "no siempre"? No tenemos un club de lotería dominical. Y cuando no funciona, ¿entonces qué? Pero cuando "no funciona" y los problemas comienzan con un retraso de diez segundos.

Y el autor definitivamente sabía sobre esto, como lo demuestra la autoestima de su creación: tres x. Sin comentarios. Esta biblioteca es la base de muchos clones y, atención, estas tres X deambulan de un proyecto a otro. Si es desarrollador, no puede notar este problema solo una vez sin probar el intercambio de red. Tampoco hay comentarios.

Para aquellos que no conocen bien el código, explicaré la esencia del problema en palabras simples. El ciclo itera sobre los sockets y, tan pronto como encuentra el adecuado, devuelve el cliente y simplemente ignora el resto. Y cuelgan durante diez segundos, hasta que "las cartas se acuestan favorablemente".

Solución al problema


Habiendo entendido la causa del problema, ciertamente no nos detendremos allí e intentaremos resolverlo. Primero, reescribamos la función para que recibir o no recibir un socket no dependa de la voluntad del caso y siempre sucede si hay uno. Esto resolverá dos problemas:

  • las solicitudes no se colgarán
  • Las solicitudes "secuenciales" se convertirán en "paralelas", lo que acelerará significativamente el trabajo

Entonces, el código para la nueva función:

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

Eliminamos el bucle, especificamos explícitamente el zócalo y no perdemos nada; si es gratis, entonces tenemos la garantía de recibir un cliente (si nos conviene).

Hacemos el mismo "encadenamiento" con el código de la función de aceptación, eliminamos el bucle y especificamos explícitamente el 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
  }
}

Y no olvide arreglar el archivo 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;
};

Eso es todo. Hicimos cambios en la biblioteca estándar y el comportamiento del servidor ha cambiado drásticamente. Si antes todo funcionaba muy lentamente, más allá de cualquier idea de usabilidad, ahora la velocidad de carga de la página ha aumentado significativamente y se ha vuelto bastante aceptable para el uso normal.

velocidad de descarga aumentada

Preste atención a la reducción del retraso de 3 a 5 veces para diferentes archivos y la naturaleza completamente diferente de la descarga, que es muy notable en el uso práctico.

Código completo para el EthernetServer.cpp modificado
/*
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;
}

Código completo para el EthernetServer.h modificado
/*
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



Problemas restantes


De esta forma, el servidor de la demostración de la idea entra en la categoría de cosas que se pueden usar en la vida cotidiana, pero persisten algunos problemas. Como puede ver en la captura de pantalla, todavía no hay un retraso fundamental, sino desagradable de tres segundos, que no debería ser. La biblioteca está escrita de tal manera que hay muchos lugares donde el código no funciona como debería, y si usted es un desarrollador calificado, su ayuda para establecer la causa del retraso de tres segundos será muy valiosa. Tanto para el proyecto Arduino Mega Server como para todos los usuarios de Arduino.

Último momento


Como cambiamos el código de la biblioteca estándar, necesitamos llamar a sus funciones de una manera ligeramente diferente. Aquí les doy el código que realmente funciona y que proporcionó AMS en la captura de pantalla anterior.

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

Aquí, la tarea de ordenar los sockets se ha transferido al nivel del boceto del cliente y, lo más importante, y cuál es el significado de todo lo anterior, no hay "congelamiento" de las solicitudes. Y la función del servidor en sí:

void serverWorks2(EthernetClient sclient) {
...
}

Puede familiarizarse con el código completo del servidor descargando el kit de distribución desde el sitio oficial del proyecto Arduino Mega Server . Y puedes hacer tus preguntas en el foro . Queda por resolver el último problema de un retraso de tres segundos y tendremos un servidor real y de trabajo rápido en Arduino. Por cierto, pronto se lanzará una nueva versión de AMS con todas las correcciones y mejoras en las que se haya resuelto uno de los problemas más acuciantes: el trabajo fuera de línea sin soporte para el servidor MajorDoMo.

Mega Servidor Arduino

Y esto se hizo posible en gran medida debido a las correcciones de la Biblioteca Arduino Ethernet estándar que acabo de mencionar.

Además . Un canal de Youtube está abierto y aquí hay un video promocional del Arduino Mega Server, que muestra cómo trabajar con un sistema real.

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


All Articles