في هذه المقالة ، سأصف الموقف الذي واجهته أثناء تطوير مشروع Arduino Mega Server . خلاصة القول هي أن هناك مكتبة Arduino Ethernet مكتوبة لدعم بطاقة شبكة Ethernet Shield على شريحة W5100. هذه لوحة قياسية ومكتبة قياسية تم تجميعها مع بيئة تطوير Arduino لسنوات عديدة.وهذه المكتبة هي الأساس لجميع المشاريع التي تستخدم تبادل المعلومات عبر شبكة سلكية. لذلك ، اتضح أن هذه المكتبة ببساطة غير مناسبة. من حيث المبدأ ، من المستحيل بناء تفاعل طبيعي للشبكة. يمكنك فقط "الانغماس" في الطلبات والاستجابات الفردية. لا يمكننا الحديث عن بناء أي خوادم بناءً على هذه المكتبة. لماذا ا؟لأن هذه المكتبة تحتوي على "خطأ" مدمج يقوم بتعليق الطلبات غير الفريدة لمدة تتراوح من ثلاث إلى عشر ثوانٍ أو أكثر. الخطأ مدمج وعلم مؤلف المكتبة بهذا ، كما يتضح من ملاحظاته في المصدر (ولكن المزيد عن هذا لاحقًا).هنا تحتاج إلى أن تفهم أن المكتبة التي تأتي مع بيئة التطوير الرسمية هي معيار معين وإذا لم يكن المشروع مناسبًا لك ، فستبحث عن العيب في أي مكان ، ولكن ليس في المكتبة القياسية ، لأنه تم استخدامه لسنوات عديدة من قبل مئات الآلاف ، إن لم يكن الملايين من الناس. من العامة. ألا يمكن أن تكون كلها خاطئة؟لماذا في الطبيعة لا توجد خوادم على Arduino
استمر تطوير المشروع كما ينبغي ، وأخيرًا ، يتعلق الأمر بتحسين الرمز وزيادة سرعة الخادم ، وهنا واجهت الموقف التالي: تم تلقي الطلبات الواردة من المتصفح و "معلقة" لمدة ثلاث إلى عشر ثوانٍ ، في المتوسط ، حتى عشرين و المزيد من الثواني مع تبادل أكثر كثافة. في ما يلي لقطة شاشة توضح أن التأخير الشاذ في استجابات الخادم "يسير" عبر الطلبات المختلفة.
عندما بدأت أفهم ، اتضح أنه لا يوجد شيء يمنع الخادم من الاستجابة ، ولكن ، مع ذلك ، "علق" الطلب لأكثر من تسع ثوان ، وفي تكرار آخر كان الطلب نفسه معلقًا بالفعل لمدة ثلاث ثوانٍ تقريبًا.لقد دفعتني هذه الملاحظات إلى التفكير العميق ، وحفرت رمز الخادم بالكامل (في نفس الوقت امتدت إلى نفسي) ولكن لم أجد عيوبًا وأدى كل المنطق إلى مكتبة "قدس الأقداس" Arduino Ethernet Library. لكن الاعتقاد التحريري بأن اللوم على المكتبة القياسية تم تجاهله على أنه غير ملائم. في الواقع ، لا يعمل المستخدمون فقط مع المكتبة ، ولكن أيضًا عدد كبير من المطورين المحترفين. ألا يمكنهم جميعاً رؤية أشياء واضحة؟بالنظر إلى المستقبل ، سأقول أنه عندما اتضح أن الأمر كان في المكتبة القياسية ، أصبح من الواضح لماذا لا توجد خوادم (عادية) في Arduino في الطبيعة. لأنه على أساس المكتبة القياسية (التي يعمل معها معظم المطورين) من المستحيل بشكل أساسي بناء خادم. يؤدي تأخر الاستجابة لمدة عشر ثوانٍ أو أكثر إلى إخراج الخادم من فئة الخوادم نفسها ويجعلها لعبة (نردي) بسيطة.انسحاب وسيط. هذا ليس Arduino غير مناسب لبناء الخوادم ، وتضع مكتبة الشبكة نهاية لفئة مثيرة جدًا من الأجهزة.تشريح المشكلة
الآن دعنا ننتقل من كلمات الأغاني إلى وصف تقني للمشكلة وحلها العملي. بالنسبة لأولئك الذين ليسوا محدثين ، موقع المكتبة القياسي (على Windows):arduino \ libraries \ Ethernetوأول شيء سننظر إليه هو الوظيفة من ملف 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);
}
عندما تغلبت على الحاجز النفسي (تحت ضغط المنطق) وبدأت في البحث عن عيب في مكتبة إيثرنت ، بدأت بهذه الوظيفة. لقد لاحظت تعليقًا غريبًا من قبل المؤلف ، لكنني لم أعلق عليه أي أهمية. بعد أن جرفت المكتبة بالكامل ، مرة أخرى ، ولكن بعد بضعة أيام وحققت تقدمًا كبيرًا في تقنيات الشبكة ، عدت إلى هذه الوظيفة لأن المنطق يشير إلى أن المشكلة كانت موجودة ونظرت عن كثب إلى التعليق.
الأصدقاء ، كل شيء مكتوب في نص واضح. في الترجمة المجانية ، يبدو الأمر مثل هذا: "إنه يعمل ، ولكن ليس دائمًا". انتظر لحظة ، ماذا يعني "ليس دائما"؟ ليس لدينا نادي لوتو الأحد. وعندما لا يعمل ، ماذا بعد ذلك؟ ولكن عندما "لا يعمل" وتبدأ المشاكل بتأخير عشر ثوانٍ. بالنسبة لأولئك الذين هم ضعيفة في التعليمات البرمجية ، سأشرح جوهر المشكلة بكلمات بسيطة. تتكرر الحلقة فوق مآخذ التوصيل ، وبمجرد العثور على الحلقة المناسبة ، تقوم بإرجاع العميل ، وتتجاهل الباقي ببساطة. ويعلقون لمدة عشر ثوان ، حتى "تستلقي البطاقات بشكل إيجابي".وقد عرف المؤلف بالتأكيد عن هذا ، كما يتضح من تقدير الذات لخلقه - ثلاثة س. بدون تعليقات. هذه المكتبة هي أساس العديد من الحيوانات المستنسخة ، والانتباه ، هذه Xs الثلاثة تتجول من مشروع إلى آخر. إذا كنت مطورًا ، فلا يمكنك ملاحظة هذه المشكلة مرة واحدة فقط دون اختبار تبادل الشبكة. أيضا لا تعليق.حل للمشكلة
بعد أن فهمت سبب المشكلة ، فإننا بالتأكيد لن نتوقف عند هذا الحد ونحاول حلها. أولاً ، دعنا نعيد كتابة الوظيفة بحيث لا يعتمد استقبال أو عدم استقبال مقبس على إرادة العلبة ويحدث دائمًا إذا كان هناك واحد. سيؤدي هذا إلى حل مشكلتين:- لن يتعطل الطلبات
- ستتحول الطلبات "التسلسلية" إلى "متوازية" ، مما سيسرع العمل بشكل كبير
إذن ، كود الوظيفة الجديدة: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);
}
نزيل الحلقة ، ونحدد بشكل واضح المقبس ولا نفقد شيئًا - إذا كانت مجانية ، فنحن نضمن استقبال العميل (إذا كان ذلك مناسبًا لنا).نقوم بنفس "التسلسل" مع رمز وظيفة القبول ، ونزيل الحلقة ، ونحدد بشكل واضح المقبس.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);
}
}
ولا تنس إصلاح ملف 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;
};
هذا كل شئ. لقد أجرينا تغييرات على المكتبة القياسية وسلوك الخادم قد تغير بشكل كبير. إذا عمل كل شيء في وقت سابق ببطء شديد ، يتجاوز أي فكرة عن قابلية الاستخدام ، فقد زادت الآن سرعة تحميل الصفحة بشكل كبير وأصبحت مقبولة تمامًا للاستخدام العادي.
انتبه إلى تقليل التأخير بمقدار 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 Ethernet القياسية التي أخبرتك عنها للتو.إضافة . قناة Youtube مفتوحة وهنا فيديو ترويجي لخادم Arduino Mega ، والذي يوضح كيفية العمل مع نظام حقيقي.