Perpustakaan Ethernet atau mengapa di alam tidak ada server di Arduino

gambar

Pada artikel ini, saya akan menjelaskan situasi yang saya temui selama pengembangan proyek Arduino Mega Server . Intinya adalah bahwa ada Perpustakaan Arduino Ethernet, ditulis untuk mendukung kartu jaringan Ethernet Shield pada chip W5100. Ini adalah papan standar dan perpustakaan standar yang telah digabungkan dengan lingkungan pengembangan Arduino selama bertahun-tahun.

Dan perpustakaan ini adalah dasar untuk semua proyek yang menggunakan pertukaran informasi melalui jaringan kabel. Jadi, ternyata perpustakaan ini tidak cocok. Di atasnya, pada prinsipnya, tidak mungkin untuk membangun interaksi jaringan yang normal. Anda hanya dapat "menuruti" permintaan tunggal dan tanggapan. Kami tidak dapat berbicara tentang membangun server apa pun berdasarkan perpustakaan ini. Mengapa?

Karena pustaka ini memiliki "bug" bawaan yang menangguhkan permintaan tidak unik selama tiga hingga sepuluh detik atau lebih. Bug ini bawaan dan penulis perpustakaan tahu tentang ini, sebagaimana dibuktikan oleh catatannya di sumber (tetapi lebih lanjut tentang ini nanti).

Di sini Anda perlu memahami bahwa perpustakaan yang datang dengan lingkungan pengembangan resmi adalah standar tertentu dan jika proyek tidak bekerja untuk Anda, maka Anda akan mencari cacat di mana saja, tetapi tidak di perpustakaan standar, karena telah digunakan selama bertahun-tahun oleh ratusan ribu, jika tidak jutaan orang. Tidak bisakah mereka semua salah?

Mengapa di alam tidak ada server di Arduino


Pengembangan proyek berjalan sebagaimana mestinya dan, akhirnya, datang untuk mengoptimalkan kode dan meningkatkan kecepatan server, dan di sini saya dihadapkan dengan situasi berikut: permintaan masuk dari browser diterima dan "ditangguhkan" untuk jangka waktu tiga hingga sepuluh detik, rata-rata, hingga dua puluh dan lebih banyak detik dengan pertukaran yang lebih intens. Berikut adalah tangkapan layar yang menunjukkan bahwa penundaan anomali dalam respons server "berjalan" di berbagai permintaan.

keterlambatan abnormal

Ketika saya mulai mengerti, ternyata tidak ada yang menghentikan server untuk merespons, tetapi, bagaimanapun, permintaan "digantung" selama lebih dari sembilan detik, dan pada pengulangan lain permintaan yang sama telah digantung selama sekitar tiga detik.

Pengamatan seperti itu membuat saya sangat perhatian dan saya menggali seluruh kode server (pada saat yang sama merentangkan diri saya) tetapi tidak menemukan cacat dan semua logika mengarah ke "Arca Mahakudus" Arduino Ethernet Library. Tetapi pemikiran yang menghasut bahwa perpustakaan standar yang harus disalahkan dibuang sebagai tidak memadai. Memang, tidak hanya pengguna bekerja dengan perpustakaan, tetapi juga sejumlah besar pengembang profesional. Tidak bisakah mereka semua tidak melihat hal-hal yang begitu jelas?

Ke depan, saya akan mengatakan bahwa ketika ternyata itu adalah perpustakaan standar, menjadi jelas mengapa di alam tidak ada server Arduino (normal). Karena berdasarkan pustaka standar (yang bekerja dengan sebagian besar pengembang) pada dasarnya tidak mungkin untuk membangun server. Penundaan respons sepuluh detik atau lebih akan membuat server keluar dari kategori server itu sendiri dan menjadikannya mainan yang sederhana (kutu buku).

Penarikan menengah. Ini bukan Arduino yang tidak cocok untuk membangun server, dan perpustakaan jaringan mengakhiri kelas perangkat yang sangat menarik.

Anatomi masalah


Sekarang mari kita beralih dari lirik ke deskripsi teknis tentang masalah dan solusi praktisnya. Bagi mereka yang tidak mutakhir, lokasi perpustakaan standar (pada Windows):

arduino \ libraries \ Ethernet

Dan hal pertama yang akan kita lihat adalah fungsi dari file 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);
}

Ketika saya mengatasi hambatan psikologis (di bawah tekanan logika) dan mulai mencari cacat di Perpustakaan Ethernet, saya mulai dengan fungsi ini. Saya memperhatikan komentar aneh oleh penulis, tetapi tidak menganggapnya penting. Setelah menyekop seluruh perpustakaan, saya kembali, tetapi setelah beberapa hari dan telah membuat kemajuan besar dalam teknologi jaringan, kembali ke fungsi ini karena logika menyarankan bahwa masalahnya ada di sana dan melihat lebih dekat pada komentar.


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


Teman-teman, semuanya ditulis dalam teks yang jelas. Dalam terjemahan gratis, itu terdengar seperti ini: "itu berfungsi, tetapi tidak selalu." Tunggu sebentar, apa artinya "tidak selalu"? Kami tidak memiliki klub lotre hari Minggu. Dan ketika itu tidak berhasil, lalu apa? Tetapi ketika "tidak bekerja" dan masalah dimulai dengan penundaan sepuluh detik.

