STM Урок 133. LAN8742A. LWIP. SOCKET. TCP Server



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

Со всеми тонкостями программирования протокола TCP мы будем знакомиться в процессе написания кода.

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

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

 

 

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

Откроем файл main.c и первым делом поправим шапку проекта в main()

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

Попробуем собрать проект. Если всё нормально, движемся дальше.

Так как протокол TCP ориентирован на соединение, то целесообразно в одной задаче создать соединение, а обмен с клиентом (или с несколькими клиентами) осуществлять в другой.

Сначала переименуем функцию udp_thread в tcp_thread, удалим из её тела полностью весь код, чтобы не запутаться впоследствии, а также в функции задачи по умолчанию StartDefaultTask изменим имя в создании задачи. Также для задачи увеличим вдвое размер стека

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

Над функцией tcp_thread создадим ещё одну функцию для обмена пакетами с клиентом (или клиентами)

 

 

В функции tcp_thread добавим ряд локальных переменных и указателей

 

 

Создадим сокет

 

 

Мы видим, что во втором аргументе у нас появился параметр SOCK_STREAM, который говорит библиотеке, чтобы сокет создался именно для потоковой передачи. В третьем аргументе поставим ноль, что заставит функцию подобрать параметр по умолчанию, а это и будет протокол TCP.

Если сокет нормально создался, то проинициализируем поля структуры нашего интерфейса (интерфейса сервера)

 

 

Здесь по сравнению с протоколом UDP ничего не изменилось.

Свяжем сокет со структурой интерфейса и проверим то, что всё прошло нормально. В противном случае сокет закроем

 

 

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

 

 

Добавим бесконечный цикл, в котором ответим на попытку соединения с клиентом, если таковая будет иметь место

 

 

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

 

 

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

Я думаю, смысл полей структуры понятен. А если не понятен, то сейчас мы всё увидим.

 

 

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

 

 

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

 

 

Присвоим параметры, пришедшие из другой задачи, соответствующим переменным

 

 

Добавим бесконечный цикл, в котором попытаемся принять пакет от клиента

 

 

Приём пакета происходит аналогично тому, как и в случае с UDP.

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

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

 

 

Чтобы нам закрыть соединение, мы будем с клиента посылать команду, например «-c«, поэтому давайте напишем обнаружение подобной строки, в теле которого закроем сокет и уничтожим задачу

 

 

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

 

 

Для начала отобразим пришедшую строку в терминальной программе

 

 

Также отобразим в терминальной программе количество байтов в пакете

 

 

Уберём возврат строки и перевод каретки, вставив вместо них ноль

 

 

Отобразим порт и строку из пакета на дисплее

 

 

Отобразим ещё минимальный оставшийся размер кучи

 

 

 

И напоследок отправим строку в качестве «эха» клиенту в ответ, добавив к ней возврат каретки и перевод строки

 

 

Соберём код, прошьём контроллер и запустим программу WireShark, отфильтровавшись по нужному IP сервера.

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

Запустим программу Putty, настроив необходимый порт и адрес. Запустим соединение, нажав кнопку Open

 

 

Посмотрим в анализаторе трафика, что мы благополучно соединились с нашим сервером

 

 

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

 

 

Попробуем что-нибудь передать серверу

 

 

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

Также мы видим, что обмен нормально идёт в анализаторе

 

 

Также мы видим пришедшую строку и её размер в терминальной программе

 

 

Данная строка также отображается и на нашем дисплее

 

 

Попробуем создать ещё одно соединение, запустив ещё одну сессию программы Putty.

Соединение у нас создалось благополучно

 

 

Сокет с дескриптором 2 также создан

 

 

Попробуем передать что-нибудь серверу. Все строки доходят до него отлично

 

 

Запустим ещё одну сессию Putty, чтобы создать третье соединение.

У нас появился теперь сокет уже под номером 3

 

 

Строки также отлично передаются.

А вот если мы попытаемся создать четвёртый сокет, то получим скорее всего ошибку, хотя когда я писал первую версию проекта, мне удавалось создать 4 соединения после увеличения кучи. Возможно, что я что-то забыл

 

 

В терминальной программе мы видим также ошибку -1

 

 

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

Теперь попробуем закрыть одно из трёх соединений, передав известную команду в командной строке Putty. Соединение закроется

 

 

Также мы видим обоюдное закрытие соединения в Wireshark

 

 

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

 

 

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

У нас снова создастся сокет с дескриптором 1

 

 

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

Итак, в данном занятии мы научились работать с использованием интерфейса SOCKET уже с протоколом TCP, который ориентирован на обязательное создание соединения. Мы создали несложный, но вполне работоспособный сервер.

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

 

 

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

 

Исходный код

 

 

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

 

 

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

 

STM LAN8742A. LWIP. SOCKET. TCP Server

3 комментария на “STM Урок 133. LAN8742A. LWIP. SOCKET. TCP Server
  1. Роман:

    Здравствуйте, создал программу по вашему уроку, все работает, но после 5 установленных и затем разорванных соединений не удается создать новое, выдает ошибку Out of memory error, буду очень благодарен за помощь

    • Игорь:

      если ты еще не разобрался в проблеме, дам наводку. Для каждого соединения создается свой нетбуффер и неткон. Если при разрыве соединения их не удалять, то вполне справедливо, памяти будет не хватать. Пользуйся функцией close(sock);

  2. Игорь:

    Сам сейчас стою перед проблемой как в lwip отслеживать состояние socket'a? Использую freertos, TCP-server. У меня постоянно крутится задача в которой постоянно запускаю lwip_accept(s,addr,addrlen). Если соединение произошло, создаю две задачи, одну на прием, другую на передачу с информацией по текущему сокету. Все работает отлично. Прием и передача через очередь. Проблема заключается в отслеживании состоянии сокета, пока не разобрался какой функцией оно происходит. Сейчас кроме как отследить неудачную попытку отправки в сокет, клиенту ничего придумать не могу. Ресурсы запустить еще одну задачу на проверку состояния сокета есть.

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

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

*