Arduino Mega Server

Модератор: Alex

Alex
Сообщения: 2357
Зарегистрирован: Пт апр 20, 2012 12:53 pm
Благодарил (а): 42 раза
Поблагодарили: 262 раза

Re: Arduino Mega Server

Сообщение Alex » Вт авг 25, 2015 10:18 pm

Итак, нормально всё быть никак не может, обязательно вылезет какая-нибудь гадость.

Похоже товарищ olehs был прав и нужно всё-таки довести до ума работу с Ethernet.

Пока в качестве временной меры можно применить костыль:

Код: Выделить всё

void serverWorks() {
  for (int sock = 0; sock < MAX_SOCK_NUM - 1; sock++) {
    EthernetClient sclient = server.available_(sock);
    serverWorks2(sclient);
  }
} 
Вроде бы и скорость не упала и данные стали отправляться. Тестируем и докладываем о результатах. А товарищ olehs и если кто ещё найдётся компетентный думают о том, как это всё-таки привести в нормальный рабочий вид. :)
annakin
Сообщения: 130
Зарегистрирован: Пн окт 28, 2013 5:06 pm
Откуда: Молдова
Благодарил (а): 11 раз
Поблагодарили: 10 раз

Re: Arduino Mega Server

Сообщение annakin » Вт авг 25, 2015 10:29 pm

Alex писал(а):Итак, нормально всё быть никак не может, обязательно вылезет какая-нибудь гадость.

Похоже товарищ olehs был прав и нужно всё-таки довести до ума работу с Ethernet.

Пока в качестве временной меры можно применить костыль:

Код: Выделить всё

void serverWorks() {
  for (int sock = 0; sock < MAX_SOCK_NUM - 1; sock++) {
    EthernetClient sclient = server.available_(sock);
    serverWorks2(sclient);
  }
}
Вроде бы и скорость не упала и данные стали отправляться. Тестируем и докладываем о результатах. А товарищ olehs и если кто ещё найдётся компетентный думают о том, как это всё-таки привести в нормальный рабочий вид. :)
Завтра попробую с новой процедурой что вы выложили посмотрим будет работать или нет, а пока мега бегает на старой версии АМС.
Server: Raspberry Pi 3 B+
OS: Rasbian
Alex
Сообщения: 2357
Зарегистрирован: Пт апр 20, 2012 12:53 pm
Благодарил (а): 42 раза
Поблагодарили: 262 раза

Re: Arduino Mega Server

Сообщение Alex » Ср авг 26, 2015 12:53 am

Код: Выделить всё

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);
}
olehs, вы пишите:
Он говорит, что не стоит возвращать первый сокет в подходящем состоянии, а желательно проверить, есть ли в нем запрос. Это позволяет обработать другие сокеты, пока клиент после подключения надумает отправить запрос.
Как я понимаю что происходит:

Цикл начинает перебирать сокеты и как только находит первый подходящий, возвращает его, прерывая цикл.

Правильно?

Если подходящим оказался первый сокет, то остальные продолжают висеть до следующего прихода алгоритма в эту точку и таким образом может попасться «невезучий» запрос, который будет висеть несколько «приходов». А поскольку приходы в эту точку могут случаться с задержкой на 1 - 2 секунды (из-за работы контроллера по своему прямому назначению), то запрос и не обрабатывается подолгу и висит.

Так? Или не так?

Если это так, то этот алгоритм работы безусловно порочен и так не должно быть. Это ГАРАНТИЯ безумных тормозов любых неодиночных запросов.
olehs
Сообщения: 1115
Зарегистрирован: Вс июн 14, 2015 11:08 am
Благодарил (а): 85 раз
Поблагодарили: 342 раза

Re: Arduino Mega Server

Сообщение olehs » Ср авг 26, 2015 1:31 am

Любая реализация серверов открывает на прослушку только один сокет. Когда к нему подключается клиент, сокет передается в поток для обработки, а затем следующий сокет открывается на прослушку.
Ethernet-библиотека также рассчитана на работу с одним сокетом одновременно (а точнее как можно с меньшим количеством), т.к. она однопоточна.
В W5100 - это наиболее критично - там сокетов всего 4.

Что действительно порочно, так это занимать их сразу, даже когда нет запросов на коннект.
А если вы захотите, например, поднять для каких-то сетевых датчиков еще один выделенный сервер на отдельном порту? У вас уже ничего не получиться - первый сервер займет все сокеты.

Догадываетесь, почему перестали отправляться данные в МД? Сокеты нужны не только серверу ;)
Alex
Сообщения: 2357
Зарегистрирован: Пт апр 20, 2012 12:53 pm
Благодарил (а): 42 раза
Поблагодарили: 262 раза

