Neste artigo, descreverei a situação que encontrei durante o desenvolvimento do projeto Arduino Mega Server . A conclusão é que existe uma biblioteca Ethernet do Arduino, escrita para suportar a placa de rede Ethernet Shield no chip W5100. Essa é uma placa e uma biblioteca padrão que são incluídas no ambiente de desenvolvimento do Arduino por muitos anos.E essa biblioteca é a base para todos os projetos que usam a troca de informações em uma rede com fio. Portanto, essa biblioteca é simplesmente inadequada. Por isso, em princípio, é impossível construir uma interação de rede normal. Você só pode "entrar" em solicitações e respostas únicas. Não podemos falar sobre a construção de servidores com base nesta biblioteca. Por quê?Como esta biblioteca possui um "bug" interno que suspende solicitações não exclusivas por um período de três a dez segundos ou mais. O bug está embutido e o autor da biblioteca sabia disso, como evidenciado por suas anotações na fonte (mas mais sobre isso posteriormente).Aqui, você precisa entender que a biblioteca que acompanha o ambiente oficial de desenvolvimento é um determinado padrão e, se o projeto não funcionar para você, você procurará o defeito em qualquer lugar, mas não na biblioteca padrão, porque ela é usada há muitos anos por centenas de milhares, se não milhões. de pessoas. Eles não podem estar todos errados?Por que, na natureza, não há servidores no Arduino
O desenvolvimento do projeto prosseguiu como deveria e, finalmente, chegou à otimização do código e ao aumento da velocidade do servidor, e aqui me deparei com a seguinte situação: as solicitações de entrada do navegador eram recebidas e “suspensas” por um período de três a dez segundos, em média, até vinte e dois anos. mais segundos com uma troca mais intensa. Aqui está uma captura de tela que mostra que o atraso anômalo nas respostas do servidor "percorre" várias solicitações.
Quando comecei a entender, descobriu-se que nada estava impedindo a resposta do servidor, mas, mesmo assim, a solicitação "travou" por mais de nove segundos e, em outra iteração, a mesma solicitação já estava pendurada por cerca de três segundos.Tais observações levaram-me a uma profunda reflexão e descobri todo o código do servidor (ao mesmo tempo, estiquei-me), mas não encontrei defeitos e toda a lógica levou à Biblioteca Arduino Ethernet do "santo dos santos". Mas o pensamento sedicioso de que a biblioteca padrão era a culpada foi descartado como inadequado. De fato, não apenas os usuários trabalham com a biblioteca, mas também um grande número de desenvolvedores profissionais. Todos eles não podem ver coisas tão óbvias?Olhando para o futuro, direi que, quando o assunto estava na biblioteca padrão, ficou claro por que, na natureza, não existem servidores Arduino (normais). Porque, com base na biblioteca padrão (com a qual a maioria dos desenvolvedores trabalha), é basicamente impossível construir um servidor. Um atraso de uma resposta de dez segundos ou mais retira o servidor da categoria de servidores e o torna um brinquedo simples (nerd).Retirada intermediária. Isso não é, o Arduino não é adequado para a criação de servidores e a biblioteca de rede encerra uma classe de dispositivos muito interessante.Anatomia do problema
Agora vamos passar da letra para uma descrição técnica do problema e sua solução prática. Para quem não está atualizado, o local padrão da biblioteca (no Windows):arduino \ libraries \ EthernetE a primeira coisa que veremos é a função do arquivo EthernetServer.cppEthernetClient 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()) {
return client;
}
}
}
return EthernetClient(MAX_SOCK_NUM);
}
Quando superei a barreira psicológica (sob pressão da lógica) e comecei a procurar um defeito na Biblioteca Ethernet, comecei com esta função. Notei um comentário estranho do autor, mas não dei importância a ele. Tendo escavado a biblioteca inteira, eu novamente, mas depois de alguns dias e tendo feito grandes avanços nas tecnologias de rede, retornei a essa função porque a lógica sugeria que o problema estava lá e olhava mais de perto o comentário.
Amigos, tudo está escrito em texto não criptografado. Em uma tradução gratuita, soa algo como isto: "funciona, mas nem sempre". Espere um minuto, o que "nem sempre" significa? Nós não temos um clube de loteria de domingo. E quando não funciona, e daí? Mas quando "não funciona" e os problemas começam com um atraso de dez segundos.E o autor definitivamente sabia disso, como evidenciado pela auto-estima de sua criação - três x. Sem comentários. Essa biblioteca é a base de muitos clones e, atenção, esses três Xs circulam de um projeto para outro. Se você é um desenvolvedor, não poderá perceber esse problema apenas uma vez sem testar a troca de rede. Também nenhum comentário.Para aqueles que são pouco versados no código, explicarei a essência do problema em palavras simples. O loop percorre os soquetes e, assim que encontra o adequado, retorna o cliente e simplesmente ignora o restante. E eles ficam pendurados por dez segundos, até que "os cartões favoreçam."Solução para o problema
Tendo entendido a causa do problema, certamente não vamos parar por aí e tentar resolvê-lo. Primeiro, vamos reescrever a função para que receber ou não receber um soquete não dependa da vontade do caso e sempre ocorra se houver um. Isso resolverá dois problemas:- pedidos não serão interrompidos
- Os pedidos "sequenciais" se transformarão em "paralelos", o que acelerará significativamente o trabalho
Então, o código para a nova função: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);
}
Removemos o loop, especificamos explicitamente o soquete e não perdemos nada - se for gratuito, temos a garantia de receber um cliente (se for adequado).Fazemos o mesmo "encadeamento" com o código da função de aceitação, removemos o loop e especificamos explicitamente o soquete.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_(sock);
}
}
E não se esqueça de corrigir o arquivo EthernetServer.hclass EthernetServer :
public Server {
private:
uint16_t _port;
void accept_(int sock);
public:
EthernetServer(uint16_t);
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;
};
Isso é tudo. Fizemos alterações na biblioteca padrão e o comportamento do servidor mudou drasticamente. Se antes tudo funcionava muito lentamente, além de qualquer idéia de usabilidade, agora a velocidade de carregamento da página aumentou significativamente e tornou-se bastante aceitável para uso normal.
Preste atenção à redução do atraso em 3-5 vezes para arquivos diferentes e a natureza completamente diferente do download, o que é muito perceptível no uso prático.Código completo para o 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 o 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 remanescentes
Dessa forma, o servidor da demonstração da ideia entra na categoria de coisas que podem ser usadas na vida cotidiana, mas alguns problemas permanecem. Como você pode ver na captura de tela, ainda não há um fundamento fundamental, mas um atraso desagradável de três segundos, o que não deveria ser. A biblioteca é escrita de tal maneira que em muitos lugares o código não funciona como deveria e, se você é um desenvolvedor qualificado, sua ajuda para estabelecer a causa do atraso de três segundos será muito valiosa. Tanto para o projeto Arduino Mega Server quanto para todos os usuários do Arduino.Último momento
Desde que alteramos o código da biblioteca padrão, precisamos chamar suas funções de uma maneira ligeiramente diferente. Aqui eu dou o código que realmente funciona e que forneceu o AMS na captura de tela acima. for (int sock = 0; sock < MAX_SOCK_NUM; sock++) {
EthernetClient sclient = server.available_(sock);
serverWorks2(sclient);
}
Aqui, a tarefa de classificar soquetes foi transferida para o nível do esboço do cliente e, mais importante, e qual é o significado de todas as opções acima, não há "congelamento" de solicitações. E a função do próprio servidor:void serverWorks2(EthernetClient sclient) {
...
}
Você pode se familiarizar com o código completo do servidor baixando o kit de distribuição no site oficial do projeto Arduino Mega Server . E você pode fazer suas perguntas no fórum . Resta resolver o último problema de um atraso de três segundos e teremos um servidor real e rápido no Arduino. A propósito, em breve uma nova versão do AMS será lançada com todas as correções e melhorias nas quais um dos problemas mais urgentes foi resolvido - o trabalho offline sem suporte para o servidor MajorDoMo.
E isso se tornou possível em grande parte devido às correções da biblioteca Ethernet padrão do Arduino que acabei de falar.Adição . Um canal do Youtube está aberto e aqui está um vídeo promocional do Arduino Mega Server, que demonstra como trabalhar com um sistema real.