Помогите с ускорением загрузки Web-страниц с Arduino

dbf-334
Offline
Зарегистрирован: 13.11.2015

Всем привет!

Нужна помощь в ускорении отображения 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-карты медленное, может определение типа контента при загрузке не в том порядке определяю. Не знаю короче... (((((( Прошу помощи.

Да, сама страничка выглядит так (из графики там только кнопки):

https://cloud.mail.ru/public/LdUG/nnsXj89gi

DIYMan
DIYMan аватар
Offline
Зарегистрирован: 23.11.2015

Если вкратце, то - стандартная библиотека 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 клиента, по сути, бОльшую часть времени оно ждёт, когда первый клиент просрётся. 

Выход - переписывать.

dbf-334
Offline
Зарегистрирован: 13.11.2015

Спасибо 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-клиента. )))) Отчасти извращение, но все же...

alexvs
Offline
Зарегистрирован: 22.07.2014

Проблема (и частичное решение) с библиотекой 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);
  }
}

 

dbf-334
Offline
Зарегистрирован: 13.11.2015

Спасибо alexvs, попробую! :)

dbf-334
Offline
Зарегистрирован: 13.11.2015

alexvs, ты пробовал вносить изменения в библиотеку по ссылке указанной тобой. Я закоментировал старые ф-ции и начал использовать измененные с префиксом нижнего подчеркивания на конце. Полезла куча ошибок при компиляции...

Например, объявлял раньше сервер и его порт:

EthernetServer glb_server(80);
glb_server.begin_(0); //запускаем сервер

Теперь выдает ошибку - 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), все работает но без видимых изменений. Ты пробовал измененную библиотеку использовать, что я не так делаю?

DIYMan
DIYMan аватар
Offline
Зарегистрирован: 23.11.2015

begin обязана быть определена, т.к. она объявлена как виртуальная в классе Server, от которого наследуется EthernetServer. Т.е. просто так закомментировать её - не выйдет. Выход - оставляйте тело родного begin пустым.

dbf-334
Offline
Зарегистрирован: 13.11.2015

DIYMan пишет:

begin обязана быть определена, т.к. она объявлена как виртуальная в классе Server, от которого наследуется EthernetServer. Т.е. просто так закомментировать её - не выйдет. Выход - оставляйте тело родного begin пустым.

Это я уже понял, использую begin_().   :) 

Попоробовал в работе новую функцию по загрузке данных с SD-карты. Картинки стали грузиться гораздо быстрее. Причем как ни странно все вполне сносно работает в браузере Firefox, в других браузерах: Chrome и Yandex проблемы с отображением. Страница все равно медленно грузиться... точнее ее области с данными. Такое ощущение что AJAX запросы долго ждут своего выполнения в очереди перед отправкой данных браузеру. Я использую около 8-ми AJAX запросов, для отображения данных на странице. При этом суммарный объем передаваемых данных около 30символов (~30байт). Хотя сама страница "тело" полностью грузится, почти мгновенно и при этом она весит 21Кб. Тут точно AJAX запросы долго обрабатываются, я реально не знаю что с этим делать. :( Я пробовал так же все данные одним AJAX запросом передавать и на стороне клиента с помощью JScript данные распределять по областям страницы (разбор строки данных). Но сути это не меняет, тоже медленно работает.

Firefox как-то не сильно критичен к задержкам пакетов - дожидается, а вот остальные браузеры - ну, раз не пришло, значит потерялось.. больше не жду. )))))

П.С. Использование одного сокета (вместо 4-х) с измененной библиотекой как-то не помогло.. в плане скорости передачи данных.

DIYMan
DIYMan аватар
Offline
Зарегистрирован: 23.11.2015

Во-первых, надо знать, чего вы там в библиотеке наколбасили ;) По-хорошему, там не надо дописывать никаких своих функций, достаточно ввести одну переменную с номером последнего проверенного клиента, и чуть переписать функцию available, чтобы она начинала опрос массива клиентов не с первого клиента каждый раз ;)

По поводу отдачи в браузер: взрослые браузеры открывают кучу соединений, если в странице много включений (рисунки, стили, скрипты, многие сразу запрашивают favicon) - по итогу все четыре клиентских слота забиваются махом. Выход - закрывать клиента сразу, как только он отдал все данные (или не нашёл контента запрошенного) - чтобы не ждать, пока соединение само закроется по таймауту.

Но всё это секс, который для серьёзной вебморды неприемлем так или иначе. Именно по этой причине я не держу вебморду на SD (да и вообще на МК где бы то ни было) - а юзаю или дешёвенький роутер, или, на период разработки - вебморда крутится на взрослом компе. А всё общение с МК идёт простенькими пакетами.

И всё летает аж пиджак заворачивается ;) И графики можно взрослые строить, и с jQuery проблем нету, и вообще - нигде ничего не жмёт :)

Видео про старую версию вебморды (там ещё графиков нету и много чего ещё нету): https://www.youtube.com/watch?v=EwMbdgM9kQc&index=6&list=PLky0v6EmdStCQi3XgBMVkbZeycrIpZnW_

Сам проект: https://github.com/Porokhnya/GreenHouseProject

dbf-334
Offline
Зарегистрирован: 13.11.2015

DIYMan пишет:

Видео про старую версию вебморды (там ещё графиков нету и много чего ещё нету): https://www.youtube.com/watch?v=EwMbdgM9kQc&index=6&list=PLky0v6EmdStCQi3XgBMVkbZeycrIpZnW_

Проект конечно хороший, но это решение из другой эпостаси... ;) Умный дом и все такое... Там не нужна веб морда на МК, за все отвечает централизованный узел (в роли ПК). В данном случае о многом можно не думать, все действительно летает, я уже это проходил. )))) 

Меня сейчас интересует полностью автономное устройство, для продажи в дальнейшем, поэтому и гемороя столько... ))) Пока буду копаться в сторону оптимизации содержимого JS в html файлах и переделки AJAX запросов, методом проб и ошибок. Где-то там видимо собака зарылась... Вчера добился задержки загрузки страницы до 10..15сек, уже лучше но все равно еще много. ;)