STM Урок 125. LAN8742A. LWIP. NETCONN. TCP Client



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

В качестве сервера теперь у нас, наоборот, будет ПК.

В качестве клиента по традиции мы будем использовать плату STM32F746-Discovery, а проект мы создадим из проекта предыдущего занятия LAN8742_TCP_SERVER_NETCONN, только назовём мы его теперь. соответственно, LAN8742_TCP_CLIENT_NETCONN.

Откроем наш проект в Cube MX, в Configuration зайдём сначала в настройки ETH и поменяем там MAC-адрес, так как, я думаю вы уже догадались, что мы будем наш клиент затем соединять с сервером на отладочной плате

 

 

Ещё в настройках LWIP изменим также сетевой адрес нашего клиента

 

 

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

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

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

 

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

 

Уберём глобальную структуру для сокетов, а также переменные этой структуры

 

typedef struct struct_sock_t {

  uint16_t y_pos;

  struct netconn *conn;

} struct_sock;

 

struct_sock sock01, sock02;

 

А добавми мы вместо этого структуру для соединения, а также одну переменную, так как мы на одном клиенте будем создавать только одно соединение

 

} struct_out;

typedef struct struct_conn_t {

  uint32_t conn;

  uint32_t buf;

} struct_conn;

struct_conn conn01;

 

У функции tcp_thread изменим имя и очистим тело

 

static void send_thread(void *arg)

{

}

 

В функции задачи по умолчанию StartDefaultTask уберём почти весь пользовательский код, оставив лишь это

 

/* USER CODE BEGIN 5 */

struct netconn *conn;

err_t err;

/* Infinite loop */

 

Занесём IP-адрес компьютера в структуру (у вас он будет, соответственно, другой)

 

err_t err;

ip_addr_t ServerIPaddr;

IP4_ADDR(&ServerIPaddr, 192, 168, 1, 87);

 

Установим другой цвет текста

 

IP4_ADDR(&ServerIPaddr, 192, 168, 1, 87);

TFT_SetTextColor(LCD_COLOR_BLUE);

 

Инициализируем структуру соединения, получив на неё указатель

 

TFT_SetTextColor(LCD_COLOR_BLUE);

conn = netconn_new(NETCONN_TCP);

 

При удачном создании структуры назначим нашему соединению порт и свяжем его с соединением

 

conn = netconn_new(NETCONN_TCP);

if(conn!=NULL)

{

  err = netconn_bind(conn, NULL, 4555);

}

 

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

 

err = netconn_bind(conn, NULL, 4555);

if (err == ERR_OK)

{

  err = netconn_connect(conn, &ServerIPaddr, 5444);

}

else

{

  netconn_delete(conn);

}

 

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

 

 

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

 

 

В программе netcat в командной строке введём следующую команду для того, чтобы сервер слушал порт, с которым клиент пытается создать соединение

 

 

Соберём проект, прошьём контроллер и увидим результат в программе WireShark (нажмите на картинку для увеличения изображения)

 

 

Чтобы разъединиться с клиентом, в программе netcat введём комбинацию клавиш «Ctrl + C«

 

 

 

Соединение разорвётся хоть и некорректно, но всё же порт освободится от прослушки

 

 

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

После функции задачи для передачи пакетов send_thread создадим ещё функцию задачи приёма пакетов, ведь клиент должен же что-то и принимать уметь

 

//---------------------------------------------------------------

static void recv_thread(void *arg)

{

  for(;;)

  {

    osDelay(1);

  }

}

//---------------------------------------------------------------

 

 

В задаче по умолчанию StartDefaultTask создадим наши задачи и передадим в них структуры в качестве параметров

 

err = netconn_connect(conn, &ServerIPaddr, 5444);

if (err == ERR_OK)

{

  conn01.conn = conn;

  sys_thread_new("send_thread", send_thread, (void*)&conn01, DEFAULT_THREAD_STACKSIZE, osPriorityNormal );

  sys_thread_new("recv_thread", recv_thread, (void*)&conn01, DEFAULT_THREAD_STACKSIZE, osPriorityNormal );

}

 

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

 

static void send_thread(void *arg)

