以太网库或为什么在Arduino上自然没有服务器

图片

在本文中,我将描述在Arduino Mega Server项目的开发过程中遇到的情况。最重要的是,有一个这样的Arduino以太网库,用于支持W5100芯片上的Ethernet Shield网卡。这是与Arduino开发环境捆绑多年的标准板和标准库。

该库是使用有线网络上的信息交换的所有项目的基础。因此,事实证明该库根本不合适。原则上,不可能在此基础上建立正常的网络交互。您只能“沉迷”单个请求和响应。我们不能谈论基于此库构建任何服务器。为什么?

因为该库具有内置的“错误”,可将非唯一请求暂停三到十秒或更长时间。该错误是内置的,并且库的作者对此有所了解,如他在源中的注释所证明的(稍后会对此进行更多介绍)。

在这里,您需要了解官方开发环境附带的库是某个标准,如果该项目不适合您,那么您将在任何地方(而不是在标准库中)查找缺陷,因为数十年来(即使不是数百万)已经使用了数十年。人。他们都错了吗?

为什么实际上在Arduino上没有服务器


该项目的开发按预期进行,最后是优化代码并提高了服务器的速度,在这里我面临以下情况:接收到来自浏览器的传入请求,并将其“暂停”平均三到十秒钟,最多二十到二十秒钟。交流时间更长,秒数更多。这是一个截图,显示了服务器响应中的异常延迟在各种请求之间“走动”。

异常延迟

当我开始理解时,事实证明,没有什么可以阻止服务器响应,但是,请求“挂起”了超过9秒钟,而在另一次迭代中,相同的请求已经挂起了大约3秒钟。

这样的观察使我陷入了沉思,并挖出了整个服务器代码(同时又费劲了),但没有发现缺陷,所有逻辑都导致了“圣洁”的Arduino以太网库。但是,认为标准库应归咎于煽动性的想法被认为是不够的。实际上,不仅用户在使用该库,而且还有大量的专业开发人员。他们难道都看不到这么明显的东西吗?

展望未来,我要说的是,当事实证明它是标准库时,很明显为什么实际上没有(普通)Arduino服务器。因为基于标准库(大多数开发人员都在使用),所以基本上不可能构建服务器。十秒或更长的响应延迟会使服务器脱离服务器本身的类别,使其成为一个简单的(讨厌的)玩具。

中级退出。这不是Arduino不适合构建服务器的情况,并且网络库结束了非常有趣的一类设备。

问题剖析


现在,让我们从歌词转到对问题及其实际解决方案的技术描述。对于那些不是最新的人,标准库位置(在Windows上):

arduino \ libraries \ Ethernet

我们首先要看的是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);
}

当我克服了心理障碍(在逻辑压力下)并开始在以太网库中查找缺陷时,我就开始使用此功能。我注意到作者的一则奇怪的评论,但对此并不重视。在铲除了整个库之后,我又重新使用了几天,但是在网络技术方面取得了长足的进步之后,我又回到了此功能,因为逻辑表明问题出在这里,并且仔细地看了一下评论。


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


朋友,一切都以明文形式写成。在免费的翻译中,这听起来像是这样:“它有效,但并非总是如此。”等一下,“不总是”是什么意思?我们没有周日乐透俱乐部。当它不起作用时,那又如何呢?但是,当“行不通”并且问题开始时会延迟十秒钟。

作者肯定知道这一点,他的创作的自尊心即证明了这一点-三x。没意见。该库是许多克隆的基础,并且请注意,这三个X从一个项目漫游到另一个项目。如果您是开发人员,那么在不测试网络交换的情况下,您只会一次注意到此问题。也没有评论。

对于那些不太熟悉代码的人,我将用简单的词来解释问题的实质。循环遍历套接字,并在找到合适的套接字后立即返回客户端,而忽略其余部分。他们挂了十秒钟,直到“卡片顺利躺下”。

解决问题


了解了问题的原因之后,我们当然不会止步并尝试解决问题。首先,让我们重写该函数,以便接收或不接收套接字都不取决于情况的意愿,并且总是在有套接字的情况下发生。这将解决两个问题:

  • 请求不会挂起
  • “顺序”请求将变为“并行”,这将大大加快工作速度

因此,新功能的代码如下:

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

我们删除循环,显式指定套接字,并且不会丢失任何内容-如果它是免费的,那么我们保证会收到一个客户端(如果它适合我​​们)。

我们对accept函数的代码进行相同的“链接”,删除循环,并显式指定套接字。

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

并且不要忘记修复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;
};

就这样。我们对标准库进行了更改,服务器行为也发生了巨大变化。如果早期的一切工作都非常缓慢,超出了可用性的任何想法,那么现在页面加载速度已经大大提高,对于正常使用来说已经可以接受了。

下载速度提高

注意将不同文件的延迟减少3-5倍,并且下载的性质完全不同,这在实际使用中非常明显。

修改后的EthernetServer.cpp的完整代码
/*
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;
}

修改后的EthernetServer.h的完整代码
/*
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



仍然存在的问题


在这种形式下,演示想法的服务器进入了可以在日常生活中使用的事物类别,但是仍然存在一些问题。正如您在屏幕快照中看到的那样,仍然没有一个基本的延迟,而是令人不快的三秒钟延迟,这不是应该的。该库的编写方式使得很多地方代码无法正常工作,如果您是合格的开发人员,那么帮助确定三秒钟延迟的原因将非常有价值。既适用于Arduino Mega Server项目,也适用于所有Arduino用户。

最后一刻


由于我们更改了标准库的代码,因此我们需要以稍微不同的方式调用其函数。在这里,我给出了真正有效的代码,该代码在上面的屏幕快照中提供了AMS。

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

在这里,对套接字进行排序的任务已经转移到了客户端草图的级别,最重要的是,以上所有含义是什么,没有“冻结”请求。服务器本身的功能:

void serverWorks2(EthernetClient sclient) {
...
}

您可以通过从Arduino Mega Server项目的官方网站下载分发套件来熟悉完整的服务器代码您可以在论坛上提问解决最后一个三秒延迟的问题还有待解决,我们将在Arduino上拥有一个真正的,快速工作的服务器。顺便说一下,不久将发布新版本的AMS,其中包含所有已解决的最紧迫问题之一的所有更正和改进-不支持MajorDoMo服务器的脱机工作。

Arduino Mega服务器

这很大程度上是由于我刚刚告诉您的标准Arduino以太网库的更正。

加法一个Youtube频道已打开,这是Arduino Mega Server 促销视频,演示了如何在真实系统上工作。

Source: https://habr.com/ru/post/zh-CN382565/


All Articles