Как заменить объекты String массивами символов, продолжая использовать строковые методы
Я запускаю следующий код на своем ESP8266 (AI-Thinker ESP8266MOD).
Я отправляю HTTP-запрос GET, и на выводе устанавливается высокий уровень на 1/2 секунды.
Однако через некоторое время (иногда 1 час, 2 часа, 12 часов, совершенно случайно) он перестает отвечать на HTTP-запросы. Я думаю, это может быть из-за фрагментации кучи из-за объектов String, которые я использую.
Вопросы:
Как заменить request
(в loop()
), s
и text
(в GenerateResponse()
) объекты с массивами символов?
После замены объектов String массивами символов, как я могу использовать метод indexOf()
и оператор +
для массивов символов?
Поможет ли этот подход с фрагментацией кучи?
Можно ли узнать, является ли проблема фрагментацией кучи?
#include <ESP8266WiFi.h>
const char* ssid = "myWifi";
const char* wifiPassword = "y76ggS";
const char* passwordToOpenDoor = "/81"; //пароль должен начинаться с косой черты
const int doorPin = 5;
WiFiServer server(301); //Выберите любой номер порта, который вам нравится
WiFiClient client;
void setup() {
Serial.begin(115200);
delay(10);
Serial.println(WiFi.localIP());
pinMode(doorPin, OUTPUT);
digitalWrite(doorPin, 0);
Serial.println();
Serial.print("Connecting to ");
Serial.println(ssid);
WiFi.begin(ssid, wifiPassword);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("");
Serial.println("WiFi connected");
server.begin();
Serial.println("Server started. Diagnostics info:");
Serial.println(WiFi.localIP());
}
void loop() {
client = server.available();
if (!client) {
return;
}
while(!client.available()){
delay(1);
}
String request = client.readStringUntil('\r');
client.flush();
Serial.println(request);
if (request.indexOf(passwordToOpenDoor) != -1) { //Верный ли пароль?
GenerateResponse("Password is correct");
OpenDoor();
}
//Получил запрос GET, и это был не запрос favicon.ico, должно быть, это был неверный пароль:
else if (request.indexOf("favicon.ico") == -1) {
GenerateResponse("Password is incorrect.");
}
}
void OpenDoor() {
digitalWrite(doorPin, 1);
delay(500);
digitalWrite(doorPin, 0);
}
void GenerateResponse(String text) {
Serial.println(text);
String s = "HTTP/1.1 200 OK\r\n";
s += "Content-Type: text/html\r\n\r\n";
s += "<!DOCTYPE HTML>\r\n<html>\r\n";
s += "<br><h1><b>" + text + "</b></h1>";
s += "</html>\n";
client.flush();
client.print(s);
delay(1);
}
@David Klempfner, 👍0
Обсуждение3 ответа
Лучший ответ:
Вы уже получили хороший ответ о некоторых общих идеях для
замена объектов String
строками C. Здесь я постараюсь добавить немного
больше трюков, более конкретно ориентированных на вашу реальную ситуацию.
Я вижу в вашей программе два места, где использование String
особенно заметно.
его удобство, а замена его строками C потребует некоторого
усилие. Во-первых, это использование Stream::readStringUntil(char)
для
получение первой строки запроса. Здесь
Я не вижу лучшего варианта (см. редактирование ниже), чем чтение
поток по одному символу за раз и помещая эти символы в
массив:
const size_t input_buffer_length = 256;
// Читаем строку текста до первого '\r'.
// Возвращает строку с нулевым завершением без завершающего символа '\r',
// в статически выделенный буфер.
// Предупреждение: это никогда не истекает.
const char *readline(Stream &input)
{
static char buffer[input_buffer_length];
size_t pos = 0; // запись позиции в буфере
int c; // текущий символ
while ((c = input.read()) != '\r') {
if (c >= 0 && pos < sizeof buffer - 1) {
buffer[pos++] = c;
}
}
buffer[pos] = '\0'; // завершаем строку
return buffer;
}
Вы бы использовали это так:
const char *request = readline(client);
Для большей надежности вы можете добавить тайм-аут. Отметим также, что это реализация не является реентерабельной, так как использует статический буфер. Это не проблема для вашего варианта использования.
Обновление: как отметил hcheung в комментарии, вам не нужно реализовывать
эту функцию readline()
, которую вы можете использовать
Stream::readBytesUntil()
, который обрабатывает тайм-аут
и обеспечивается ядром Arduino:
char buffer[input_buffer_length];
client.readBytesUntil('\r', buffer, input_buffer_length);
Другое место — это использование String::operator+=(const char *)
для
генерация ответа. Здесь я бы посоветовал вам против использовать
strcat()
или strncat()
: они потребуют дополнительной памяти для хранения
конкатенированная строка, которая вам, вероятно, не нужна. Вместо этого вы можете
просто print()
части один за другим:
void GenerateResponse(const char *text) {
Serial.println(text);
client.println("HTTP/1.1 200 OK");
client.println("Content-Type: text/html");
client.println();
client.println("<!DOCTYPE HTML>");
client.println("<html>");
client.print("<br><h1><b>");
client.print(text);
client.println("</b></h1></html>");
client.flush();
delay(1);
}
В качестве альтернативы вы можете использовать неявную конкатенацию строк
литералы, чтобы уменьшить количество вызовов client.print()
:
void GenerateResponse(const char *text) {
Serial.println(text);
client.print(
"HTTP/1.1 200 OK\r\n"
"Content-Type: text/html\r\n"
"\r\n"
"<!DOCTYPE HTML>\r\n"
"<html>\r\n"
"<br><h1><b>"
);
client.print(text);
client.print("</b></h1></html>\r\n");
client.flush();
delay(1);
}
Вместо создания собственной функции readline()
, вероятно, было бы чище использовать client.readBytesUntil('\r', буфер, длина)
, так как и readStringUntil()
, и readBytesUntil()
унаследованы от ` Класс потока., @hcheung
Спасибо за Ваш ответ. Он работает уже более 12 часов :) Похоже, проблема была в объектах String. Я дам вам знать, если он все еще работает через пару дней., @David Klempfner
Вы спросили: "После того, как я заменю объекты String массивами char, как мне использовать метод indexOf() и оператор + для массивов char?"
Короткий ответ: вы не можете. Если вы хотите использовать строки C (также известные как массивы символов), вам придется использовать строковые функции C.
Методы String работают с объектами String. Здравый смысл состоит в том, чтобы полностью избегать класса String, так как он полагается на память кучи, а платы Arduino слишком сильно нуждаются в памяти, чтобы иметь возможность использовать такой класс, как String, который создает временные объекты в куче.
Итак, вот код для начала:
myMessageArray [256] = {'\0'}; // Определяем глобальный массив достаточно большим и завершаем нулем
метод + заменяется
strcpy (myMessageArray, "Text to Add");
// Инициализирует myMessageArray, начиная с индекса 0
strcat (myMessageArray, "More text to Add");
// Добавляется к myMessageArray начиная с текущего индекса
преобразование числовых значений в символы:
uint_16_t myNumberValue = 31253;
char numBuffer [16] = {'\0'}; // Вспомогательный буфер для конверсий
itoa (myNumberValue,numBuffer,10); // преобразует целое число в 10 (десятичный) символ
itoa (myNumberValue,numBuffer,2); // преобразует целое число в двоичный символ с основанием 2
itoa (myNuberValue,numBuffer,16); // преобразует целое число в 16 (шестнадцатеричный) символ
itoa инициализирует массив символов, поэтому нам нужен вспомогательный массив:
strcat (myMessageArray, numBuffer);
для преобразования float мы используем
dtostrf(floatVariable, StringLengthIncDecimalPoint, numVarsAfterDecimal, numBuffer);
чтобы преобразовать символы обратно в int, используйте
int16_t myIntVar = atoi(numBuffer);
чтобы преобразовать символы обратно в числа с плавающей точкой, используйте
float myFloatVar = atof(numBuffer, decimalsToShow); // используя только atof(numBuffer)
gives you standard x.XX only 2 decimals
Рабочий IndexOf для массивов символов:
/*************************************************** ***************************************/
/**
\brief Находит индекс заданного значения в массиве, начиная с заданного индекса
\автор codebreaker007
\param [in] targetArray массив для поиска char
\param [in] valueToFind искомое значение
\param [in] startIndex индекс для начала поиска
\вернуть индекс значения в массиве
\details Этот метод возвращает INDEX_NOT_FOUND^(-1) для пустого входного массива.
Отрицательный startIndex обрабатывается как ноль. StartIndex больше, чем массив
длина вернет INDEX_NOT_FOUND(-1)
*/
/********************************************************************************/
int8_t indexOf(char* targetArray, char valueToFind, uint16_t startIndex = 0) {
if (targetArray == NULL) {
return INDEX_NOT_FOUND;
}
if (startIndex <= 0) {
startIndex = 0;
}
for (uint16_t i = startIndex; i < strlen(targetArray); i++) {
if (valueToFind == targetArray[i]) {
return i;
}
}
return INDEX_NOT_FOUND;
}
Чтобы сравнить массивы символов, вы должны изучить функцию strcmp
, и если вы хотите проверить, совпадают ли первые n символов, которые вы используете:
if (strncmp(myMessageArray , "POST", 4 ) == 0) {
// сравнивает первые 4 символа, если они совпадают с = 0, подробности см. в документации по arduino
Надеемся, что это подробное введение в массивы символов поможет вам в ваших текущих и будущих проектах. Как узнать - преобразовать в массивы char и, если он работает стабильно, это было так. До этого преобразования вы никогда не сможете исключить класс String как виновника.
Хороший исчерпывающий ответ. (проголосовал). Я обычно использую sprintf() и printf() в C, что слишком тяжело для большинства плат Arduino. Я также не уверен в классах WiFiClient и WiFiServer и в том, есть ли у них методы, которые возвращают массивы символов, а не строки., @Duncan C
Этот способ идеально подходит для переполнения буфера. Я предлагаю использовать функцию snprintf., @SBF
Все стандартные классы на ESP8366/32 используют char (после изучения проблемы кучи String в тяжелых сценариях com). Поскольку snprintf и подобные все используют временные массивы char - посмотрите исходники - это задерживает проблему фрагментации кучи, но не решает ее. @SBF, где вы видите переполнение буфера, я предлагаю глобальные массивы символов, скомпилированные для флэш-памяти - у меня esps работает уже 1,5 года с интенсивным использованием массивов символов, ни одного сброса / сбоя - та же программа с классом String вылетает через 30+ минут, @Codebreaker007
Насчет «_Рабочий IndexOf для массивов символов_»: или просто используйте старый добрый [strchr()
](http://man7.org/linux/man-pages/man3/strchr.3.html)., @Edgar Bonet
@ Codebreaker007: Я полностью согласен избегать класса Sting и использовать буфер символов, однако, если ваш буфер слишком мал, когда вы выполняете strcat, у вас происходит переполнение буфера. snprintf()
имеет аргумент для ограничения максимального количества байтов, которые будут использоваться в буфере. Сгенерированная строка имеет длину не более n-1, оставляя место для дополнительного завершающего нулевого символа., @SBF
@SBF: snprintf()
, вероятно, немного излишен для объединения строк. Альтернативой strcat()
с меньшей защитой от переполнения является [strncat()
](http://man7.org/linux/man-pages/man3/strcat.3.html)., @Edgar Bonet
Конечно, но он также заменяет весь пушок itoa. С помощью strncat() вам нужно вычислить оставшиеся свободные байты в буфере., @SBF
- Как составить URL-адрес HTTP-запроса GET с параметрами ключ/значение
- Чтение строки из Firebase и сохранение ее в виде CString
- Есть ли объяснение такому поведению?
- форматирование строк в Arduino для вывода
- Проблемы с преобразованием byte[] в String
- Преобразование строки в IP-адрес
- HTTP GET запрос с использованием Arduino Uno и ESP8266
- Чтение строки, разделенной запятыми
В дополнение ко всем ответам, предоставленным до сих пор, при использовании массива char
String.indexOf()
можно заменить наstrstr (str1, str2)
. Он возвращает номер позиции, когда str2 совпадает с частью str1., @hcheungДля обучения прочитайте `[<cstring>](https://www.cplusplus.com/reference/cstring/), в котором есть все функции для работы со строками c и массивами, которые вам нужны при работе с массивом char., @hcheung
@hcheung в этой строке "else if (strstr(request, "favicon.ico") == -1) {" Я получаю "ISO C++ запрещает сравнение между указателем и целым числом [-fpermissive]", @David Klempfner
Мне пришлось удалить сравнение с 1 и -1 в обоих операторах if, чтобы заставить его скомпилироваться., @David Klempfner