STM Урок 124. LAN8742A. LWIP. NETCONN. TCP Server



Переходим к следующему, более серьёзному типу соединения транспортного уровня — TCP (Transmission Control Protocol, протокол управления передачей). С данным протоколом мы уже встречались неоднократно. Поэтому, хоть он и является непростым, мы его изучили очень неплохо. Мы знаем, как именно происходит создание и разрыв соединения, знаем, как передаются пакеты TCP, как они делятся на сегменты. Поэтому работать нам с ним и анализировать определённые создающиеся нестандартные ситуации мы уже вполне можем. Изучая стек протоколов LWIP, мы также сталкивались с протоколом TCP, даже соединяли с помощью него два контроллера, организовывали между ними передачу данных. Но всё это происходило с использованием интерфейса RAW. Теперь настала пора поработать с данным протоколом при помощи интерфейса NETCOON, который, как известно, работает с использованием системы реального времени FREERTOS.

В качестве платы для испытания мы возьмём STM32F746-Discovery, а проект для прототипа мы возьмём из урока 120 с именем LAN8742_UDP_SERVER_NETCONN. Присвоим ему теперь соответствующее имя LAN8742_TCP_SERVER_NETCONN и откроем его в Cube MX.

Немного подправим настройки LWIP в разделе Key Options, чтобы они соответствовали настройкам клиентов в будущих уроках

 

 

 

Сгенерируем проект для System Workbench и откроем его там. Установим уровень оптимизации в 1, уберём при наличии отладочные настройки и закомментируем неизвестные компилятору строки в файле main.c.

Попробуем собрать проект и начнём работать с файлом main.c.

Первым делом исправим шапку в main()

 

TFT_DisplayString(0, 10, (uint8_t *)"TCP Server", CENTER_MODE);

 

Наш проект будет поддерживать два соединения (или, как принято называть, сокета). Отличие протокола TCP от UDP помимо всего прочего состоит в том, что мы можем работать с двумя соединениями на одном порте сервера. Поэтому в структуре для сокета мы уберём свойство порта, но добавим свойство соединения TCP

 

typedef struct struct_sock_t {

  uint16_t y_pos;

  uint16_t port;

  struct netconn *conn;

} struct_sock;

 

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

 

static void tcp_thread(void *arg)

{

  struct_out *qstruct;

  err_t err, recv_err;

  struct netconn *conn;

}

 

Теперь переходим в задачу по умолчанию StartDefaultTask и также удалим из неё почти весь пользовательский код, оставив лишь инициализацию структур в плане вертикальной координаты вывода информации на экран дисплея

 

/* USER CODE BEGIN 5 */

sock01.y_pos = 60;

sock02.y_pos = 180;

/* Infinite loop */

 

Объявим здесь же указатель на переменную структуры соединения и переменную для ошибки

 

/* USER CODE BEGIN 5 */

struct netconn *conn;

err_t err;

 

Создадим соединение

 

sock02.y_pos = 180;

conn = netconn_new(NETCONN_TCP);

 

Если всё нормально создалось, то передадим указатель на данную структуру обоим соединениям

 

conn = netconn_new(NETCONN_TCP);

if(conn!=NULL)

{

  sock01.conn = conn;

  sock02.conn = conn;

}

 

 

Свяжем структуру, например с портом 80, что пригодится нам на будущее для создания сервера HTTP

 

sock02.conn = conn;

err = netconn_bind(conn, NULL, 80);

 

Если ошибок нет, то начинаем слушать порт, а если нет — удаляем структуру

 

err = netconn_bind(conn, NULL, 80);

if (err == ERR_OK)

{

  netconn_listen(conn);

}

else

{

  netconn_delete(conn);

}

 

Запустим анализатор трафика Wireshark и отфильтруем там поток по IP-адресу нашего сервера

 

 

Теперь мы можем проверить создание соединения.