Dan penulis pasti tahu tentang ini, sebagaimana dibuktikan oleh harga diri ciptaannya - tiga x. Tidak ada komentar. Perpustakaan ini adalah dasar bagi banyak klon dan, perhatikan, ketiga X ini berkeliaran dari satu proyek ke proyek lainnya. Jika Anda seorang pengembang, maka Anda tidak dapat melihat masalah ini hanya sekali tanpa menguji pertukaran jaringan. Juga tidak ada komentar.

Bagi mereka yang kurang berpengalaman dalam kode, saya akan menjelaskan esensi masalah dengan kata-kata sederhana. Loop berulang di atas soket dan, segera setelah menemukan yang cocok, mengembalikan klien, dan mengabaikan sisanya. Dan mereka menggantung selama sepuluh detik, sampai "kartu-kartu itu berbaring dengan nyaman."

Solusi untuk masalah tersebut


Setelah memahami penyebab masalah, kami tentu tidak akan berhenti di situ dan mencoba menyelesaikannya. Pertama, mari kita menulis ulang fungsinya sehingga menerima atau tidak menerima soket tidak tergantung pada keinginan kasing dan selalu terjadi jika ada. Ini akan menyelesaikan dua masalah:

  • permintaan tidak akan hang
  • Permintaan "Sequential" akan berubah menjadi "parallel", yang secara signifikan akan mempercepat pekerjaan

Jadi, kode untuk fungsi baru:

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

Kami menghapus loop, tentukan soket dengan jelas dan tidak kehilangan apa pun - jika gratis, maka kami dijamin akan menerima klien (jika cocok dengan kami).

Kami melakukan "rantai" yang sama dengan kode fungsi terima, lepaskan loop, dan tentukan soket secara eksplisit.

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

Dan jangan lupa untuk memperbaiki file 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;
};

Itu saja. Kami membuat perubahan pada perpustakaan standar dan perilaku server telah berubah secara dramatis. Jika sebelumnya semuanya bekerja sangat lambat, di luar gagasan tentang kegunaan, sekarang kecepatan memuat halaman telah meningkat secara signifikan dan menjadi cukup dapat diterima untuk penggunaan normal.

kecepatan unduhan meningkat

Perhatikan pengurangan penundaan sebanyak 3-5 kali untuk file yang berbeda dan sifat unduhan yang benar-benar berbeda, yang sangat nyata dalam penggunaan praktis.

Kode lengkap untuk EthernetServer.cpp yang dimodifikasi
/*
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;
}

Kode lengkap untuk EthernetServer.h yang dimodifikasi
/*
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



Masalah yang tersisa


Dalam bentuk ini, server dari demonstrasi ide masuk ke dalam kategori hal-hal yang dapat digunakan dalam kehidupan sehari-hari, tetapi beberapa masalah tetap ada. Seperti yang dapat Anda lihat di tangkapan layar, masih tidak ada yang mendasar, tetapi penundaan yang tidak menyenangkan dari tiga detik, yang seharusnya tidak. Perpustakaan ditulis sedemikian rupa sehingga ada banyak tempat di mana kode tidak berfungsi sebagaimana mestinya, dan jika Anda adalah pengembang yang memenuhi syarat, maka bantuan Anda dalam menentukan penyebab keterlambatan tiga detik akan sangat berharga. Baik untuk proyek Arduino Mega Server dan untuk semua pengguna Arduino.

Saat terakhir


Karena kami mengubah kode perpustakaan standar, kami perlu memanggil fungsinya dengan cara yang sedikit berbeda. Di sini saya memberikan kode yang benar-benar berfungsi dan yang memberikan AMS pada tangkapan layar di atas.

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

Di sini, tugas menyortir soket telah dipindahkan ke tingkat sketsa klien, dan, yang paling penting, dan apa arti dari di atas, tidak ada "pembekuan" permintaan. Dan fungsi server itu sendiri:

void serverWorks2(EthernetClient sclient) {
...
}

Anda dapat membiasakan diri dengan kode server lengkap dengan mengunduh kit distribusi dari situs resmi proyek Arduino Mega Server . Dan Anda dapat mengajukan pertanyaan Anda di forum . Tetap untuk menyelesaikan masalah terakhir dari penundaan tiga detik dan kami akan memiliki server yang nyata dan bekerja cepat di Arduino. Omong-omong, segera versi baru AMS akan dirilis dengan semua perbaikan dan perbaikan di mana salah satu masalah yang paling mendesak telah diselesaikan - pekerjaan offline tanpa dukungan untuk server MajorDoMo.

Arduino Mega Server

Dan ini menjadi mungkin sebagian besar karena koreksi dari Perpustakaan Arduino Ethernet standar yang baru saja saya katakan.

Selain itu . Saluran Youtube terbuka dan berikut ini adalah video promo dari Arduino Mega Server, yang menunjukkan cara bekerja dengan sistem nyata.

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


All Articles