Re: Arduino Mega Server

Сообщение Alex » Ср авг 26, 2015 7:53 am

Догадываетесь, почему перестали отправляться данные в МД? Сокеты нужны не только серверу ;)
Это понятно. Именно поэтому мне понадобилось меньше минуты, чтобы догадаться куда нужно поставить -1, чтобы всё заработало. Но сейчас вопрос не в этом. Мне бы хотелось получить от вас чёткие ответы по разбору алгоритма выше. Я правильно описал то что там происходит? Конкретно по шагам.

Кстати, хоть и в кривом варианте, но с костылём и скорость приличная и запросы отправляются, так что в принципе возможно написать алгоритм когда всё работает как надо.

И ещё один момент. В данном случае скорость является приоритетом (потому, что нет скорости — нет проекта). Никому не нужна на 1000% корректная библиотека, которая заставляет ждать загрузки страниц по 35 секунд. Всех более чем устроит кривая, но позволяющая нормально пользоваться АМС.

В данном варианте не занимается 1 сокет и оставляется для прочих нужд. Это не лучший вариант, но так по крайней мере работает.

Ещё раз: правильно ли я описал то что происходит по шагам?
timapple
Сообщения: 4
Зарегистрирован: Пн авг 24, 2015 1:15 pm
Благодарил (а): 0
Поблагодарили: 1 раз

Re: Arduino Mega Server

Сообщение timapple » Ср авг 26, 2015 10:11 am

Всем привет.
После изучения стандартной библиотеки Ethernet, драйвера W5100 и спек HTTP пришел к таким выводам:
1) Ethernet действительно не вполне правильно работает
2) Изменения Alex'а приводят к улучшению ситуации, но не решают проблему как таковую
3) Сервер AMS имеет недочеты реализации HTTP

Как же работает оригинальная библиотека Ethernet в режиме сервера:
оригинальная библиотека EthernetПоказать
Скетч в цикле вызывает EthernetServer::available(), в котором:
  • EthernetServer::accept() - закрываются ненужные сокеты и проверяется, есть ли хотябы один слушающий сокет. Если таковых нет - вызывается EthernetServer::begin()
  • ищется сокет с данными от клиента и возвращается скетчу. Причем, если таких сокетов несколько, то всегда выбирается с минимальным номером, т.е. без учета очередности подключения клиентов. Вот об этом тот самый комментарий XXX
EthernetServer::begin() - открывает первый свободный (CLOSED) сокет на прослушивание (LISTEN).
А теперь вспомним, что скетч однопоточный, т.е. запросы от клиентов (сокеты) обрабатываются по очереди.
Сложив всё вместе получаем такую ситуацию:
  • браузер запрашивает одновременно несколько файлов
  • открытый (LISTEN) сокет принимает первый запрос до заполнения буферов и ждёт когда его обработает скетч
  • остальные запросы браузера не принимаются.
А вот так работает измененная Alex'ом библиотека Ethernet в режиме сервера:
измененная библиотека EthernetПоказать
Скетч в цикле вызывает EthernetServer::available_(int sock) для каждого сокета по очереди, в котором:
  • EthernetServer::accept_(int sock) - закрывается ненужное соединение и если сокет не в режимы прослушивания - вызывается EthernetServer::begin_(int sock)
  • если есть данные от клиента, то возвращается скетчу для обработки
EthernetServer::begin_(int sock) - открывает сокет на прослушивание (LISTEN).
А теперь вспомним, что скетч однопоточный, т.е. запросы от клиентов (сокеты) обрабатываются по очереди.
Сложив всё вместе получаем такую ситуацию:
  • на сервере открыто несколько сокетов
  • браузер запрашивает одновременно несколько файлов
  • открытые (LISTEN) сокеты одновременно принимают запросы до заполнения буферов и ждут когда их обработает скетч
  • остальные запросы браузера не принимаются
  • скетч получает запрос с очередного сокета и обрабатывает, остальные ждут
  • после обработки первого - запрашивает следующий
вот тут может возникнуть проблема:
обработка запроса может быть слишком долгой (отдача файла например) - тогда остальные ожидающие сокеты могут прерваться по таймауту, и браузер попробует их повторить. да вот только сокетов свободных может и не быть. в любом случае - браузеру ничего другого не остается как ждать
Заслуга Alex'а в том, что он изменил цикл опроса на свой, и задействовал несколько сокетов на прием сразу. Сокеты перебираются по очереди (1-2-3-4-1-...). Т.о. от браузера принимаются сразу несколько запросов и W5100 считывает их в буфер одновременно. Поэтому браузеру для первых 3-4 запросов не требуется ждать и переподключаться, что заметно улучшает открывание сайта.
Но из-за однопоточности и достаточно долгой обработки одного запроса - остальные отрабатываются всеравно с задержкой и достижение таймаута всё еще возможно.