Соберём код, прошьём контроллер и запустим программу сетевого обмена Putty, в которой выберем созданное ранее нами соединение. Если у кого нет, то создаём подобное, на скриншоте всё есть. Запустим соединение с помощью кнопки Open

 

 

Мы видим, что соединение у нас успешно установилось, трёхкратное рукопожатие произошло (нажмите на картинку для увеличения изображения)

 

 

Закроем окно соединения в Putty, соединение разорвалось, но только в одностороннем порядке. Сервер не изъявил желание на разрыв, а мы помним, что всё здесь должно быть обоюдно (нажмите на картинку для увеличения изображения)

 

 

Желание сервера на разрыв соединения мы инициируем позже.

Также в нашем соединении мы пока не можем передавать пакеты, мы это ещё не настроили, всё это будет происходить в задачах соединений, которые мы сейчас и создадим

 

netconn_listen(conn);

sys_thread_new("tcp_thread1", tcp_thread, (void*)&sock01, DEFAULT_THREAD_STACKSIZE, osPriorityNormal );

sys_thread_new("tcp_thread2", tcp_thread, (void*)&sock02, DEFAULT_THREAD_STACKSIZE, osPriorityNormal );

 

Также здесь мы передали указатель на структуру соединения.

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

 

struct netconn *conn;

struct netbuf *inbuf;

struct netconn *newconn;

 

Поймаем переданный с помощью параметра задаче указатель на структуру соединения и передадим его локальному указателю

 

struct netconn *newconn;

struct_sock *arg_sock;

arg_sock = (struct_sock*) arg;

conn = arg_sock->conn;

 

Создадим переменную для хранения длины буфера, а также указатель на обычный строковый буфер

 

conn = arg_sock->conn;

u16_t buflen;

char* buf;

 

 

Изменим цвет вывода информации на дисплей

 

char* buf;

TFT_SetTextColor(LCD_COLOR_BLUE);

 

Организуем бесконечный цикл, в котором свяжем структуру сокета для передачи данных с общей структурой TCP

 

TFT_SetTextColor(LCD_COLOR_BLUE);

for(;;)

{

  err = netconn_accept(conn, &newconn);

}

 

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

 

err = netconn_accept(conn, &newconn);

if (err == ERR_OK)

{

  for(;;)

  {

    recv_err = netconn_recv(newconn, &inbuf);

  }

}

else

{

  osDelay(1);

}

 

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

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

 

recv_err = netconn_recv(newconn, &inbuf);

if (recv_err == ERR_OK)

{

  netbuf_data(inbuf, (void**)&buf, &buflen);

}

else

{

  netbuf_delete(inbuf);

  netconn_close(newconn);

  break;

}

 

Вот теперь у нас разъединение с клиентом должно будет происходить обоюдно.

Проверим это, собрав код, прошив контроллер и запустив соединение с помощью программы Putty, а затем разорвав его с помощью закрытия окна соединения данной программы.

Мы должны теперь получить вот такой результат (нажмите на картинку для увеличения изображения)

 

 

Проверим концовку буфера на перевод строки и возврат каретки. Если таковой имеется, то освободим память структуры буфера и пропустим одну итерацию в цикле с помощью оператора continue

 

netbuf_data(inbuf, (void**)&buf, &buflen);

if((buf[0]==0x0D)||(buf[0]==0x0A))

{

  netbuf_delete(inbuf);

  continue;

}

 

А если это ещё не окончание строки, то продолжаем наш цикл.

Мы отправим содержимое буфера на дисплей с помощью очереди, обнулив определённый байт в строчном буфере, чтобы создать окончание строки

 

  continue;

}

qstruct = osMailAlloc(strout_Queue, osWaitForever);

qstruct->y_pos = arg_sock->y_pos;

strncpy(str_buf,buf,buflen);

str_buf[buflen]=0;

sprintf(qstruct->str,"%-20s", str_buf);

osMailPut(strout_Queue, qstruct);

osMailFree(strout_Queue, qstruct);

 

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

 

