Урок 49
LAN. ENC28J60. TCP Server. Передаём данные
На прошлом занятии мы плотно познакомились с протоколом передачи данных TCP и научились устанавливать и разрывать соединение, так как это является неотъемлемым условием данного протокола.
Теперь перед нами встала задача следующая — а как же нам что-то передать полезное с помощью данного протокола. Иначе зачем он тогда нужен.
Вот этим мы сегодня и займёмся.
Проект мы назовём теперь ENC28J60_TCPS_DATA, а файлы с исходным кодом будем использовать из проекта предыдущего занятия с именем ENC28J60_TCPS_CONNECT.
Скомпилируем наш проект, прошьём контроллер и пока, не добавляя никакого кода попробуем послать данные в модуль.
Для этого мы сегодня уже попробуем другую утилиту — Putty, которую можно всегда скачать с официального сайта, и которая является абсолютно бесплатной.
Сначала, как обычно, запустим терминальную программу, в которой соединимся с нашим контроллером, а также запустим утилиту анализа сетевого трафика WireShark, в которой также подключимся к нашему модулю. Затем запустим утилиту Putty, в которой заполним IP-адрес модуля и порт 80, выберем тип соединения RAW, а также запретим закрывать окно автоматически
Мы можем данные настройки сохранить, добавить придуманное имя нашего соединения нажав кнопку Save, После чего появится строка с нашими сохранёнными настройками в списке соединений
После этого при запуске программы мы можем выбирать данную строку и, нажимая кнопку Load, восстанавливать все наши настройки и не вводить из каждый раз заново.
Теперь мы нажимаем кнопку Open и у нас откроется окно с командной строкой
Также в момент открытия нашего окна соединение также открывается, о чём свидетельствуют следующие строки в WireShark
Попробуем ввести какую-нибудь строку и нажать кнопку Enter
Мы получим следующие несколько строк в Wireshark (нажмите на картинку для увеличения изображения)
Всё дело в том, что клиент нам пытается отправить данные, но так как установлен флаг ACK, то это означает, что клиент также от нас ещё ждёт и подтверждения. Данные попытки не могут продолжаться бесконечно и, не дождавшись подтверждения, клиент рвёт с нами соединение. Так же об этом рапортует и Putty
Но нам это не важно сейчас, всё это мы впоследствии обработаем. Зато мы сейчас можем проанализировать, что именно нам передал клиент и с какими флагами. Мы видим также, что помимо флага ACK в пакете также присутствует флаг PSH, означающий, что данные мы должны сразу обработать.
Поэтому закрываем утилиту Putty и идём в наш проект, а в нём сначала попытаемся отобразить в терминальной программе пришедшую строку в качестве данных в пакете.
Для этого мы в функции tcp_read в файле tcp.h сначала проведём некоторые корректировки.
Давайте отобразим также в строке в терминальной программы вместо адресов MAC адреса IP источника и приёмника, а также отобразим размер данных, а как этот размер вычислить, я чуть позже расскажу. А пока же просто создадим для этого размера переменную в нашей функции и ещё переменную для счётчика
uint8_t res=0;
uint16_t len_data=0;
uint16_t i=0;
Строки подключения к пакетам перенесём выше, так как они нам нужны будут уже сейчас
uint16_t i=0;
ip_pkt_ptr *ip_pkt = (void*)(frame->data);
tcp_pkt_ptr *tcp_pkt = (void*)(ip_pkt->data);
Посчитаем длину данных в пакете, вычтя из длины пакета IP, которая находится в соответствующем поле включая данные, длину заголовка IP (20) и длину заголовка TCP, в которую входят также опции, а данные не входят
tcp_pkt_ptr *tcp_pkt = (void*)(ip_pkt->data);
len_data = be16toword(ip_pkt->len)-20-(tcp_pkt->len_hdr>>2);
И теперь мы отобразим в терминальной программе всё, что собирались отобразить
len_data = be16toword(ip_pkt->len)-20-(tcp_pkt->len_hdr>>2);
sprintf(str1,"%ld.%ld.%ld.%ld-%ld.%ld.%ld.%ld %d tcp\r\n",
ip_pkt->ipaddr_src & 0x000000FF,(ip_pkt->ipaddr_src>>8) & 0x000000FF,
(ip_pkt->ipaddr_src>>16) & 0x000000FF, ip_pkt->ipaddr_src>>24,
ip_pkt->ipaddr_dst & 0x000000FF,(ip_pkt->ipaddr_dst>>8) & 0x000000FF,
(ip_pkt->ipaddr_dst>>16) & 0x000000FF, ip_pkt->ipaddr_dst>>24, len_data);
USART_TX((uint8_t*)str1,strlen(str1));
Далее создадим условие, в которое мы зайдём, если в пакете будут данные, то есть результат будет ненулевой
USART_TX((uint8_t*)str1,strlen(str1));
//Если есть данные, то покажем их в терминальной программе
if (len_data)
{
}
Отобразим в терминальной программе данные в теле условия и перейдём на новую строку
if (len_data)
{
for (i=0;i<len_data;i++)
{
USART_Transmit(tcp_pkt->data[i]);
}
USART_TX((uint8_t*)"\r\n",2);
}
Соберём в очередной раз код и прошьём контроллер, затем соединимся с сервером и пошлём ту же строку
Мы видим, что данные приходят, причём почему-то сначала в данных находится только строка, а затем ещё и перевод строки с возвратом каретки.
Вообщем, строка к нам пришла, осталось нам подтвердить этот пакет, причём если пришла именно такая строка, то мы ещё и ответим на неё другой строкой, тем самым испытаем передачу данных в другом направлении.
В файле tcp.h добавим ещё один код операции
#define TCP_OP_ACK_OF_RST 3
#define TCP_OP_ACK_OF_DATA 4
Такой код мы будем использовать в случае если нам нужно будет подтверждать именно пакет с данными.
Не выходя из условия обнаружения данных в функции tcp_read, добавим код, согласно которому мы будем отвечать на пакет
USART_TX((uint8_t*)"\r\n",2);
//Если включен флаг подтверждения, то подтвердим приём данных
if (tcp_pkt->fl&TCP_ACK)
{
tcp_send(ip_pkt->ipaddr_src, be16toword(tcp_pkt->port_src), TCP_OP_ACK_OF_DATA);
}
Перейдём теперь в функцию отправки tcp_send и добавим там сначала ещё одну локальную переменную для размера полезных данных пакета TCP
uint32_t num_seg=0;
uint16_t sz_data=0;
Добавим там новое условие реакции на наш четрвёртый код опции
else if (op==TCP_OP_ACK_OF_DATA)
{
}
return res;
В тело данного условия мы скопируем код из предыдущего условия до первой отправки данных, а затем будем просто изменять его в некоторых местах
Сначала в самом начале тела условия вставим строку для сохранения размера полезных данных в переменную
//Заполним заголовок пакета TCP
sz_data = be16toword(ip_pkt->len)-20-(tcp_pkt->len_hdr>>2);
tcp_pkt->port_dst = be16toword(port);
И вот здесь вместо единички мы прибавим этот размер
tcp_pkt->num_ask = be32todword(be32todword(tcp_pkt->bt_num_seg) + sz_data);
Пока больше ничего не трогаем, попробуем собрать код и прошить контроллер и ввести ту же строку и посмотреть, прийдет ли наше подтверждение, так как если мы ошибёмся например в контрольной сумме, то клиенту наше подтверждение не понравится (нажмите на картинку для увеличения изображения)
Нам отправилось целых 2 пакета, и оба от нас подтверждены. Отлично!
Теперь давайте в ответ именно на эту строку ответим клиенту. Для этого продолжаем наше тело условия. А в нём создадим ещё одно условие
enc28j60_packetSend((void*)frame,len);
//Если пришло "Hello!!!", то отправим ответ
if (!strcmp((char*)tcp_pkt->data,"Hello!!!"))
{
}
А в тело уже этого добавленного условия мы добавим код, который занесёт в поле данных строку с нашим ответом и отправит пакет (флаги поставим такие же как ставил клиент при отправки нам строки)
if (!strcmp((char*)tcp_pkt->data,"Hello!!!"))
{
strcpy((char*)tcp_pkt->data,"Hello to TCP Client!!!\r\n");
tcp_pkt->fl = TCP_ACK|TCP_PSH;
sprintf(str1,"hdr_len:%d\r\n",sizeof(tcp_pkt_ptr));
USART_TX((uint8_t*)str1,strlen(str1));
len = sizeof(tcp_pkt_ptr);
tcp_pkt->len_hdr = len << 2;
len+=strlen((char*)tcp_pkt->data);
tcp_pkt->cs = 0;
tcp_pkt->cs=checksum((uint8_t*)tcp_pkt-8, len+8, 2);
//Заполним заголовок пакета IP
len+=sizeof(ip_pkt_ptr);
ip_pkt->len=be16toword(len);
ip_pkt->cs = 0;
ip_pkt->cs = checksum((void*)ip_pkt,sizeof(ip_pkt_ptr),0);
len+=sizeof(enc28j60_frame_ptr);
enc28j60_packetSend((void*)frame,len);
}
В очередной раз соберём код и прошьём контроллер и проверим наш «чат», в котором при попытке ввести строку «Hello!!!» нам сервер наш будет отвечать своей строкой, а при попытке ввода другой строки (в нашем случае почти такая же, но только с двумя восклицательными знаками, сервер нам уже строкой не отвечает
В терминальной программе мы также видим, что сервер нам уже строку несколько раз не шлёт
Завершим соединение, закрыв Putty. Можно также посмотреть в WireShark, что оно завершилось корректно двумя двухкратными рукопожатиями.
Затем давайте вернёмся в функцию приёма tcp_read и обработаем ещё один вариант, если вдруг с теми же флагами придет пакет без данных. В этом случае мы будем завершать соединение
else if (tcp_pkt->fl == (TCP_PSH|TCP_ACK))
{
//Если данных нет
if(!be16toword(ip_pkt->len)-20-(tcp_pkt->len_hdr>>2))
{
tcp_send(ip_pkt->ipaddr_src, be16toword(tcp_pkt->port_src), TCP_OP_ACK_OF_FIN);
}
}
else if (tcp_pkt->fl == TCP_ACK)
Соберём в очередной раз код и проверим его работоспособность.
Таким образом, сегодня мы уже продвинулись в знании протокола TCP, научившись принимать и отправлять малые порции данных в режиме сервера, само собой без применения окон и длинных потоков. Это уже достижение. Я думаю, если в будущем нам потребуется передать данные большей длины, то с этой задачей мы также справимся.
Предыдущий урок Программирование МК AVR Следующий урок
Приобрести плату Arduino UNO R3 можно здесь.
Приобрести программатор USBASP USBISP с адаптером можно здесь USBASP USBISP 3.3 с адаптером
Ethernet LAN Сетевой Модуль можно купить здесь ENC28J60 Ethernet LAN Сетевой Модуль.
Смотреть ВИДЕОУРОК в RuTube (нажмите на картинку)
Смотреть ВИДЕОУРОК в YouTube (нажмите на картинку)
Добавить комментарий