Веб-сервер Arduino: более быстрая альтернатива «indexof» для разбора запросов GET?

У меня есть веб-сервер, успешно работающий на Arduino Mega, но я пытаюсь убедиться, что он анализирует запросы GET как можно быстрее. Вот как выглядит соответствующий код для запросов GET:

String readString;
EthernetClient client = server.available();
if (client) {
  while (client.connected()) {   
    if (client.available()) {
      char c = client.read();

    if (readString.length() < 20) {
      readString += c;
    }
    ...
    ...
      if (readString.indexOf("?a1o") >0){
        mySwitch.send(a1o, 24);
      }
      if (readString.indexOf("?a1c") >0){
        mySwitch.send(a1c, 24);
      }

Скетч проверяет 40 возможных переменных GET. Я запускаю его как локальный веб-сервер с адресом: http://192.168.0.180.

-Есть ли команда быстрее, чем "indexOf", которую я мог бы использовать?

-Я видел некоторую информацию, предполагающую, что иногда "parseInt" может быть быстрее. Есть ли способ заставить это работать, даже если мой локальный базовый URL уже заполнен целыми числами? Я был бы готов преобразовать команды в целые числа (например, «11» вместо «a1o»), если бы был способ использовать с ним parseInt или другую более быструю команду.

Спасибо!

, 👍3

Обсуждение

Можете ли вы опубликовать пример типа ввода, который вы обрабатываете? Возможно, это: http://192.168.0.180?a1o **или** http://192.168.0.180?a1c, или их может быть несколько в строке GET?, @Nick Gammon

Может быть, укажите, что это за 40 переменных GET. Есть ли образец? Например. а1о, а1с, а2о, а2с, а3о, а3с...?, @Nick Gammon

да, именно так это и выглядит: 192.168.0.180?a1o, @Jerry

Есть шаблон, но он не должен быть таким. В настоящее время идет: a1o, a1c, a2o, a2c и т. д. до a5o, a5c. Затем начинается: b1o, b1c и т. д. («o» означает «открыть», а «c» означает «закрыть», это открытие и закрытие различных беспроводных выходов). У меня не было бы проблем с переименованием их всех, например, просто присвоением им всех номеров или четкого шаблона, если бы был способ ускорить синтаксический анализ., @Jerry

Извлеките строку запроса один раз и сохраните ее в переменной. Затем проверьте, является ли эта переменная «a1o» или «a1c» и т. д. Использование [switch-case](https://www.arduino.cc/en/Reference/SwitchCase) будет быстрее, чем целый список операторов if., @Gerben

Спасибо Гербен! Я попытаюсь использовать приведенный ниже код Гаммона, но с добавлением вашего совета о переключателе., @Jerry

Я знаю, что это очень старый пост, но не могли бы вы показать, как анализировать метод POST с вашим кодом? Это должно быть очень полезно для начинающих, как я :-) Большое спасибо! РРТ, @user58497


1 ответ


Лучший ответ:

8

Ну, для быстрого разбора я бы не стал использовать String для начала. Я написал пример (ниже), который избавляется от класса String, а также демонстрирует однократный синтаксический анализ строки.

#include <SPI.h>
#include <Ethernet.h>

// Введите ниже MAC-адрес и IP-адрес вашего контроллера.
byte mac[] = {  0xB3, 0x8D, 0x72, 0x1D, 0xCE, 0x91 };

// Наш IP-адрес
IPAddress ip(10,0,0,241);

// Инициализируем библиотеку сервера Ethernet
// с IP-адресом и портом, который вы хотите использовать
// (порт 80 по умолчанию для HTTP):
EthernetServer server(80);

void setup() 
  {
  // Открытие последовательной связи и ожидание открытия порта:
  Serial.begin(115200);
  while (!Serial) { } // ждем подключения последовательного порта.

  // запускаем соединение Ethernet и сервер:
  Ethernet.begin(mac, ip);
  server.begin();
  Serial.print(F("Server is at "));
  Serial.println(Ethernet.localIP());
  }  // конец настройки

// сколько последовательных данных мы ожидаем перед новой строкой
const unsigned int MAX_INPUT = 100;
// максимальная длина принимаемых параметров
const int MAX_PARAM = 10;

// Пример строки GET: GET /?foo=bar HTTP/1.1
void processGet (const char * data)
  {
  // найти, где начинаются параметры
  const char * paramsPos = strchr (data, '?');
  if (paramsPos == NULL)
    return;  // без параметров
  // найти пробел в конце
  const char * spacePos = strchr (paramsPos, ' ');
  if (spacePos == NULL)
    return;  // место не найдено
  // определить длину параметров
  int paramLength = spacePos - paramsPos - 1;
  // смотрим, не слишком ли длинно
  if (paramLength >= MAX_PARAM)
    return;  // слишком долго для нас
  // копируем параметры в буфер
  char param [MAX_PARAM];
  memcpy (param, paramsPos + 1, paramLength);  // пропустить "?"
  param [paramLength] = 0;  // нулевой терминатор

  // делаем что-то в зависимости от аргумента (параметры GET)

  if (strcmp (param, "foo") == 0)
    Serial.println (F("Activating foo"));
  else if (strcmp (param, "bar") == 0)
    Serial.println (F("Activating bar"));

  }  // конец процессаGet

// здесь для обработки входящих последовательных данных после получения терминатора
void processData (const char * data)
  {
  Serial.println (data);
  if (strlen (data) < 4)
    return;

  if (memcmp (data, "GET ", 4) == 0)
    processGet (&data [4]);
  }  // конец данных процесса

bool processIncomingByte (const byte inByte)
  {
  static char input_line [MAX_INPUT];
  static unsigned int input_pos = 0;
  switch (inByte)
    {
    case '\n':   // конец текста
      input_line [input_pos] = 0;  // завершающий нулевой байт
      if (input_pos == 0)
        return true;   // получили пустую строку
      // терминатор достигнут! обработать input_line здесь...
      processData (input_line);
      // сброс буфера для следующего раза
      input_pos = 0;  
      break;

    case '\r':   // отменить возврат каретки
      break;

    default:
      // продолжаем добавлять, если не полный ... разрешить завершающий нулевой байт
      if (input_pos < (MAX_INPUT - 1))
        input_line [input_pos++] = inByte;
      break;
    }  // конец переключателя
  return false;    // еще нет пустой строки
  } // конец процессаIncomingByte


void loop() 
  {
  // прослушивание входящих клиентов
  EthernetClient client = server.available();
  if (client) 
    {
    Serial.println(F("Client connected"));
    // HTTP-запрос заканчивается пустой строкой
    boolean done = false;
    while (client.connected() && !done) 
      {
      while (client.available () > 0 && !done)
        done = processIncomingByte (client.read ());
      }  // конец, пока клиент подключен

    // отправляем стандартный HTTP-заголовок ответа
    client.println(F("HTTP/1.1 200 OK"));
    client.println(F("Content-Type: text/html"));
    client.println(F("Connection: close"));  // закрыть после завершения ответа
    client.println();   // конец заголовка HTTP
    client.println(F("<!DOCTYPE HTML>"));
    client.println(F("<html>"));
    client.println(F("<head>"));
    client.println(F("<title>Test page</title>"));
    client.println(F("</head>"));
    client.println(F("<body>"));
    client.println(F("<h1>My web page</h1>"));
    client.println(F("<p>Requested actions performed"));
    client.println(F("</body>"));
    client.println(F("</html>"));

    // дать веб-браузеру время для получения данных
    delay(10);
    // закрыть соединение:
    client.stop();
    Serial.println(F("Client disconnected"));
  }  // конец полученного нового клиента
}  // конец цикла

Входящие строки от клиента фиксируются в статическом буфере. Это устраняет проблемы с фрагментацией памяти (вызванные использованием String).

processIncomingByte вызывается для каждого байта от клиента. После сборки всей строки вызывается processData. Если он находит строку «GET», он вызывает processGet. Это ищет параметры в строке GET. Строка GET выглядит так:

GET /?bar HTTP/1.1

Итак, нам нужно попасть между "?" и следующий пробел, чтобы получить параметр (в данном случае "bar"). Этого можно добиться, найдя их позиции с помощью strchr.

Наконец (в качестве простого примера) я проверил с помощью strcmp прямое совпадение различных слов. Возможно, вы могли бы сделать это более эффективным, но я сомневаюсь, что это будет иметь большое значение.

,

Я только что внедрил эти изменения, и это заметно НА ТОННУ быстрее!!!!! Я так счастлив с этим! Большое спасибо за помощь!, @Jerry

Я преобразовал все свои параметры GET в целые числа, чтобы можно было использовать переключатель. Я преобразовал "param" в целое число следующим образом: param2 = atoi(param); а затем я сделал «переключатель (парам2);». Не могли бы вы сказать мне, если это плохой способ делать вещи? Спасибо., @Jerry

Это нормально, это даст вам небольшое увеличение скорости. Если вам понравился мой ответ, пожалуйста, «примите» его, нажав на галочку рядом с ним. Это позволяет другим пользователям узнать, что ответ был полезен. Спасибо!, @Nick Gammon

Спасибо! JSYK, насколько быстрее был ваш код: раньше требовалось 2-3 секунды после нажатия на веб-ссылку, прежде чем активировались беспроводные переключатели. Теперь это занимает всего 200-300 мс. Это очень полезно для меня., @Jerry