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, который ориентирован на обязательное создание соединения. Мы создали несложный, но вполне работоспособный сервер.

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

 

 

 

Исходный код

 

 

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

 

 

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

STM LAN8742A. LWIP. SOCKET. TCP Server

 

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

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). Если соединение произошло, создаю две задачи, одну на прием, другую на передачу с информацией по текущему сокету. Все работает отлично. Прием и передача через очередь. Проблема заключается в отслеживании состоянии сокета, пока не разобрался какой функцией оно происходит. Сейчас кроме как отследить неудачную попытку отправки в сокет, клиенту ничего придумать не могу. Ресурсы запустить еще одну задачу на проверку состояния сокета есть.

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

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

*