{

  struct_conn *arg_conn;

  struct_out *qstruct;

  struct netconn *conn;

  err_t sent_err;

 

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

 

err_t sent_err;

arg_conn = (struct_conn*) arg;

conn = (void*)arg_conn->conn;

 

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

 

conn = (void*)arg_conn->conn;

uint32_t syscnt = 0;

char buf[15] = {};

 

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

 

char buf[15] = {};

for(;;)

{

  osDelay(1000);

}

 

 

Узнаем в цикле количество системных квантов, прошедших с момента включения контроллера

 

for(;;)

{

  syscnt = osKernelSysTick();

 

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

 

syscnt = osKernelSysTick();

if(syscnt>50000)

{

  netconn_close(conn);

  netconn_delete(conn);

  qstruct = osMailAlloc(strout_Queue, osWaitForever);

  qstruct->y_pos = 160;

  strcpy(qstruct->str,"Connection was closed!");

  osMailPut(strout_Queue, qstruct);

  osMailFree(strout_Queue, qstruct);

  osDelay(2);

  break;

}

 

Если мы не вставим задержку, то очередь не сработает, а если поставим 1, то сработает не до конца.

А если мы ещё не достигли данного значения, то передадим количество прошедших системных квантов серверу в виде строки с возвратом каретки и переводом на новую строку

 

  break;

  }

sprintf(buf,"%lu\r\n",syscnt);

sent_err = netconn_write(conn, (void *) buf, strlen(buf), NETCONN_COPY);

 

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

 

sent_err = netconn_write(conn, (void *) buf, strlen(buf), NETCONN_COPY);

if(sent_err == ERR_OK)

{

  qstruct = osMailAlloc(strout_Queue, osWaitForever);

  qstruct->y_pos = 60;

  buf[strlen(buf)-2]=0;

  strcpy(qstruct->str,buf);

  osMailPut(strout_Queue, qstruct);

  osMailFree(strout_Queue, qstruct);

}

 

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

Результат в netcat будет таким

 

 

В WireShark мы должны видеть такую картину

 

 

По достижению 50000 мы видим попытку разъединения

 

 

Клиент разрыв соединения запросил, сервер ему ответил подтверждением, затем сервер также запросил разъединение, но клиент ему не ответил, сервер повторил ещё 4 раза попытку, но клиент так и не ответил. А не ответил потому, что он пока не умеет это делать, но это пока.

На дисплее мы видим вот такую картину во время передачи пакетов

 

 

По окончанию передачи пакетов

 

 

А netcat по окончанию приёма вернётся в стандартную строку

 

 

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

 

static void recv_thread(void *arg)

{

  struct_conn *arg_conn;

  err_t recv_err;

  struct netconn *conn;

  struct netbuf *inbuf;

  struct_out *qstruct;

  uint8_t* buf;

  u16_t buflen;

 

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

 

u16_t buflen;

arg_conn = (struct_conn*) arg;

conn = (void*)arg_conn->conn;

 

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

 

for(;;)

{

  osDelay(1);

  recv_err = netconn_recv(conn, &inbuf);

}

 

Если пакет пришёл, то примем его в буфер

 

recv_err = netconn_recv(conn, &inbuf);

if (recv_err == ERR_OK)

{

  if (netconn_err(conn) == ERR_OK)

  {

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

  }

}

 

В случае, если длина буфера больше 1, то отобразим содержимое буфера на дисплее, а затем освободим память под структуру буфера

 

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

if(buflen>1)

{

  qstruct = osMailAlloc(strout_Queue, osWaitForever);

  qstruct->y_pos = 100;

  strncpy(str_buf,(char*)buf,buflen);

  str_buf[buflen-1]=0;

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

  osMailPut(strout_Queue, qstruct);

  osMailFree(strout_Queue, qstruct);

}

netbuf_delete(inbuf);

 

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

 

 

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

Во время приёма пакетов от клиента на сервере в программе netcat попробуем что-нибудь передать клиенту и посмотрим, как клиент справляется с приёмом пакетов от сервера

 

 

Посмотрим на дисплей

 

 

Всё отлично приходит.

Итак, на данном занятии мы смогли создать работоспособный клиент TCP из нашей платы, который способен запросить соединение, разорвать его также корректно, а также, самое главное, способен обмениваться информацией с сервером в виде TCP-пакетов.

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

 

 

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

 

Исходный код

 

 

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

 

 

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

 

STM Урок 125. LAN8742A. LWIP. NETCONN. TCP Client

2 комментария на “STM Урок 125. LAN8742A. LWIP. NETCONN. TCP Client
  1. NKP144:

    Странно организована завершение передачи данных в потоке send_thread.
    if(syscnt>50000)

    {
    netconn_close(conn);
    netconn_delete(conn);

    break; —> вот здесь выход из цикла for(;;).
    }
    После этого программа вылетает в prvTaskExitError( void ), т.е. некорректное завершение задачи.
    Предлагаю вот такой вариант:
    for(;;)
    {
    syscnt = osKernelSysTick();
    if (syscnt > 50000)
    {
    netconn_close(conn); // Разрываем соединение с сервером
    netconn_delete(conn); // Освобождаем память
    … что-то еще
    }
    else
    {
    sprintf(buf,»%lu\r\n»,syscnt);
    sent_err = netconn_write(conn, (void *) buf, strlen(buf), NETCONN_COPY); // Отправляем данные на сервер
    if(sent_err == ERR_OK)
    {

    }
    }
    osDelay(1000);
    }
    Т.е. сохраняем задачу, но она ничего не делает.
    Либо правильно удаляем задачу, после завершения передачи.

  2. Nemo:

    Здравствуйте!
    Есть код на основе Вашего примера, все работает только если сервер создан и потом запускается клиент. Но у моем случае, сначала запускается клиент и только потом сервер (точного времени запуска сервера я не знаю — могут бить секунды, могут несколько часов). И мне нужного сделать так чтоб клиент ожидал на сервер постоянно и когда сервер будет создан, подключаться к нему. (так как сейчас, если нет сервера программа, соединение не будет установленно.Питался делать netconn_connect(conn, &ServerIPaddr, TCP_PORT)) в цикле, не работает. Буду благодарен за помощь.

    void Tcp_TrasmitTask(void *argument)
    {
    for(;;){….}
    }

    void Tcp_RecieveTask(void *argument)
    {
    for(;;){….}
    }

    static void TcpInitTask(void *argument)
    {
    const uint16_t TCP_PORT = 5444;
    static ip_addr_t ServerIPaddr;

    MX_LWIP_Init();

    IP4_ADDR(&ServerIPaddr, 192, 168, 1, 32);

    conn = netconn_new(NETCONN_TCP);

    if (ERR_OK == netconn_connect(conn, &ServerIPaddr, TCP_PORT))
    {
    sys_thread_new(«send_thread», Tcp_TrasmitTask, NULL, DEFAULT_THREAD_STACKSIZE, osPriorityNormal );
    sys_thread_new(«recieve_thread», Tcp_RecieveTask, NULL, DEFAULT_THREAD_STACKSIZE, osPriorityAboveNormal1 );

    //decrease priory for this task
    osThreadSetPriority(TcpInitTaskHandle, osPriorityIdle);
    }

    for(;;)
    {
    osDelay(1000);
    }
    }

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

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

*