В приведенном ниже коде объект WiFiClient
меняет поведение в зависимости от того, присвоен ли он переменной с локальной или нелокальной областью.
Локально:
#include <WiFi.h>
char ssid[] = "[redacted]"; // your network SSID (name)
char pass[] = "[redacted]"; // your network password
int status = WL_IDLE_STATUS;
WiFiServer server(80);
void setup()
{
Serial.begin(115200);
while (status != WL_CONNECTED) {
status = WiFi.begin(ssid, pass);
delay(5000);
}
server.begin();
}
void loop()
{
WiFiClient client = server.available();
if (client) {
Serial.println("new client connected");
String currentLine = "";
while (client.connected()) {
if (client.available()) {
char c = client.read();
Serial.write(c);
if (c == '\n') {
if (currentLine.length() == 0) {
//do something
} else {
currentLine = "";
}
} else if (c != '\r') {
currentLine += c;
}
}
}
client.stop();
Serial.println("client disconnected");
}
}
Вывод (когда клиент подключается):
// execution resumes here from blocking server.available() after a client connects ...
[INFO] A client connected to this server :
[PORT]: 58620
[IP]:192.168.2.36
new client connected
GET / HTTP/1.1
Host: 192.168.2.172
Connection: keep-alive
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9,fil;q=0.8
Cookie: __guid=259319114.1308697201864418800.1719734660203.9114
Нелокальная область видимости:
#include <WiFi.h>
char ssid[] = "[redacted]"; // your network SSID (name)
char pass[] = "[redacted]"; // your network password
int status = WL_IDLE_STATUS;
WiFiServer server(80);
WiFiClient client;
void setup()
{
Serial.begin(115200);
while (status != WL_CONNECTED) {
status = WiFi.begin(ssid, pass);
delay(5000);
}
server.begin();
}
void loop()
{
client = server.available();
if (client) {
Serial.println("new client connected");
String currentLine = "";
while (client.connected()) {
if (client.available()) {
char c = client.read();
Serial.write(c);
if (c == '\n') {
if (currentLine.length() == 0) {
//do something
} else {
currentLine = "";
}
} else if (c != '\r') {
currentLine += c;
}
}
}
client.stop();
Serial.println("client disconnected");
}
}
Вывод (когда клиент подключается):
// execution resumes here from blocking server.available() after a client connects ...
[INFO] A client connected to this server :
[PORT]: 51193
[IP]:192.168.2.36
new client connected
client disconnected
[INFO] A client connected to this server :
[PORT]: 51192
[IP]:192.168.2.36
new client connected
client disconnected
[INFO] A client connected to this server :
[PORT]: 51194
[IP]:192.168.2.36
new client connected
client disconnected
// ...continues indefinitely
Согласно документации Arduino для server.available(), поведение, наблюдаемое, когда WiFiClient
не локально ограничено, не является нормальным, и я уже сообщил о проблеме разработчикам используемого SDK. Однако в целом мне хотелось бы понять, почему назначение объекта в разных областях видимости в C/C++ может привести к такому изменению поведения.
Помимо помещения WiFiClient
в глобальную переменную, как указано выше, я также попытался сделать ее членом класса. Я также пробовал разные вещи, чтобы заставить его работать, например, сохранение указателя или ссылки на объект WiFiClient
, но ничего из этого не сработало.
В основном я использовал языки, основанные на интерпретаторе, и еще не сталкивался с такой проблемой. Мой предыдущий опыт заставил меня поверить, что объект должен вести себя относительно одинаково в разных областях видимости (я признаю, что из-за этого ожидания потребовалось очень много времени, чтобы сузить проблему до изменений в области видимости переменных), так что это действительно ново и увлекательно для меня.
Очевидно, что более конкретное обсуждение того, почему произошло то, что я испытал выше, потребует глубоких знаний платформы Arduino и SDK, который я использую, так что я спрашиваю не об этом. Мне бы очень хотелось узнать более концептуально, почему такая проблема может возникнуть вообще.
Дополнительная информация: я забыл упомянуть, что server.available()
блокируется — хотя существует неблокирующий режим, он здесь не включен, чтобы еще больше исключить проблему, вызванную этим режимом. Проблемы возникают при самой первой поездке через loop()
. Выполнение останавливается на WiFiClient client = server.available()
/client = server.available()
, пока клиент не подключится. Выходы последовательного монитора, которые я включаю, запускаются сразу после подключения клиента. Я также отредактировал эти результаты, чтобы сделать это более понятным.
Деструкторы вызываются в конце }
блока с ограниченной областью действия. Проблема не имеет ничего общего с Arduino — именно так работает C++. while (whatever) { T x; }
совершенно отличается от T x; while (whatever) { }
. Первый имеет x
живым, а второй (как уже упоминалось) создает и уничтожает x
на каждой итерации. Если деструктор выполняет различные действия, влияющие на работу x
, то неудивительно, что все работает странно.
Существует также разница в назначении копирования/перемещения с client = server.available();
и с WiFiClient client = server.available();
вызовом конструктора.
char ssid[] = "";
и char pass[] = "";
выглядят подозрительно; они оба создают массив из 1 char
, который явно недостаточно велик, чтобы содержать SSID или пароль. Недостаточно кода, чтобы увидеть, как они используются, но если где-то зарыт strcpy
, он будет писать за пределами границ.
Я подозреваю, что WiFiClient
неправильно реализует правило 5/3/0, поэтому удаление перемещенного временного объекта неправильно влияет на экземпляр, который все еще жив.
@PaulMcKenzie @Someprogrammerdude Я просто хотел добавить дополнительный контекст: server.available()
блокируется до тех пор, пока клиент не подключится, поэтому он никогда не достигает завершения }
до того, как произойдет ошибка (пожалуйста, поправьте меня, если я ошибаюсь). Он останавливается на client = server.available();
, пока клиент не подключится. Если это так, разве это не должно работать как обычно (по крайней мере, в самом первом цикле, до вызова деструкторов)?
@PeteBecker, извините, если это было неясно в моем сообщении, но char ssid[] = "";
и char pass[] = "";
удалены из моего SSID и пароля.
@RobertErnestDelaCruz — я не понимаю, что ты имеешь в виду. Но чтобы прояснить мою точку зрения, каждый из этих двух массивов может содержать не более 1 символа.
@PeteBecker - Разве указание размера массива символов при инициализации не является обязательным? См. соответствующую часть документа по агрегатной инициализации.
@RobertErnestDelaCruz - я не уверен, где именно в этой документации вы ищете. char x[] = "abcd";
определяет x
как массив char
из 5 элементов; инициализатор "abcd"
(который имеет 5 символов, включая завершающий нуль) определяет размер массива. char x[] = "";
определяет x
как массив char
с 1 элементом (поскольку ""
имеет 1 символ, завершающий нуль).
@PetBecker Ах да. поэтому я извинился за неясность своего поста. char ssid[] = ""
/char[] pass = ""
так происходит только потому, что я отредактировал (удалил) значения, которые там были изначально (мой собственный SSID и пароль). Они заполнены, как и должно быть, в моем собственном коде. Еще раз извините, если я неясно выразился тогда и раньше.
@RobertErnestDelaCruz - хорошо, они инициализируются значениями, которые используются повсюду. Так что нет проблем. Совет: используйте const char ssid[] = "";
для данных персонажа, которые не будут изменены.
Между версиями есть как минимум 2 различия (которые связаны между собой):
В локальной версии следующая строка:
WiFiClient client = server.available();
вызывает конструктор WiFiClient
.
Но в глобальной версии эта строка:
client = server.available();
вызывает оператор присваивания (перемещение/копирование), который потенциально отличается.
Функция loop
вызывается в цикле системным кодом Arduino.
Каждый раз он вызывает новый экземпляр этой строкой:
WiFiClient client = server.available();
Тогда каждый раз, когда функция loop
возвращает значение, область действия client
также заканчивается и она уничтожается.
Это отличается от глобальной версии, где всегда используется один и тот же экземпляр client
(но, как упоминалось выше, он назначается при каждом вызове loop
). Обратите внимание, что в глобальном случае для каждого присваивания создается временный объект (а затем уничтожается).
Оба различия (конструкция и присвоение, а также многократное уничтожение client
) на самом деле являются аспектами одной и той же причины — локальной/глобальной области видимости.
«Это отличается от глобальной версии, в которой когда-либо создается только один экземпляр клиента». Временный создается и удаляется сразу после назначения.
@Jarod42 Джарод42, ты прав, исправил мой ответ.
Спасибо за понимание. Это дает мне дополнительную информацию о потенциальных проблемах и является своего рода указателем, который я искал, чтобы указать мне правильное направление. Но я просто хотел некоторых разъяснений: та же идея справедлива и для локальных и классовых областей, не так ли? Потому что я попробовал это и получил те же результаты. Кроме того, может ли строительство и/или разрушение временного объекта вызвать потенциальные проблемы, даже если он не завершен на момент первого задания (первая поездка через loop()
) — актуально из-за блокировки server.available()
.
@RobertErnestDelaCruz (1) относительно области действия класса: не понимаю, что вы имеете в виду. В любом случае член класса жив, пока жив объект класса, и уничтожается, пока жив объект. (2) строительство/разрушение временного объекта может вызвать проблемы, если он не будет должным образом соблюдать правило 0/3/5.
@wohlstad Не знал, спасибо! Я проголосовал за ваш ответ.
Функция
loop
вызывается в цикле системным кодом Arduino. Поэтому, когдаclient
является локальной переменной, она будет создаваться и уничтожаться каждый раз при вызове функции. Если он глобальный, то он не будет уничтожен между вызовами.