osMailFree(strout_Queue, qstruct);

str_buf[buflen] = '\r';

str_buf[buflen+1] = '\n';

netconn_write(newconn, str_buf, buflen+2, NETCONN_COPY);

netbuf_delete(inbuf);

 

Собственно, вот весь и код.

Соберём его, прошьём контроллер, запустим программу Putty, откроем порт и попробуем что-нибудь передать нашему серверу

 

 

Мы видим, что строка от сервера нам успешно вернулась.

Также мы видим, что строка пришла и на дисплей

 

 

Попробуем передать ещё несколько строк

 

 

Создадим ещё одно соединение с клиента, запустив ещё одну сессию Putty, и проверим прохождение строк также и оттуда

 

 

Данные также успешно передались и вернулись.

Также они пришли в виде строки на дисплей в другую, более нижнюю позицию

 

 

Попробуем и здесь передать ещё несколько строк

 

 

Данные также успешно передаются и возвращаются куда надо, не путаясь между собой.

Также мы видим, что в программе анализа трафика пакеты тоже проходят нормально (нажмите на картинку для увеличения изображения)

 

 

Закроем обе сессии и посмотрим корректность разрыва соединения (нажмите на картинку для увеличения изображения)

 

 

Итак, на данном занятии мы создали несложный, но в то же время способный корректно работать сервер TCP, с которым корректно соединяются и разъединяются клиенты, а также происходит обмен пакетами между узлами.

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

 

 

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

 

Исходный код

 

 

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

 

 

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

 

STM LAN8742A. LWIP. NETCONN. TCP Server

8 комментариев на “STM Урок 124. LAN8742A. LWIP. NETCONN. TCP Server
  1. Евгений:

    Здравствуйте, можете помочь?
    Я реализовал TCP server и он работал, но в какой-то момент контроллер стал зависать при попытке подключения ПК к контроллеру. Даже не знаю какие ещё входные данные предоставить. Базовые настройки размеров буферов в cubeMX для LWIP не менял.
    Как я понял зависание происходит в netconn_accept(conn, &newconn)
    Кусок кода:

    err = netconn_listen(conn);
    if (err == ERR_OK)
    {
    for (;;)
    {
    accept_err = netconn_accept(conn, &newconn);
    if (accept_err == ERR_OK)
    {

    • Евгений:

      Работаю на F7, поборол эту проблему. Необходимо вытащить строку
      accept_err = netconn_accept(conn, &newconn);
      из вечного цикла, достаточно запустить эту функцию один раз.

  2. Денис:

    Хочу указать на небольшую неточность в примере, которая мне, как начинающему с lwip, доставила немало хлопот.

    Функция netconn_close(newconn) разрывает соединение, но не удаляет его структуру. При разрыве соединения и новом подключении снова вызывается функция netconn_accept, которая создает новую структуру и их общее количество в стеке увеличивается на единицу. Количество структур ограничено значением MEMP_NUM_NETBUF. При достижении этого числа, структуры перестают создаваться, а следовательно, клиенту отказывается в подключении.
    Проблема решается добавлением команды netconn_delete(newconn) после netconn_close(newconn).
    Возможно, эта проблема имеет те же корни что и проблема предыдущего пользователя. Не знаю только, чьё решение более верное.

  3. Ford:

    Здравствуйте. Как можно сделать на данной библиотеке LWIP. NETCONN. одновременно сервер и клиент?

    • Здравствуйте!
      Одновременно в одной сети скорей всего никак. Только по очереди. А это переинициализация. Да и вряд ли это нужно для чего-нибудь. Особенно в одной сети. Чтобы перейти в режим клиента, надо убедиться, что никто соединиться не попытается, а это значит, что всем надо об этом сообщить. Думаю, что переобуться в прыжке легче.

  4. jc:

    Please, give me an advice. The example worked until I ended the connection on the client's side. Then it was no longer possible to connect.

  5. jc:

    not before, after is correct

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

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

*