Помогите с ускорением загрузки Web-страниц с Arduino
- Войдите на сайт для отправки комментариев
Всем привет!
Нужна помощь в ускорении отображения WEB-страниц в браузере пользователя. Уже наверное все испробовал что знал, но скорость загрузки (отображения) одной страницы плавает в диапазоне 8-25сек. Что очень долго... Пробовал загрузку страниц в браузерах: FireFox, Chrome, Yandex. Везде работает одинаково, часто не отображаются некоторые элементы страницы в виде картинок. Картинок немного и весят мало. Субъективно замедление отображения происходит именно на картинках, сама WEB-страница грузится достаточно быстро.
Использую шилд W5100 на платформе Mega2560. ВЕБ-страницы хранятся на SD-карте. Фрагмент кода загрузчика на Ардуино:
void get_command_builder(String page, String param, String count) //вызываем обработчик команды
{
File webFile;
//если текущий клиент не является авторизованным, то просим пройти авторизацию
if (client_check()==0)
{
glb_auth=0;
}
else
{
glb_auth=1; //клиент уже авторизован
}
//если клиент не авторизован
if (glb_auth==0)
{
//отображаем страницу авторизации
if ((page.indexOf(".htm",0) != -1) || (page=="/") ) page="/index.htm";
}
//в противном случае грузим любую страницу. Но если страница не указана, то грузим страницу по умолчанию.
else if (page=="/") page="/switch.htm";
//загрузчик страниц и элементов ///////////////////////////////////////////////////
webFile = SD.open(page);
if (webFile)
{
glb_client.println(F("HTTP/1.1 200 OK"));
if (page.indexOf(".htm", 0) != -1)
glb_client.println(F("Content-Type: text/html"));
else if (page.indexOf(".css", 0) != -1)
glb_client.println(F("Content-Type: text/css"));
else if (page.indexOf(".png", 0) != -1)
glb_client.println(F("Content-Type: image/png"));
else if (page.indexOf(".jpg", 0) != -1)
glb_client.println(F("Content-Type: image/jpeg"));
else if (page.indexOf(".gif", 0) != -1)
glb_client.println(F("Content-Type: image/gif"));
else if (page.indexOf(".js", 0) != -1)
glb_client.println(F("Content-Type: application/x-javascript"));
//glb_client.println(F("Connnection: close"));
glb_client.println();
//загружаем данные блоками (медиа файлы)
byte cB[1024];
int cC=0;
while (webFile.available())
{
cB[cC]=webFile.read();
cC++;
if(cC > 1023)
{
glb_client.write(cB, 1024);
cC=0;
}
}
if(cC > 0) glb_client.write(cB,cC);
webFile.close();
}
////////////////////////////////////////////////////////////////////////////////////
//обработчик команд управления /////////////////////////////////////////////////////
if (page=="/rcpdu")
{
......
}
}
Результат обработки команд поступивших в Ардуино из браузера отсылается обратно клиенту посредством AJAX запросов. Заменяются данные только содержимого нужного участка WEB-страницы, без ее перезагрузки целиком.
Фрагмент JS обработчика:
function get_information()
{
var request = new XMLHttpRequest();
request.onreadystatechange = function()
{
if (this.readyState == 4)
{
if (this.status == 200)
{
if (this.responseText != null)
{
document.getElementById("inf_block").innerHTML=this.responseText;
}
}
}
}
request.open("GET", "rcpdu?get_information", true);
request.send(null);
}
Собственно фрагмент кода нужного участка ВЕБ-страницы для отображения полученных данных, тут все просто:
<div class="inf_block" id="inf_block" align="center"> </div>
Вообщем пока грешу только на скорость отображения содержимого страницы, может чтение с SD-карты медленное, может определение типа контента при загрузке не в том порядке определяю. Не знаю короче... (((((( Прошу помощи.
Да, сама страничка выглядит так (из графики там только кнопки):
Если вкратце, то - стандартная библиотека Ethernet - кривая. Попробуйте помедитировать над вот этим куском кода:
EthernetClient EthernetServer::available() { accept(); for (int sock = 0; sock < MAX_SOCK_NUM; sock++) { EthernetClient client(sock); if (EthernetClass::_server_port[sock] == _port) { uint8_t s = client.status(); if (s == SnSR::ESTABLISHED || s == SnSR::CLOSE_WAIT) { if (client.available()) { // XXX: don't always pick the lowest numbered socket. return client; } } } } return EthernetClient(MAX_SOCK_NUM); }Там даже комментарий об этой кривости есть. Криворукая библиотека не перебирает нормально клиентов, и, хотя есть буфер на 4 клиента, по сути, бОльшую часть времени оно ждёт, когда первый клиент просрётся.
Выход - переписывать.
Спасибо DIYMan, посмотрю библиотеку тоже... :) Еще будут у кого мысли на этот счет?
Кстати, я этих четырех клиентов программно отлавливаю по их IP, использовал такую конструкцию:
//////////////////////////////////////////////////////////////////////////////////// int client_check() { byte rip[]={0,0,0,0}; glb_client.getRemoteIP(rip); //Определяем IP клиента /*Serial.println("Client IP:"); for (int i= 0; i < 4; i++) { Serial.print(rip[i], DEC); if (i<3) Serial.print("."); }*/ //ищем клиента в структуре int k; int m=0; int n=0; for (int j=0; j<4; j++) { //проверяем байты IP адреса k=0; for (int i=0; i<4; i++) { if( glb_client_ip[j].ip[i] == rip[i]) k++; //нашли совпадение, +1 к счетчику } //если клиент уже был авторизован (найдена запись IP адреса, все 4-байта) if ( (k==4) && (glb_client_ip[j].access==1) ) { m=1; //ставим флаг, что клиент найден glb_client_ip[j].times=EEPROM.read(67); //обновляем счетчик LOGOUT активности клиента break; } } return m; } //////////////////////////////////////////////////////////////////////////////////// void client_logout_time() { String str; if ( ((millis()-glb_time)>59990) || (glb_time>millis())) //прошла 1 минута или если произошло переполнение счетчика { glb_time=millis(); //фиксируем новое время //обновляем записи авторизованных пользователей for (int j=0; j<4; j++) { glb_client_ip[j].times--; //если время авторизации истекло if (glb_client_ip[j].times==0) { glb_client_ip[j].access=0; //закрываем доступ для повторной авторизации //стираем значение IP адреса клиента str=""; for (int i=0; i<4; i++) { str+=String(glb_client_ip[j].ip[i]); if (i<3) str+="."; glb_client_ip[j].ip[i]=0; } sd_save("client_logout_time -> "+str, EEPROM.read(66)); } } } } //////////////////////////////////////////////////////////////////////////////////// void client_logout() { String str; byte rip[]={0,0,0,0}; glb_client.getRemoteIP(rip); //Определяем IP клиента //ищем текущего клиента в структуре int k; for (int j=0; j<4; j++) { //проверяем байты IP адреса k=0; for (int i=0; i<4; i++) { if( glb_client_ip[j].ip[i] == rip[i]) k++; //нашли совпадение, +1 к счетчику } if (k==4) { glb_client_ip[j].access=0; //закрываем доступ для повторной авторизации glb_client_ip[j].times=0; //сбрасываем счетчик времени //стираем значение IP адреса клиента str=""; for (int i=0; i<4; i++) { str+=String(glb_client_ip[j].ip[i]); if (i<3) str+="."; glb_client_ip[j].ip[i]=0; } sd_save("client_logout -> "+str, EEPROM.read(66)); break; } } } //////////////////////////////////////////////////////////////////////////////////// void client_add() { String str; byte rip[]={0,0,0,0}; glb_client.getRemoteIP(rip); //Определяем IP клиента //проверяем свободные записи в буфере авторизации for (int j=0; j<4; j++) //ищем свободную запись структуры для нового клиента { if( glb_client_ip[j].access==0 ) //нашли свободную запись { //вносим IP нового клиента в структуру str=""; for (int i=0; i<4; i++) { str+=String(rip[i]); if (i<3) str+="."; glb_client_ip[j].ip[i] = rip[i]; } sd_save("client_add -> "+str, EEPROM.read(66)); glb_client_ip[j].times=EEPROM.read(67); //обновляем счетчик LOGOUT активности клиента glb_client_ip[j].access=1; //забираем свободную запись, ставим флаг авторизации клиента break; } } }В Ethernet библиотеке нужно тоже функцию вставить getRemoteIP(), для получения IP-клиента. )))) Отчасти извращение, но все же...
Проблема (и частичное решение) с библиотекой Ethernet изложены тут https://geektimes.ru/post/259898/
Я использовал такой код для вывода страниц из файла
void FileToWebPage(EthernetClient& client) { while (webFile.available()) { int rsize; byte bbuffer[BUFFER_SIZE]; rsize = webFile.read((char*)bbuffer, BUFFER_SIZE); if (!rsize) {break;} client.write(bbuffer, rsize); } }Спасибо alexvs, попробую! :)
alexvs, ты пробовал вносить изменения в библиотеку по ссылке указанной тобой. Я закоментировал старые ф-ции и начал использовать измененные с префиксом нижнего подчеркивания на конце. Полезла куча ошибок при компиляции...
Например, объявлял раньше сервер и его порт:
Теперь выдает ошибку - cannot declare variable 'glb_server' to be of abstract type 'EthernetServer'. Не хочет объявлять сервер. При этом ф-ция virtual void begin() в обоих файлах EthernetServer опечатана использую только begin_(). Если использовать базовую ф-цию begin(), то все работает, хотя производительности никакой не увидел. Указываю нулевой сокет glb_server.begin_(0) и в теле loop() функцию с сокетом glb_client = glb_server.available_(0), все работает но без видимых изменений. Ты пробовал измененную библиотеку использовать, что я не так делаю?
begin обязана быть определена, т.к. она объявлена как виртуальная в классе Server, от которого наследуется EthernetServer. Т.е. просто так закомментировать её - не выйдет. Выход - оставляйте тело родного begin пустым.
begin обязана быть определена, т.к. она объявлена как виртуальная в классе Server, от которого наследуется EthernetServer. Т.е. просто так закомментировать её - не выйдет. Выход - оставляйте тело родного begin пустым.
Это я уже понял, использую begin_(). :)
Попоробовал в работе новую функцию по загрузке данных с SD-карты. Картинки стали грузиться гораздо быстрее. Причем как ни странно все вполне сносно работает в браузере Firefox, в других браузерах: Chrome и Yandex проблемы с отображением. Страница все равно медленно грузиться... точнее ее области с данными. Такое ощущение что AJAX запросы долго ждут своего выполнения в очереди перед отправкой данных браузеру. Я использую около 8-ми AJAX запросов, для отображения данных на странице. При этом суммарный объем передаваемых данных около 30символов (~30байт). Хотя сама страница "тело" полностью грузится, почти мгновенно и при этом она весит 21Кб. Тут точно AJAX запросы долго обрабатываются, я реально не знаю что с этим делать. :( Я пробовал так же все данные одним AJAX запросом передавать и на стороне клиента с помощью JScript данные распределять по областям страницы (разбор строки данных). Но сути это не меняет, тоже медленно работает.
Firefox как-то не сильно критичен к задержкам пакетов - дожидается, а вот остальные браузеры - ну, раз не пришло, значит потерялось.. больше не жду. )))))
П.С. Использование одного сокета (вместо 4-х) с измененной библиотекой как-то не помогло.. в плане скорости передачи данных.
Во-первых, надо знать, чего вы там в библиотеке наколбасили ;) По-хорошему, там не надо дописывать никаких своих функций, достаточно ввести одну переменную с номером последнего проверенного клиента, и чуть переписать функцию available, чтобы она начинала опрос массива клиентов не с первого клиента каждый раз ;)
По поводу отдачи в браузер: взрослые браузеры открывают кучу соединений, если в странице много включений (рисунки, стили, скрипты, многие сразу запрашивают favicon) - по итогу все четыре клиентских слота забиваются махом. Выход - закрывать клиента сразу, как только он отдал все данные (или не нашёл контента запрошенного) - чтобы не ждать, пока соединение само закроется по таймауту.
Но всё это секс, который для серьёзной вебморды неприемлем так или иначе. Именно по этой причине я не держу вебморду на SD (да и вообще на МК где бы то ни было) - а юзаю или дешёвенький роутер, или, на период разработки - вебморда крутится на взрослом компе. А всё общение с МК идёт простенькими пакетами.
И всё летает аж пиджак заворачивается ;) И графики можно взрослые строить, и с jQuery проблем нету, и вообще - нигде ничего не жмёт :)
Видео про старую версию вебморды (там ещё графиков нету и много чего ещё нету): https://www.youtube.com/watch?v=EwMbdgM9kQc&index=6&list=PLky0v6EmdStCQi3XgBMVkbZeycrIpZnW_
Сам проект: https://github.com/Porokhnya/GreenHouseProject
Видео про старую версию вебморды (там ещё графиков нету и много чего ещё нету): https://www.youtube.com/watch?v=EwMbdgM9kQc&index=6&list=PLky0v6EmdStCQi3XgBMVkbZeycrIpZnW_
Проект конечно хороший, но это решение из другой эпостаси... ;) Умный дом и все такое... Там не нужна веб морда на МК, за все отвечает централизованный узел (в роли ПК). В данном случае о многом можно не думать, все действительно летает, я уже это проходил. ))))
Меня сейчас интересует полностью автономное устройство, для продажи в дальнейшем, поэтому и гемороя столько... ))) Пока буду копаться в сторону оптимизации содержимого JS в html файлах и переделки AJAX запросов, методом проб и ошибок. Где-то там видимо собака зарылась... Вчера добился задержки загрузки страницы до 10..15сек, уже лучше но все равно еще много. ;)