Продолжаем работать с приёмом и передачей данных по беспроводной сети Wi-Fi.
И на данном уроке мы попытаемся написать уже простой клиент TCP. Надеюсь, мы все помним, что это за протокол, если нет, то смотрим уроки по другим контроллерам, где всё это рассмотрено очень глубоко.
Самое главное, что мы должны помнить о том, что передача данных по протоколу TCP предусматривает наличие соединения между клиентом и сервером, которое достигается путём трехкратного рукопожатия и по окончанию обмена должно быть разорвано.
Схема наша по-прежнему не изменилась: отладочная плата, подключенная к ПК по USB
Проект был сделан из проекта урока 15 с именем ESPCONN_UDP_CLIENT и был назван, соответственно, ESPCONN_TCP_CLIENT_01. Постфикс _01 был применён в связи с тем, что у нас будет два проекта, незначительно отличающихся друг от друга.
Задача первого проекта — соединиться с сервером, передать ему пакет и сразу же от него отсоединиться.
Откроем проект в Eclipse и в файле user_config.h изменим имена следующих констант
#define TCPSERVERIP "192.168.1.76"
#define TCPSERVERPORT 30000
В файле main.c подключим заголовочный файл для работы с памятью
1 2 |
#include "osapi.h" #include "mem.h" |
Переименуем функцию udp_connect в tcp_connect, удалив из неё практически всё тело. Оставим только объявление переменной и массива, имя которого тоже немного изменим, и переменной
static void ICACHE_FLASH_ATTR tcp_connect()
{
uint32_t ip;
char tcpserverip[15] = {};
}
Также изменим имя и в вызове данной функции в теле функции wifi_check_ip
tcp_connect();
Функцию udp_client_udp_send_cb переименуем в tcpclient_sent_cb
void ICACHE_FLASH_ATTR tcpclient_sent_cb(void* arg)
Выше добавим ещё одну функцию обратного вызова для обработки события разрыва соединения с сервером
1 2 3 4 5 6 |
//------------------------------------------------------ static void ICACHE_FLASH_ATTR tcpclient_discon_cb(void *arg) { } //------------------------------------------------------ |
А функцию udp_client_udp_recv_cb пока удалим совсем вместе с телом.
Вместо неё добавим другую функцию, которая будет служить функцией обратного вызова при событии соединения с сервером
1 2 3 4 5 6 |
//------------------------------------------------------ static void ICACHE_FLASH_ATTR tcpclient_connect_cb(void *arg) { } //------------------------------------------------------ |
В функции tcp_connect объявим переменную типа структуры espconn и выделим для неё память
1 2 |
char udpserverip[15] = {}; struct espconn *pCon = (struct espconn *)os_zalloc(sizeof(struct espconn)); |
Если сделать это не удалось, то выведем соответствующее сообщение в терминальную программу и покинем тело функции
1 2 3 4 5 6 |
struct espconn *pCon = (struct espconn *)os_zalloc(sizeof(struct espconn)); if (pCon == NULL) { os_printf("TCP connect failed\r\n"); return; } |
Инициализируем тип и состояние соединения
1 2 3 4 |
return; } pCon->type = ESPCONN_TCP; pCon->state = ESPCONN_NONE; |
Скопируем адрес сервера в символьный массив
1 2 |
pCon->state = ESPCONN_NONE; os_sprintf(tcpserverip, "%s", TCPSERVERIP); |
Преобразуем строковое значение адреса сервера в целочисленное
1 2 |
os_sprintf(tcpserverip, "%s", TCPSERVERIP); ip = ipaddr_addr(tcpserverip); |
Выделим память под поле структуры
1 2 |
ip = ipaddr_addr(tcpserverip); pCon->proto.tcp = (esp_tcp *)os_zalloc(sizeof(esp_tcp)); |
Сгенерируем порт клиента и занесём его в соответствующее поле
1 2 |
pCon->proto.tcp = (esp_tcp *)os_zalloc(sizeof(esp_tcp)); pCon->proto.tcp->local_port = espconn_port(); |
А в поле порта сервера занесём значение из константы
1 2 |
pCon->proto.tcp->local_port = espconn_port(); pCon->proto.tcp->remote_port = TCPSERVERPORT; |
Затем занесём в соответствующее поле сетевой адрес сервера
1 2 |
pCon->proto.tcp->remote_port = TCPSERVERPORT; os_memcpy(pCon->proto.tcp->remote_ip, &ip, 4); |
Зарегистрируем функцию обратного вызова события соединения с сервером
1 2 |
os_memcpy(pCon->proto.tcp->remote_ip, &ip, 4); espconn_regist_connectcb(pCon, tcpclient_connect_cb); |
Вызовем функцию запуска соединения с сервером, перед этим выдав соответствующее сообщение в терминальную программу
1 2 3 |
espconn_regist_connectcb(pCon, tcpclient_connect_cb); os_printf("Start TCP connect...\r\n"); espconn_connect(pCon); |
В данной функции обратного вызова tcpclient_connect_cb объявим символьный массив для передачи строки серверу
1 2 3 |
tcpclient_connect_cb(void *arg) { char payload[30]; |
Объявим указатель на переменную структуры espconn, приравняв его адресу аргументов из первого параметра
1 2 |
char payload[30]; struct espconn *pConn = (struct espconn *)arg; |
Зарегистрируем функции обратного вызова обработки событий отправки пакета серверу и разрыва соединения с сервером
1 2 3 |
struct espconn *pConn = (struct espconn *)arg; espconn_regist_sentcb(pConn, tcpclient_sent_cb); espconn_regist_disconcb(pConn, tcpclient_discon_cb); |
Отправим строку серверу, занеся её перед этим в символьный массив
1 2 3 |
espconn_regist_disconcb(pConn, tcpclient_discon_cb); os_sprintf(payload, "Hello from ESP8266!!!\r\n"); espconn_sent(pConn, (uint8_t*) payload, strlen(payload)); |
В функции-обработчике отправки пакета также объявим указатель на переменную типа структуры espconn, присвоив ему адрес аргументов из входного параметра функции, оправим сообщение в терминальную программу и затем вызовем функцию разрыва соединения с сервером
1 2 3 4 5 |
void ICACHE_FLASH_ATTR tcpclient_sent_cb(void* arg) { struct espconn *pConn = (struct espconn *)arg; os_printf("TCP packet sent\r\n"); espconn_disconnect(pConn); |
В функции-обработчике завершения соединения с сервером мы также объявим указатель на переменную типа структуры espconn, присвоив ему адрес аргументов из входного параметра функции, очистим память структуры и поля tcp и выведем соответствующее сообщение в терминальной программе
1 2 3 4 5 6 7 |
static void ICACHE_FLASH_ATTR tcpclient_discon_cb(void *arg) { struct espconn *pConn = (struct espconn *)arg; os_free(pConn->proto.tcp); os_free(pConn); os_printf("TCP disconnected\r\n"); |
Соберём код, прошьём контроллер, запустим терминальную программу, в которой мы увидим, что наш клиент начнёт пытаться соединиться с сервером
Запустим WireShark, отфильтровавшись по адресу клиента, который мы также видим в терминальной программе.
Также запустим netcat, чтобы он начал слушать порт 30000
Перезагрузим нашу плату, так как клиент уже получил отказ в соединении, ибо мы в то время ещё не слушали порт.
Теперь мы увидим, что сервер получил строку, отправленную ему клиентом, и затем разъединился с ним, так как мы уже находимся в основной командной строке
Также мы это видим и в терминальной программе
И также в программе WireShark мы можем наблюдать, как мы успешно соединились с клиентом, получили от него пакет и затем разъединились
Отлично!
Теперь из нашего первого проекта сделаем ещё один проект с именем ESPCONN_TCP_CLIENT_02, задачей которого будет также соединиться с сервером, передать ему пакет, но уже разъединяться с ним только после того, как сервер передаст ответный пакет, так как в первом проекте мы не принимали пакеты от сервера.
Откроем наш новый проект и после функции tcpclient_sent_cb добавим ещё функцию-обработчик приёма пакетов TCP
1 2 3 4 5 6 |
//------------------------------------------------------ static void ICACHE_FLASH_ATTR tcpclient_recv_cb(void *arg, char* pusrdata, uint16_t length) { } //------------------------------------------------------ |
В функции tcpclient_connect_cb зарегистрируем данную функцию обратного вызова
1 2 |
espconn_regist_sentcb(pConn, tcpclient_sent_cb); espconn_regist_recvcb(pConn, tcpclient_recv_cb); |
В функции tcpclient_sent_cb удалим вызов функции разрыва соединения с сервером
espconn_disconnect(pConn);
В функции-обработчике приёма пакета добавим переменную для вывода принятых символов в терминальную программу, а также объявим указатель на переменную типа структуры espconn, присвоив ему адрес аргументов из входного параметра функции
1 2 3 4 5 |
static void ICACHE_FLASH_ATTR tcpclient_recv_cb(void *arg, char* pusrdata, uint16_t length) { uint16_t idx; struct espconn *pConn = (struct espconn *)arg; |
Отправим строку, принятую от сервера, посимвольно в терминальную программу и вызовем функцию разрыва соединения
1 2 3 4 5 6 |
struct espconn *pConn = (struct espconn *)arg; for(idx=0;idx<length;idx++) { uart_tx_one_char(UART0,pusrdata[idx]); } espconn_disconnect(pConn); |
Запустим порт на прослушивание с помощью программы netcat, соберём код, прошьём контроллер, соединимся с платой в терминальной программе и увидим, что строка наша также была передана серверу
Мы видим, что после передачи строки серверу соединение остаётся неразорванным.
Теперь попробуем передать строку клиенту в командной строке netcat
Теперь мы видим, что соединение разорвалось, так как нас выбросило в основную командную строку.
Также мы это можем заметить и по сообщениям в терминальной программе
В Wireshark мы также можем наблюдать, что после передачи пакета клиенту соединение было разорвано по инициативе клиента
Таким образом, на данном уроке нам удалось создать простой клиент TCP, который способен соединяться с сервером, разъединяться с ним, а также передавать ему и принимать от него пакеты.
Всем спасибо за внимание!
Предыдущий урок Программирование МК ESP8266 Следующий урок
Модуль ESP NodeMCU можно купить здесь: Модуль ESP NodeMCU
Различные модули ЕSP8266 можно приобрести здесь Модули ЕSP8266
Переходник USB to TTL можно приобрести здесь ftdi ft232rl
Многофункциональный переходник JTAG UART FIFO SPI I2C можно приобрести здесь CJMCU FT232H USB к JTAG UART FIFO SPI I2C
Логический анализатор 16 каналов можно приобрести здесь
Смотреть ВИДЕОУРОК в RuTube (нажмите на картинку)
Смотреть ВИДЕОУРОК в YouTube (нажмите на картинку)
Что то опять на исходный код ссылка не работает!
#define TCPSERVERIP «192.168.100.176»
static void ICACHE_FLASH_ATTR tcp_connect()
{
uint32_t ip;
char tcpserverip[15] = {}; // <—- 15? Не нужно ли тут предусмотреть место для конечного нуля?
os_sprintf(tcpserverip, "%s", TCPSERVERIP);