Вот еще раз перечитал статью, и все же не покидает меня чувство что что-то здесь не так. Даже не столько смущает, что приходится выносить цикл перебора сокетов из библиотеки. А скорей смущает то, что можно было так ошибиться в 90 строках кода, и что никто этого за столько времени не заметил в опенсорсном проекте.
Потому я еще раз решил просто в уме пройтись по алгоритму работы библиотеки EthernetServer.cpp, чтобы понять, как она работает, и вот к чему пришел.
Метод available():
Код: Выделить всё
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);
}
Вот что здесь происходит:
- accept() - принять входящее соединение. Вернусь к нему позже.
- Цикл перебора сокетов.
- Здесь есть следующие проверки:
- _server_port[sock] == _port - проверка, что порт, к которому привязан сокет - это порт нашего сервера. Эта проверка нужна в случае, если поднимается несколько серверов на разных портах (желательно не больше MAX_SOCK_NUM )
- (client.status() == SnSR::ESTABLISHED || client.status() == SnSR::CLOSE_WAIT) - клиент либо установил соединение, либо пытается его закрыть. Второй случай возможен, если клиент отправил нам запрос, ответ ему не нужен и он сразу отключается.
- client.available() - проверка, что в буфере чтения от клиента есть данные. Здесь логика разная в зависимости от client.status().
Если клиент подключился (ESTABLISHED), но не передал запрос, нам не имеет смысла его пока что обрабатывать.
Если клиент уже хочет отключиться, то перед закрытием сокета нужно обработать его запрос, который может быть в буфере.
Именно этот момент и объясняет комментарий //XXX.
Он говорит, что не стоит возвращать первый сокет в подходящем состоянии, а желательно проверить, есть ли в нем запрос. Это позволяет обработать другие сокеты, пока клиент после подключения надумает отправить запрос.
Теперь про метод accept()
Код: Выделить всё
void EthernetServer::accept()
{
int listening = 0;
for (int sock = 0; sock < MAX_SOCK_NUM; sock++) {
EthernetClient client(sock);
if (EthernetClass::_server_port[sock] == _port) {
if (client.status() == SnSR::LISTEN) {
listening = 1;
}
else if (client.status() == SnSR::CLOSE_WAIT && !client.available()) {
client.stop();
}
}
}
if (!listening) {
begin();
}
}
- Цикл перебора сокетов
- _server_port[sock] == _port - проходимся только по "своим" сокетам
- client.status() == SnSR::LISTEN - ищем хоть один уже слушающий сокет
- if (client.status() == SnSR::CLOSE_WAIT && !client.available())
client.stop(); - попутно закрываем закрывающиеся сокеты, если их буферы пусты
- Если нет ни одного слушающего сокета, вызвать begin()
Переделка именно этого метода и противоречит логике работы оригинальной библиотеки.
Суть метода заключается в переводе
одного и только одного для данного сервера сокета в режим LISTEN, если такого нет.
Модифицированная же версия переводит все 4 сокета в режим LISTEN и не оставляет шансов другим возможным серверам занять сокет.
Другими словами библиотека больше не позволит поднять больше одного сервера даже на разных портах (на самом деле поднять позволит, но работать с ними будет невозможно).
Таким образом, переделка библиотеки в текущем виде ломает работу в режиме нескольких серверов. Естественно, это неприемлемо для библиотеки, которая поставляется с Arduino.
С другой стороны, в режиме одного сервера это позволяет скешировать 3 дополнительных запроса (насколько я понимаю после приема соединения W5100 делает это сам) и не дать им уйти в Retransmission Timeout, что, по всей видимости, и дало некий прирост производительности в новой версии AMS