Немного про работу браузера. После получения кода страницы (html), он анализирует все ссылки (стили, скрипты, картинки) и отправляет запросы на сервер (не все сразу, но не менее 10-30 в зависимости от настроек). Но у W5100 всего 4 сокета (и то не факт, что слушают все). Первые несколько принимаются, а осталым отказ. Браузеру ничего другого не остается, кроме как ждать и снова посылать запросы.

Как видите, проблема главным образом из-за ограничений платформы:
  • однопоточность
  • ограниченное кол-во сокетов
Причем увеличение кол-ва сокетов фактически пользы не даст - обрабатываться всеравно будут по очереди.

Т.о. нужно направить силы на оптимизацию работы через один сокет:
  • как можно быстрее обрабатывать запрос
  • уменьшить кол-во запросов со стороны браузера (склеивание файлов какраз для этого)
[/list][/list]

А еще браузер, как правило, отправляет запрос с заголовком "Connection: keep-alive", т.е. предлагает не закрывать соединение сразу, а оставлять открытым для последующих запросов. AMS в свою очередь обрабатывает очередной сокет и обрывает соединение (sclient.stop()). Браузер снова негодует, т.к. закрытие происходит без предупреждения (хотя я не уверен, что это плохо). Т.ч. желательно в ответ всегда включать заголовок "Connection: close" и спокойно закрывать соединение.

P.S. пост был отредактирован после замечаний olehs.
Последний раз редактировалось timapple Ср авг 26, 2015 11:55 am, всего редактировалось 1 раз.
За это сообщение автора timapple поблагодарил:
olehs (Ср авг 26, 2015 12:14 pm)
Рейтинг: 1.16%
timapple
Сообщения: 4
Зарегистрирован: Пн авг 24, 2015 1:15 pm
Благодарил (а): 0
Поблагодарили: 1 раз

Re: Arduino Mega Server

Сообщение timapple » Ср авг 26, 2015 10:32 am

Остается открытым вопрос, как реагирует браузер на недоступность свободных сокетов при попытке отправить очередной запрос. Логично подумать, что ждет n секунд и пытается снова. Возможно это одна из причин большого общего времени открытия страницы.
Это еще один повод для того, чтобы постараться отдать всю страницу (вместе со стилями, скриптами и картинками) одним или хотябы не более чем 4 запросами.
Alex
Сообщения: 2357
Зарегистрирован: Пт апр 20, 2012 12:53 pm
Благодарил (а): 42 раза
Поблагодарили: 262 раза

Re: Arduino Mega Server

Сообщение Alex » Ср авг 26, 2015 10:41 am

из-за особенности реализации метода available() - следующим может быть обработан не второй по очереди запрос, а только что поступивший на освободившийся первый :!: , т.е. при интенсивной бомбежке сервера браузером - часть запросов на 2-4 сокетах могут игнорироваться очень долго
Вот об этом я и твержу уже очень долго. Это КРЕСТ (ХХХ) на нормальной работе, если запросы не единичные.
уменьшить кол-во запросов со стороны браузера (склеивание файлов какраз для этого)
Это уже сделано. При особом желании можно всё запихнуть в одну страницу и один запрос. Но остаётся ещё проблема с потоковым Ajax-ом.
Т.ч. желательно в ответ всегда включать заголовок "Connection: close"
В 0.12 версии так и делается, в 0.11 этого не было.

--------------------

Выход:

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

Это приведёт к тому, что у веб-интерфейса будет приоритет, а в перерывах будут работать другие сервисы.

Или оставить как есть: 3 сокета у веб-интерфейса, 1 у всех остальных, благо там только одиночные команды гуляют.

Можно ещё переходить на «форсированный» режим при подключении браузера и откатываться на стандартный при отключении (ведь веб-интерфейс не всегда подключен)
Последний раз редактировалось Alex Ср авг 26, 2015 10:49 am, всего редактировалось 1 раз.
olehs
Сообщения: 1115
Зарегистрирован: Вс июн 14, 2015 11:08 am
Благодарил (а): 85 раз
Поблагодарили: 342 раза

Re: Arduino Mega Server

Сообщение olehs » Ср авг 26, 2015 10:47 am

