RoboCraft
Ethernet library – библиотека для работы с Ethernet-шилдом
Ethernet-шилд – это дополнительная плата, которая подключается к ардуино и позволяет этой замечательной плате посылать и получать данные через локальную сеть и даже работать с Интернетом.
Работа с протоколами TCP/IP в шилде реализована аппаратно на микросхеме W5100.
Для работы с данным шилдом ничего дополнительно скачивать не нужно, т.к. в составе Arduino IDE уже есть удобная библиотека (\libraries\Ethernet\).
Библиотека содержит три класса: EthernetClass ,Client и Server.
Чтобы начать работать с библиотекой – её нужно сначала подключить; для этого можно написать
или выбрать в меню Arduino IDE:
Sketch – Import Library… — Ethernet
и в Ваш скетч автоматически добавятся строчки:
Библиотека позволяет выступать не только в качестве клиента, но и как сервер и поддерживает до четырёх одновременных подключений (входящих или исходящих).
Рассмотрим функции, которые реализуют этот функционал.
Первым делом необходимо инициализировать работу с библиотекой функцией begin, которая принимает от двух до четырёх параметров:
void begin(uint8_t *, uint8_t *);
Описание:
Инициализирует работу с библиотекой Ethernet и устанавливает сетевые настройки.
Параметры:
mac — MAC-адрес — физический адрес устройства (массив из 6 байт)
ip — IP-адрес – сетевой адрес Вашего устройства (массив из 4 байт)
gateway – IP-адрес Вашего сетевого шлюза (массив из 4 байт) (по-умолчанию – IP-адрес устройства с 1 в последнем октете)
subnet – маска подсети. (массив из 4 байт) (по-умолчанию 255.255.255.0)
если Вы не помните свои сетевые параметры, то на компьютере под управлением ОС Windows их можно посмотреть через
Пуск – Панель управления – Сетевые подключения
Щёлкнуть правой кнопкой мышки на «Подключение по локальной сети» и выбрать «Свойства»
В появившемся окошке выделить в списке «Протокол Интернета TCP/IP» и нажать кнопку «Свойства»
или же просто набрать в консоли команду
Возвращаемое значение:
ничего
Как уже отметили – библиотека позволяет работать в двух режимах – серверном или клиентском.
Рассмотрим серверные функции.
Описание:
Это конструктор класса Server – при вызове данной функции создаётся объект типа Server, для обработки входящих подключений по указанному порту.
Параметры:
port: номер порта сервера (int)
Возвращаемое значение:
ничего
Описание:
Запускает сервер на приём входящих соединений.
Возвращаемое значение:
ничего
Описание:
Возвращает объект типа Client , который представляет клиентское подключение к серверу и имеет данные для считывания (запрос). Объект сохраняется при выходе из области видимости; его можно закрыть вызовом client.stop().
Возвращаемое значение:
ничего
virtual void write(uint8_t);
virtual void write(const char *str);
virtual void write(const uint8_t *buf, size_t size);
Описание:
Запись данных для передачи подключенному клиенту.
Параметры:
data – байт или строка символов
Возвращаемое значение:
ничего
Т.к. класс Server наследуется от класса Print возможен так же вызов функций
print()
println()
Печатает данные для всех подключённых клиентов. Числа печатаются, как последовательность цифр, в виде ASCII-символов (например, число 123 будет передано как три символа: ‘1’, ‘2’, ‘3’).
Функции println печатают данные с последующим переводом на новую строчку.
Параметры:
data – данные для печати (char, byte, int, long или строка)
BASE (опционально): основание представления числа: BIN -двоичное (основание 2), DEC десятичное (основание 10), OCT — восьмеричное (основание 8), HEX — шестнадцатеричное(основание 16).
Теперь рассмотрим клиентские функции.
Client(uint8_t);
Client(uint8_t *, uint16_t);
Описание:
Это конструктор класса Client – при вызове данной функции создаётся объект типа Client , который может подключаться к указанному IP-адресу и порту.
Параметры:
ip: IP-адрес к которому клиент должен подключиться (массив из 4 байт)
port: номер порта для поключения (int)
Описание:
Определяет есть ли подключение к серверу.
! Клиент считается подключённым, если подключение уже закрыто, но остались несчитанные данные.
Возвращаемое значение:
true – если клиент подключен и false в противном случае.
Описание:
Подключается к IP-адресу и порту, заданному через конструктор.
Возвращаемое значение:
ничего
Пример:
true – если подключиться удалось и false в противном случае.
virtual void write(uint8_t);
virtual void write(const char *str);
virtual void write(const uint8_t *buf, size_t size);
Описание:
Запись данных для передачи к подключенному серверу.
Параметры:
data – байт или строка символов
Возвращаемое значение:
ничего
Как и класс Server – класс Client наследуется от класса Print и так же возможен вызов функций
print()
println()
Печатает данные для сервера, к которому подключён клиент. Числа печатаются, как последовательность цифр, в виде ASCII-символов (например, число 123 будет передано как три символа: ‘1’, ‘2’, ‘3’).
Функции println печатают данные с последующим переводом на новую строчку.
Параметры:
data – данные для печати (char, byte, int, long или строка)
BASE (опционально): основание представления числа: BIN -двоичное (основание 2), DEC десятичное (основание 10), OCT — восьмеричное (основание 8), HEX — шестнадцатеричное(основание 16).
Описание:
Возвращает количество байт, доступных для чтения (т.е. объем данных, которые были переданы клиенту со стороны сервера).
Возвращаемое значение:
Количество доступных байт (int).
Описание:
Считывает следующий байт, полученный клиентом от сервера (после последнего вызова read())
Возвращаемое значение:
Байт (или символ) или -1 если данные отсутствуют.
Описание:
Очищает данные, которые были получены клиентом, но еще не были им прочитаны.
Ethernet Library или почему в природе не существует серверов на Arduino
В этой статье я опишу ситуацию с которой столкнулся во время разработки проекта Arduino Mega Server. Суть дела заключается в том, что существует такая библиотека Arduino Ethernet Library, написанная для поддержки сетевой платы Ethernet Shield на чипе W5100. Это стандартная плата и стандартная библиотека, которая многие годы поставляется в комплекте со средой разработки Arduino.
И эта библиотека является основой для всех проектов, использующих обмен информацией по проводной сети. Так вот, оказалось, что эта библиотека попросту профнепригодна. На ней в принципе невозможно построить нормальное сетевое взаимодействие. Можно только «баловаться» одиночными запросами и ответами. Ни о каком построении серверов на базе этой библиотеки речь не может идти. Почему?
Потому, что эта библиотека имеет встроенный «баг», который подвешивает неодиночные запросы на время от трёх до десяти секунд и более. Баг именно встроенный и автор библиотеки об этом знал, о чём свидетельствует его пометки в исходниках (но об этом несколько позже).
Тут нужно понимать, что библиотека, поставляемая с официальной средой разработки является неким стандартом и если у вас не работает проект, то вы будете искать дефект где угодно, только не в стандартной библиотеке, ведь ей много лет и ею пользуются сотни тысяч, если не миллионы людей. Не могут же они все ошибаться?
Почему в природе не существует серверов на Arduino
Разработка проекта шла своим чередом и дело, наконец, дошло до оптимизации кода и увеличения скорости работы сервера и тут я столкнулся с такой ситуацией: приходящие запросы от браузера принимались и «подвешивались» на время от трёх до десяти секунд, в среднем и до двадцати и более секунд при более интенсивном обмене. Вот скриншот, на котором видно, что аномальная задержка ответов сервера «гуляет» по различным запросам.
Когда я стал разбираться, то выяснилось, что серверу ничто не мешает ответить, но, тем не менее, запрос «висит» девять с лишним секунд, а при другой итерации этот же запрос висит уже около трёх секунд.
Подобные наблюдения повергли меня в глубокую задумчивость и я перекопал весь код сервера (заодно размялся) но дефектов не обнаружил и вся логика вела к «святая святых» библиотеке Arduino Ethernet Library. Но крамольную мысль, что виновата стандартная библиотека я отбрасывал, как неадекватную. Ведь с библиотекой работают не только пользователи, но и огромное количество профессиональных разработчиков. Не могут же они все не видеть столь очевидных вещей?
Забегая вперёд, скажу, что когда выяснилось, что дело именно в стандартной библиотеке, стало понятно почему в природе не существует (нормальных) серверов на Arduino. Потому, что на базе стандартной библиотеки (с которой работает большинство разработчиков) построить сервер в принципе невозможно. Задержка ответа в десять секунд и более выводит сервер из категории собственно серверов и делает его просто (тормозной) игрушкой.
Промежуточный вывод. Это не Ардуино не подходит для построения серверов, а сетевая библиотека ставит крест на очень интересном классе устройств.
Анатомия проблемы
Теперь от лирики давайте перейдём к техническому описанию проблемы и её практическому решению. Для тех, кто не в курсе, стандартное место расположения библиотеки (в Windows):
И первое, что мы рассмотрим, это функция из файла EthernetServer.cpp
Когда я преодолел психологический барьер (под давлением логики) и начал искать дефект в Ethernet Library я начал именно с этой функции. Заметил я и странный комментарий автора, но не придал ему значения. Перелопатив всю библиотеку, я опять, но уже через пару дней и сильно продвинувшись в сетевых технологиях, вернулся к этой функции потому, что логика подсказывала, что проблема именно здесь и более внимательно посмотрел на комментарий.
Друзья, там всё написано открытым текстом. В вольном переводе это звучит примерно так «это работает, но не всегда». Подождите минуточку, что значит «не всегда»? У нас же не воскресный клуб по игре в лото. А когда не работает, то что? А вот когда «не работает» и начинаются проблемы с задержкой в десять секунд.
И автор об этом определённо знал, о чём свидетельствует самооценка его творения — три икса. Без комментариев. Эта библиотека является основой для многих клонов и, внимание, эти три икса кочуют из одного проекта в другой. Если вы разработчик, то не заметить эту проблему можно только не разу не протестировав сетевой обмен. Тоже без комментариев.
Для тех, кто плохо разбирается в коде поясню суть проблемы простыми словами. Цикл перебирает сокеты и, как только находит подходящий, возвращает клиента, а остальных просто игнорирует. И они висят по десять секунд, пока «карты благоприятно не лягут».
Решение проблемы
Поняв причину проблемы, мы конечно не остановимся на этом и попробуем её решить. Для начала давайте перепишем функцию таким образом, чтобы получение или не получение сокета не зависело от воли случая и происходило всегда, если есть свободный. Это решит две проблемы:
- запросы не будут зависать
- «последовательные» запросы превратятся в «параллельные», что значительно ускорит работу
Итак, код новой функции:
Убираем цикл, явным образом указываем сокет и ничего не теряем — если он свободен, то мы гарантированно получаем клиента (если он нам подходит).
То же самое «по цепочке» проделываем с кодом функции accept, убираем цикл и явно указываем сокет.
И не забываем поправить файл EthernetServer.h
Вот, собственно и всё. Мы внесли изменения в стандартную библиотеку и поведение сервера кардинально изменилось. Если раньше всё работало очень медленно, за гранью любых представление о юзабилити, то теперь скорость загрузки страниц значительно возросла и стала вполне приемлемой для нормального использования.
Обратите внимание на уменьшение задержки в 3 — 5 раз для разных файлов и совсем другой характер загрузки, что очень заметно при практическом пользовании.
/*
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 Полный код изменённого EthernetServer.h
/*
Mod for Arduino Mega Server project
fix bug delay answer server
*/
#ifndef ethernetserver_h
#define 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;
>;
Оставшиеся проблемы
В таком виде сервер из демонстрации идеи переходит в категорию вещей, которыми можно пользоваться в повседневной жизни, но остались некоторые проблемы. Как вы видите на скриншоте ещё присутствует не принципиальная, но неприятная задержка в три секунды, которой не должно быть. Библиотека написана так, что мест, где код работает не так, как нужно, очень много и если вы квалифицированный разработчик, то ваша помощь в установлении причины трёхсекундной задержки будет очень ценной. И для проекта Arduino Mega Server и для всех пользователей Arduino.
Последний момент
Поскольку мы изменили код стандартной библиотеки, то и вызывать её функции нужно несколько иным способом. Здесь я привожу код, который реально работает и который обеспечивал работу AMS на скриншоте выше.
Здесь задача перебора сокетов перенесена на уровень клиентского скетча и, что самое главное, и в чём смысл всего вышесказанного, не происходит «подвисания» запросов. И функция собственно сервера:
С полным кодом сервера вы можете ознакомиться, скачав дистрибутив с официального сайта проекта Arduino Mega Server. А задать возникшие вопросы можно на форуме. Осталось решить последнюю проблему трёхсекундной задержки и у нас будет настоящий, быстро работающий сервер на Ардуино. Кстати, скоро выйдет новая версия AMS со всеми исправлениями и улучшениями в которой решена одна из самых актуальных проблем — автономная работа без поддержки сервера MajorDoMo.
И возможным это стало во многом благодаря тем исправлениям стандартной библиотеки Arduino Ethernet Library о которых я вам сейчас рассказал.
Дополнение. Открыт канал на Youtube и вот промо ролик Arduino Mega Server, который демонстрирует работу с реальной системой.