STM Урок 98. LAN8720. LWIP. TCP Server. Часть 2



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

 

Начнём писать теперь тело обработчика, который вызывается при получении пакета сервером. Также создадим сначала указатель на структуру

 

static err_t tcp_server_recv(void *arg, struct tcp_pcb *tpcb, struct pbuf *p, err_t err)

{

  struct server_struct *es;

 

Присвоим ей аргументы

 

err_t ret_err;

LWIP_ASSERT("arg != NULL",arg != NULL);

es = (struct server_struct *)arg;

 

Ну и добавим теперь разных условий на разные случаи жизни

 

es = (struct server_struct *)arg;

if (p == NULL)

{

}

else if(err != ERR_OK)

{

}

else if(es->state == ES_ACCEPTED)

{

}

else if (es->state == ES_RECEIVED)

{

}

else if(es->state == ES_CLOSING)

{

}

else

{

}

return ret_err;

 

И начнём теперь эти условия обрабатывать.

Сначала тело первого условия

 

if (p == NULL)

{

  es->state = ES_CLOSING;

  if(es->p == NULL)

  {

    tcp_recved(tpcb, p->tot_len);

  }

  else

  {

    //acknowledge received packet

    tcp_sent(tpcb, tcp_server_sent);

    //send remaining data

    tcp_server_send(tpcb, es);

  }

  ret_err = ERR_OK;

}

 

В случае прихода пустого пакета (обычно это подтверждение разъединения) мы устанавливаем статус на разъединение, если буфер пустой, мы даём команду на отправку подтверждения разъединения, если непустой, то отправляем серверу остатки буфера.

Если мы сейчас соберём код и прошьём контроллер, то при попытке разъединения наш сервер уже будет слать клиенту подтверждение, но флаг на разъединение теперь слать уже не будет, то есть всё с точностью до наоборот

 

 

Вроде бы одно починили, другое сломали. Но так может показаться только на первый взгляд. Теперь разъединение будет происходить более осознанно, так что починим и то, что «сломали».

 

 

Теперь следующее условие

 

else if(err != ERR_OK)

{

  if (p != NULL)

  {

    es->p = NULL;

    pbuf_free(p);

  }

  ret_err = err;

}

 

Это случай, если пришел статус ошибки, мы его передаём в возврат функции и освобождаем буферы.

Следующее условие

 

else if(es->state == ES_ACCEPTED)

{

  tcp_recved(tpcb, p->tot_len);

  strncpy(str1,p->payload,p->len);

  str1[p->len] = '\0';

  HAL_UART_Transmit(&huart6, (uint8_t*)str1,strlen(str1),0x1000);

  ret_err = ERR_OK;

}

 

Это случай, если пришел нормальный пакет с. Мы проверяем статус, и после этого попадаем в тело условия, принимаем данные из буфера контроллера в буфер структуры, затем копируем его в строковый массив, завершаем нулём и отображаем в терминальной программе.

Можно опять собрать код и прошить контроллер. Также запустим терминальную программу с теми же настройками как и в уроке по клиенту TCP, соединимся с сервером и попытаемся ему передать строку из командной строки Putty

 

 

Посмотрим, пришла ли строка в терминальную программу

 

 

Строку мы получили. Отлично. Разъединяемся с сервером и пишем дальше наш код.

Напишем тело следующего условия

 

else if (es->state == ES_RECEIVED)

{

  if(es->p == NULL)

  {

    ret_err = ERR_OK;

  }

  else

  {

    struct pbuf *ptr;

    ptr = es->p;

    pbuf_chain(ptr,p);

  }

  ret_err = ERR_OK;

}

 

Данный статус обычно мы ставим, когда обрабатываются данные с целью, чтобы объявить побольше окно. Если буфер пустой, то мы ничего не делаем, если непустой то создаём указатель на буфер, передаём ему адрес буфера нашей локальной структуры, и вызываем функцию, которая свяжет наш буфер с буфером, который пришел на входе в функцию.

Следующее условие

 

else if(es->state == ES_CLOSING)

{

  tcp_recved(tpcb, p->tot_len);

  es->p = NULL;

  pbuf_free(p);

  ret_err = ERR_OK;

}

 

Это условие, когда нужно обработать статус разъединения. Мы отправляем подтверждение, и обнуляем все соответствующие указатели, а также освобождаем память буфера.

 

 

Ну и последнее условие — это когда не сходится с остальными, какой-то особый случай

 

else

{

  tcp_recved(tpcb, p->tot_len);

  es->p = NULL;

  pbuf_free(p);

  ret_err = ERR_OK;

}

 

В этом случае мы делаем то же самое.

Теперь напишем тело следующего обработчика, вызываемого в случае передачи пакета — tcp_server_sent

 

static err_t tcp_server_sent(void *arg, struct tcp_pcb *tpcb, u16_t len)

{

  struct server_struct *es;

  LWIP_UNUSED_ARG(len);

  es = (struct server_struct *)arg;

  es->retries = 0;

  if(es->p != NULL)

  {

    tcp_server_send(tpcb, es);

  }

  else

  {

    if(es->state == ES_CLOSING)

      tcp_server_connection_close(tpcb, es);

  }

  return ERR_OK;

}

 

То же самое — указатель на структуру, присваиваем аргументы, если буфер не пустой, то передаём, если пусто да и плюс если статус на разъединение, то разъединяемся.

Пишем тело следующей функции — tcp_server_poll. Это обработчик, который вызывается через определённое время для мониторига сети, не важно, есть ли пакеты, передаваемые в данный момент в ту или иную сторону или нет

 

err_t ret_err;

struct server_struct *es;

es = (struct server_struct *)arg;

HAL_GPIO_TogglePin(GPIOD, GPIO_PIN_14);

 

Мы также создадим структуру, присвоим ей указатель на аргументы и переключим состояние светодиода, чтобы убедиться в своевременности вызовов данного обработчика. Если мы соберём код, прошьем контроллер и соединимся с нашим сервером, то мы увидим, что красный светодиод будет мигать периодичностью в полсекунды, так как в параметрах, когда мы объявляли данную функцию, мы передавали 0.

В данный момент, даже если мы разъединимся с сервером, то мигать данный светодиод не перестанет, так как сервер считает, что он ещё не разъединился с клиентом.

Нет, он конечно перестанет, но не скоро, когда клиенту надоест ждать команды на разъединения и он передаст серверу флаг RST.

Напишем остальной код нашей функции

 

HAL_GPIO_TogglePin(GPIOD, GPIO_PIN_14);

if (es != NULL)

{

  if (es->p != NULL)

  {

  }

  else

  {

    if(es->state == ES_CLOSING)

    {

      HAL_GPIO_WritePin(GPIOD, GPIO_PIN_14, GPIO_PIN_RESET);

      HAL_GPIO_WritePin(GPIOD, GPIO_PIN_12, GPIO_PIN_SET);

      tcp_server_connection_close(tpcb, es);

    }

    if(usartprop.is_text==1)

    {

      usartprop.is_text=0;

    }

  }

  ret_err = ERR_OK;

}

else

{

  tcp_abort(tpcb);

  ret_err = ERR_ABRT;

}

return ret_err;

 

Если структура нормально создалась, то мы проверим, что буфер не пустой. Если пустой, то мы ничего не делаем, если непустой то мы проверим статус, если мы находимся в стадии разъединения, то мы выключаем красный светодиод, зажигаем зелёный и разъединяемся с клиентом, сбрасываем статус передачи текста клиенту и присваиваем результату успешное значение. В противном случае (если структура не создалась или указатель не получен) мы передаём клиенту флаг RST и уходим с ошибкой.

Если мы теперь соберём код, прошьём контроллер, соединимся с сервером, а затем разъединимся, то мы увидим, что процесс разъединения происходит корректно

 

 

Осталось нам лишь только научить сервер в любой момент передавать строки клиенту.

Для этого напишем тело функции передачи строки

 

void sendstring(char* buf_str)

{

  usartprop.is_text=1;

  tcp_sent(ss->pcb, tcp_server_sent);

  tcp_write(ss->pcb, (void*)buf_str, strlen(buf_str), 1);

  tcp_recved(ss->pcb, strlen(buf_str));

  usartprop.usart_cnt=0;

}

 

Мы сначала устанавливаем статус передачи текста, затем показываем нашей структуре «дорого» к функции-обработчику передачи данных, далее мы записываем нашу строку в буфер структуры TCP, затем производим передачу. Ну и по окончании сбрасываем счётчик USART.

Соберём код, прошьём контроллер, соединимся с нашим сервером и попробуем попередавать строки с клиента на сервер и обратно. С клиента на сервер мы уже данные передавали, поэтому я покажу процесс передачи с сервера клиенту.

Для этого мы введём строку в терминальной программе и передадим её в USART. Контроллер обработает данную строку и сначала он её эхом отправит обратно в USART

 

 

А потом отправим серверу, что мы и видим в окне Putty

 

 

По идее, для полного порядка мы при разъединении с клиентом должны освобождать и обнулять нашу глобальную структуру, но так как мы пока работаем с одним соединением, то это делать необязательно, думаю, ничего с памятью не случится, так как по большому счёту — наша структура лишь указатель, она сама по себе память не занимает. Поэтому цель урока считаю достигнутой, с чем вас и поздравляю! Мы создали простой сервер, который способен реагировать на желание клиента создать TCP-соединение с ним, также разъединяться, и также, что самое главное — сервер способен принимать и передавать пакеты с данными по протоколу TCP.

Всем спасибо за внимание!

 

 

Предыдущая часть Программирование МК STM32 Следующий урок

 

Исходный код

 

 

Отладочную плату можно приобрести здесь: STM32F4-DISCOVERY

Модуль LAN можно приобрести здесь: LAN8720

Плату расширения можно приобрести здесь: STM32F4DIS-BB

Переходник USB to TTL можно приобрести здесь ftdi ft232rl

 

 

Смотреть ВИДЕОУРОК (нажмите на картинку)

 

STM LAN8720. LWIP. TCP Server

16 комментариев на “STM Урок 98. LAN8720. LWIP. TCP Server. Часть 2
  1. Roman:

    Здравствуйте!
    Использую похожий модуль dp83848. Есть ли у данных модулей возможность отслеживания приема данных по прерываниям а не в цикле? У меня через приложение с компьютера отправляются данные через lan на контроллер. И в цикле выполняется алгоритм. Важно быстродействие, поэтому можно как-то убрать проверку приема данных из while? Наподобие как с модулями wiznet, enc28j60.

    • Mikhail:

      У STM32F107 есть прерывание по приёму. Я делал приём именно через это прерывание, выставляя признак в приёмном дескрипторе, чтобы не сидеть долго в прерывании. А в главном цикле идёт опрос приёмных дескрипторов на факт появления этого признака.

  2. Галина:

    Спасибо большое за статью!

  3. алексей:

    почему с putty на coolterm можно только 8 сообщений отправить? далее все как будто виснет, а с coolterm на putty неограниченное количество

    • алексей:

      заметил, что количество принятых по TCP сообщений соответствует переменной PBUF_POOL_SIZE, возможно ли как то неограниченное количество пакетов принимать в течении одного соединения? а также заметил, что после закрытия соединения (закрытия putty),новое соединение не устанавливается, только после перезагрузки микроконтроллера

    • Смотрите трафик в WireShark, возможно у Вас несколько портов открывается и потом они не закрываются и тем самым превышается порог максимально открытых коннектов. Будет время, я тоже проверю. Хотя в случае RAW тяжело отследить такие вещи.

  4. Oleg:

    Спасибо вам за статью. Я не видел нигде mem_free(ss); Разве не нужно освободить память глобального параметра?

  5. ChrisF:

    Thank you so much for this source. I'm trying to send variable to putty console without uart. To get data maybe i can use ext interrupt with button. Could you direct me shortly for sending variable, pls?

  6. funfunfun:

    Здравствуйте. Спасибо за ваш труд. Однако, немного критики:

    if (p == NULL)
    {
    es->state = ES_CLOSING;
    if(es->p == NULL)
    {
    tcp_recved(tpcb, p->tot_len);
    }

    Невозможно взять p->tot_len, когда p == NULL

      • funfunfun:

        Ну и раз уж пошла такая пьянка…
        else if(es->state == ES_ACCEPTED)
        {
        tcp_recved(tpcb, p->tot_len);
        strncpy(str1,p->payload,p->len);
        str1[p->len] = '\0';
        HAL_UART_Transmit(&huart6, (uint8_t*)str1,strlen(str1),0x1000);
        ret_err = ERR_OK;
        }

        здесь, видимо, нужно сменить состояние es->state = ES_RECEIVED, либо удалить ветку if (es->state == ES_RECEIVED), поскольку перехода в это состояние нигде не происходит.
        Но более важно то, что вы здесь не освобождаете буфер pbuf: pbuf_free(p);
        Это, очевидно, приводит к утечке памяти.
        PS: я не придираюсь, просто хочу, чтобы ошибок в коде было меньше и людям, которые знакомятся с lwip, было проще разобраться.

  7. ЭдМахалыч:

    После приема пакета необходимо освободить буфер в ветке
    if(es->state == ES_ACCEPTED), т.к. это массив структур размером в PBUF_POOL_SIZE и он после каждого приема пишет в следующую ячейку.
    Т.е. после strncpy(str1,p->payload,p->len); добавляем pbuf_free(p); и получаем неограниченное кол-во принимаемых пакетов.
    ЗЫ. Спасибо огромное за ресурс. Все замечания уважаемому NarodStream прошу воспринимать исключительно как процесс обсуждения темы.

      • Артем:

        Исправьте пожалуйста этот косяк с утечкой памяти! я два дня потерял, пока комменты не перечитал!

        • Я проходил этот урок последовательно после TCP Client — урока, там в комментариях один уважаемый уже указывал, что надо было добавить pbuf_free(p);
          Т.к. здесь функция приёма подобна, и потеря приёма также происходит на том же месте (17-й пакет, если не ошибаюсь), то первое что приходит в голову, также освободить буффер.

          else if(es->state == ES_SERVER_ACCEPTED)
          {
          tcp_recved(tpcb, p->tot_len);

          // !debug
          strncpy(str1, p->payload, p->len);
          str1[p->len] = '\0';
          printf(str1);
          printf(«\n»);

          // ! corrected here
          pbuf_free(p);

          ret_err = ERR_OK;
          }

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

*