Спасибо вам, вы собрали в кучу все наши с Алексом исследования на последних n страницах, и можно еще раз все осознать. Но есть пару замечаний.
timapple писал(а): Как же работает библиотека Ethernet в режиме сервера:
Скетч в цикле вызывает EthernetServer::available(), в котором:
  • EthernetServer::accept() - закрываются ненужные сокеты и проверяется, есть ли хотябы один слушающий сокет. Если таковых нет - вызывается EthernetServer::begin()
  • ищется сокет с данными от клиента и возвращается скетчу. Причем, если таких сокетов несколько, то всегда выбирается с минимальным номером, т.е. без учета очередности подключения клиентов. Вот об этом тот самый комментарий XXX
EthernetServer::begin() - открывает все свободные (CLOSED) сокеты на прослушивание (LISTEN)
вот тут Вы ошиблись.
begin() открывает только один сокет на прослушивание. Но да, в один момент времени может быть несколько сокетов в статусе ESTABLISHED, но только один в LISTEN.
  • из-за особенности реализации метода available() - следующим может быть обработан не второй по очереди запрос, а только что поступивший на освободившийся первый :!: , т.е. при интенсивной бомбежке сервера браузером - часть запросов на 2-4 сокетах могут игнорироваться очень долго
и вот это не совсем корректно. Шансы, что сокет, который только что был переведен в статус LISTEN сразу же (за пару инструкций процессора) получит входящий коннект, да еще и сданными ничтожно малы. Для этого клиент должен попасть своим SYN пакетом четко в это промежуток и сразу же запихнуть данные
olehs
Сообщения: 1115
Зарегистрирован: Вс июн 14, 2015 11:08 am
Благодарил (а): 85 раз
Поблагодарили: 342 раза

Re: Arduino Mega Server

Сообщение olehs » Ср авг 26, 2015 11:12 am

Предположим, сервер образно организован стандартным образом

Код: Выделить всё

while(1)
  if(available()) {
    processRequest();
    stop();
  } 
Я приведу пример алгоритма, по которому МОЖЕТ отработать система, но это частный случай, т.к. все будет зависеть, в какой момент браузер будет отсылать каждый запрос. Предположим, браузер отправил один запрос, а затем через несколько миллисекунд еще два
Итерация1.
нет слушающих сокетов. делаем begin() - открывается сокет 1
Итерация2.
сокет 1 - LISTEN. запросов нет
...
Итерация 10 например.
к сокету 1 подключились (ESTABLISHED), потому нет больше слушающих сокетов. делам begin() - открываем сокет 2 (LISTEN)
client.avaliable() = false - предположим, браузер еще не успел отправить текст запроса
Итерация 11.
сокет 1 ESTABLISHED. сокет2 LISTEN. данных на сокете 1 нет. (та же ситуация)
...
Итерация 14.
сокет 1 ESTABLISHED. сокет2 ESTABLISHED - коннект запроса №2. делам begin() - открываем сокет 3 (LISTEN). Есть данные на сокете 1, возвращаем его
обрабатываем processRequest() и закрываем stop().
Итерация 15.
сокет 1 CLOSED. сокет 2 ESTABLISHED. сокет 3 ESTABLISHED. делам begin() - открываем сокет 1!!! (LISTEN). Есть данные на сокете 2, возвращаем его
обрабатываем processRequest() и закрываем stop().
Итерация 16.
сокет 1 LISTEN. сокет 2 CLOSED. сокет 3 ESTABLISHED. Есть данные на сокете 3, возвращаем его
обрабатываем processRequest() и закрываем stop().
Итерация 17.
сокет 1 LISTEN. сокет 2 CLOSED. сокет 3 CLOSED. Переходим на Итерацию 2.

Еще раз хочу акцентировать внимание, что это один из возможных алгоритмов, т.к. все зависит от занятости процессора наложенной на задержки между запросами от браузера.

Как видите, стандартная библиотека корректно отработает 3 практически одновременных запроса, никаких "невезучих" не получилось.

А вот если на Итерации 10 запрос придет сразу, а processRequest() занимает время - Запрос №3 уйдет в TCP Retransmission.

И вот тут изменение Alex'ом метода begin(), которое открывает все сокеты сразу не даст запросу №3 уйти в таймаут.
Но модификация самого available(), как и всех остальных, в такой ситуации эффекта не дает. Т.е. теоретически можно убрать break в begin() для достижения того же результата. Но такая модификация мешает другим частям скетча, работающим с сокетами.

UPD
Чуть лишнего скопипастил. убрал шаг 16 и сдвинул вверх

Рассмотрим ситуацию, когда на шаге 15 processRequest() затяжной, и в это время придет запрос №4. В таком случае он займет сокет 1 и начнет обрабатываться на шаге 16, а запрос №3 будет висеть. Тогда перебор Alex'ом сокетов из скетча как раз и спасет запрос №3.
Последний раз редактировалось olehs Ср авг 26, 2015 11:40 am, всего редактировалось 2 раза.
Ответить