Урок 87
Часть 1
LAN. ENC28J60. TCP WEB Server. Передаём страницу побольше
Сегодня мы продолжим тему протокола HTTP, который очень активно используется в наше время для передачи данных.
На предыдущем занятии мы познакомились с данным протоколом, с его заголовком, с типами сообщений в заголовке, а также смогли сочинить небольшой сервер из нашего контроллера в паре с модулем на микросхеме ENC28J60 и передать страницу клиенту, который запросил её из браузера. Правда наша страница состояла из единственного пакета TCP, но тем не менее она корректно передалась.
Давайте сегодня попробуем передать страничку побольше, которая будет состоять уже из любого количества пакетов. В этом есть своя специфика, но, я думаю, мы с этим потихоньку разберёмся. Также мы по традиции опять немного оптимизируем и усовершенствуем наш проект, с чего, собственно, и начнём.
Мы добавили дополнительный входящий аргумент в функцию eth_send, находящуюся в файле net.c, которая была создана для передачи пакетов Ethernet, также поправили вызовы данной функции, тем самым заметив, что вызываем мы данную функцию только в двух местах проекта, хотя данные пакеты мы передаём постоянно. В осальных местах, как оказалось, мы данную функцию не используем, а обращаемся напрямик к низкоуровневой функции передачи фреймов, что вносит некоторую избыточность в код, так как приходится то. что находится в нашей функции, постоянно писать во всех местах кода. Давайте это поправим.
Для этого нам будет сначала необходим проект, который мы назовём ENC28J60_HTTPS_LARGE, а сделан он будет на основе проекта предыдущего занятия ENC28J60_HTTPS.
Запустим наш проект в Cube MX, сгенерируем проект для Keil, откроем его, настроим программатор на автоперезагрузку, на всякий случай отключим оптимизацию и подключим наши файлы в проект.
Начнём непосредственно с файла net.c. Найдём там функцию icmp_request и в самом низу удалим следующие строчки кода
//Заполним заголовок пакета Ethernet
memcpy(frame->addr_src,macaddr,6);
frame->type=ETH_IP;
enc28j60_packetSend((void*)frame,len + sizeof(enc28j60_frame_ptr));
Вместо них мы добавим следующее
//отправим пакет Ethernet
eth_send(frame,ETH_IP,len);
В данном файле больше менять нечего. Теперь идём в arp.c, найдём в нём функцию arp_request.
Удалим вот это
memcpy(frame->addr_src,macaddr,6);
frame->type = ETH_ARP;
enc28j60_packetSend((void*)frame,sizeof(arp_msg_ptr) + sizeof(enc28j60_frame_ptr));
А вставим вот это
//отправим пакет Ethernet
eth_send(frame,ETH_ARP,sizeof(arp_msg_ptr));
Есть ещё одно место — файл ntp.c функция ntp_request.
Удаляем (не всё, не забывая, оставить нужное)
//Заполним заголовок пакета Ethernet
memcpy(frame->addr_src,macaddr,6);
memcpy(frame->addr_dest,ntpprop.macaddr_dst,6);
frame->type=ETH_IP;
enc28j60_packetSend((void*)frame,len + sizeof(enc28j60_frame_ptr));
Вставляем
//Отправим пакет Ethernet
memcpy(frame->addr_dest,ntpprop.macaddr_dst,6);
eth_send(frame,ETH_IP,len);
Теперь поработаем с основной темой занятия. Для этого перейдём в файл tcp.c и попробуем теперь отделить запрос клиентом главной страницы от запросов других документов. Для этого в функции tcp_read исправим код, отслеживающий строку запроса, исключив оттуда пробел, чтобы нам теперь работать со всеми запросами HTTP, а не только с запросом главной страницы сервера. Также немного подправим и комментарий
//Если строка "GET /", то значит это запрос HTTP
if (strncmp((char*)tcp_pkt->data,"GET /", 5) == 0)
Зайдём в тело данного условия и добавим там следующее ветвление, разделяющее запрос главной страницы от запросов остальных документов
if (strncmp((char*)tcp_pkt->data,"GET /", 5) == 0)
{
//Если пробел, то это запрос главной страницы
if((char)tcp_pkt->data[5]==' ')
{
}
else
{
}
tcpprop.data_size = strlen(http_header) + sizeof(index_htm);
Перейдём в файл tcp.h и добавим там несколько вариантов документов. Пока у нас будет только два — главная страница и страница с ошибкой существования документа на сервере
//--------------------------------------------------
//Варианты документов HTTP
#define INDEX_HTML 0
#define E404_HTML 1
//--------------------------------------------------
Также в этом же заголовочном файле добавим ещё одно свойство для свойств TCP
volatile uint16_t cnt_rem_data_part;//количество оставшихся частей данных для передачи
volatile uint8_t http_doc;//вариант документа для передачи
} tcp_prop_ptr;
Заодно вначале данной структуры добавим также поле для хранения MAC-адреса, а также IP-адреса будущего получателя, так как хоть они и равны адресу отправителя, который отправил нам запрос, только зачастую они имеют свойство теряться
typedef struct tcp_prop {
uint8_t macaddr_dst[6];//MAC-адрес получателя
uint8_t ipaddr_dst[6];//IP-адрес получателя
Вернёмся в файл tcp.c и в функции tcp_read сразу проинициализируем данные поля структуры с адресами
tcp_pkt_ptr *tcp_pkt = (void*)(ip_pkt->data);
memcpy(tcpprop.macaddr_dst,frame->addr_src,6);
memcpy(tcpprop.ipaddr_dst,ip_pkt->ipaddr_src,4);
tcpprop.port_dst = be16toword(tcp_pkt->port_src);
Добавим ещё один вариант заголовка ответа HTTP для ответа на запрос несуществующего документа
const char http_header[] = {"HTTP/1.1 200 OK\r\nServer: nginx\r\nContent-Type: text/html\r\nConnection: keep-alive\r\n\r\n"};
const char error_header[] = {"HTTP/1.1 404 File not found\r\nServer: nginx\r\nContent-Type: text/html\r\nConnection: keep-alive\r\n\r\n"};
//--------------------------------------------------
Наш заголовок отличается от предыдущего только кодом ошибки существования документа 404 и самим сообщением об ошибке «File not found«.
А также после объявления массива с нашей главной страницей добавим ещё один массив со страницей ошибки. Правда, браузер скорей всего, увидев соответствующий заголовок ответа HTTP от сервера, будет либо выдавать свою страницу, либо будет предпринимать ещё какие-либо действия, но тем не менее мы данную страницу будем иметь на вооружении
0x3e,0x0a,0x3c,0x2f,0x62,0x6f,0x64,0x79,0x3e,0x3c,0x2f,0x68,0x74,0x6d,0x6c,0x3e};
const uint8_t e404_htm[] = {
0x3c,0x68,0x74,0x6d,0x6c,0x3e,0x0a,0x20,0x20,0x3c,0x68,0x65,0x61,0x64,0x3e,0x0a,
0x20,0x20,0x20,0x20,0x3c,0x74,0x69,0x74,0x6c,0x65,0x3e,0x34,0x30,0x34,0x20,0x4e,
0x6f,0x74,0x20,0x46,0x6f,0x75,0x6e,0x64,0x3c,0x2f,0x74,0x69,0x74,0x6c,0x65,0x3e,
0x0a,0x20,0x20,0x3c,0x2f,0x68,0x65,0x61,0x64,0x3e,0x0a,0x3c,0x62,0x6f,0x64,0x79,
0x3e,0x0a,0x3c,0x68,0x31,0x20,0x73,0x74,0x79,0x6c,0x65,0x3d,0x22,0x74,0x65,0x78,
0x74,0x2d,0x61,0x6c,0x69,0x67,0x6e,0x3a,0x20,0x63,0x65,0x6e,0x74,0x65,0x72,0x3b,
0x22,0x3e,0x34,0x30,0x34,0x20,0x45,0x72,0x72,0x6f,0x72,0x20,0x46,0x69,0x6c,0x65,
0x20,0x4e,0x6f,0x74,0x20,0x46,0x6f,0x75,0x6e,0x64,0x3c,0x2f,0x68,0x31,0x3e,0x0a,
0x3c,0x68,0x32,0x20,0x73,0x74,0x79,0x6c,0x65,0x3d,0x22,0x74,0x65,0x78,0x74,0x2d,
0x61,0x6c,0x69,0x67,0x6e,0x3a,0x20,0x63,0x65,0x6e,0x74,0x65,0x72,0x3b,0x22,0x3e,
0x20,0x54,0x68,0x65,0x20,0x70,0x61,0x67,0x65,0x20,0x79,0x6f,0x75,0x20,0x61,0x72,
0x65,0x20,0x6c,0x6f,0x6f,0x6b,0x69,0x6e,0x67,0x20,0x66,0x6f,0x72,0x20,0x6d,0x69,
0x67,0x68,0x74,0x20,0x68,0x61,0x76,0x65,0x20,0x62,0x65,0x65,0x6e,0x20,0x72,0x65,
0x6d,0x6f,0x76,0x65,0x64,0x2c,0x20,0x3c,0x62,0x72,0x20,0x2f,0x3e,0x68,0x61,0x64,
0x20,0x69,0x74,0x73,0x20,0x6e,0x61,0x6d,0x65,0x20,0x63,0x68,0x61,0x6e,0x67,0x65,
0x64,0x2c,0x20,0x6f,0x72,0x20,0x69,0x73,0x20,0x74,0x65,0x6d,0x70,0x6f,0x72,0x61,
0x72,0x69,0x6c,0x79,0x20,0x75,0x6e,0x61,0x76,0x61,0x69,0x6c,0x61,0x62,0x6c,0x65,
0x2e,0x3c,0x2f,0x68,0x32,0x3e,0x0a,0x3c,0x2f,0x62,0x6f,0x64,0x79,0x3e,0x3c,0x2f,
0x68,0x74,0x6d,0x6c,0x3e};
//-----------------------------------------------
Вернёмся теперь в тело нашего условия и заполним код пустых тел условия запроса документа, а строку вычисления длины документа, который мы будем посылать в ответ, удалим, так как она будет разной для наших вариантов
//Если пробел, то это запрос главной страницы
if((char)tcp_pkt->data[5]==' ')
{
tcpprop.http_doc = INDEX_HTML;
//сначала включаем в размер размер заголовка
tcpprop.data_size = strlen(http_header);
//затем размер самого документа
tcpprop.data_size += sizeof(index_htm);
}
else
{
tcpprop.http_doc = E404_HTML;
//сначала включаем в размер размер заголовка
tcpprop.data_size = strlen(error_header);
//затем размер самого документа
tcpprop.data_size += sizeof(e404_htm);
}
tcpprop.data_size = strlen(http_header) + sizeof(index_htm);
Теперь также мы должны разделить варианты заполнения поля с данными пакета TCP в функции отправки страницы tcp_send_http_one. Две строки с заполнением поля с данными заменим следующим кодом
//Отправляем страницу
if (tcpprop.http_doc==INDEX_HTML)
{
strcpy((char*)tcp_pkt->data,http_header);
memcpy((void*)(tcp_pkt->data+strlen(http_header)),(void*)index_htm,sizeof(index_htm));
}
else
{
strcpy((char*)tcp_pkt->data,error_header);
memcpy((void*)(tcp_pkt->data+strlen(error_header)),(void*)e404_htm,sizeof(e404_htm));
}
len = sizeof(tcp_pkt_ptr);
Теперь мы можем проверить наш код, скомпилировав его прошив контроллер. Сначала проверим, что у нас также работает передача главной страницы, а затем в адресной строке браузера введём запрос несуществующего документа с нашего сервера. Результат будет вот таким (нажмите на картинку для увеличения изображения)
Как я и предупреждал, браузер взамен нашей страницы предложил свою. Посмотрим также передачу нашей странице в WireShark (нажмите на картинку для увеличения изображения)
Мы видим. что ответ передан клиенту корректно и был нормально распознан.
Теперь перейдём к решению нашей главной задачи — передать клиенту страницу, состоящую не из одного, а из двух и более пакетов.
Опять зайдём в заголовочный файл tcp.h и внесём некоторые поправки в структуру TCP, изменив имя поля для количества оставшихся страниц
volatile uint16_t cnt_rem_data_part;//количество оставшихся частей данных для передачи
Старое имя нам также пригодится, но мы его добавим позже, иначе нам тяжело будет найти вхождение данной переменной в тексте кода, что мы сейчас сделаем по ошибкам, исправив в коде файла tcp.c везде старое имя поля на новое, а использовалось данное поле у нас пока только в функции tcp_read. Для этого соберём код и по ошибкам всё это и проделаем.
Затем вернёмся в нашу структуру и над этим полем вставим ещё одно со старым именем, но ещё и с другим комментарием
volatile uint16_t cnt_data_part;//общее количество частей данных для передачи
volatile uint16_t cnt_rem_data_part;//количество оставшихся частей данных для передачи
То есть в будущем для корректной работы кода нужно будет знать не только количество оставшихся пакетов, которое нам нужно будет передать, но ещё и помнить общее количества таких пакетов.
Вернёмся теперь в нашу функцию tcp_read в файл tcp.c и подумаем над тем, что может случиться следующая ситуация, при которой общее количество данных, которые мы собираемся передать клиенту разделится без остатка на максимальный размер сегмента. При этом мы можем встретиться с такой проблемой, при которой у нас размер оставшейся части будет нулевым, а количество пакетов будет завышено на один. Поэтому давайте с этим проведём некоторую борьбу
tcpprop.last_data_part_size = tcpprop.data_size % tcp_mss;
//борьба с неправильным расчётом, когда общий размер делится на минимальный размер сегмента без остатка
if(tcpprop.last_data_part_size==0)
{
tcpprop.last_data_part_size=tcp_mss;
tcpprop.cnt_rem_data_part--;
}
Также сохраним общее количество пакетов для передачи, пока их число не продекрементировалось
tcpprop.cnt_rem_data_part--;
}
tcpprop.cnt_data_part = tcpprop.cnt_rem_data_part;
Давайте также в функцию, которая занимается подготовкой заголовка TCP-пакета, добавим ещё один аргумент — длину для расчёта контрольной суммы, так как эта длина зачастую отличается от длины заголовка в силу того, что могут быть и данные в пакете, которые также участвуют в расчёте контрольной суммы
void tcp_header_prepare(tcp_pkt_ptr *tcp_pkt, uint16_t port, uint8_t fl, uint16_t len, uint16_t len_cs)
Также в конце данной функции исправим вызов функции расчёта контрольной суммы, используя данный аргумент
tcp_pkt->cs=checksum((uint8_t*)tcp_pkt-8, len_cs+8, 2);
}
Добавим также этот аргумент во всех местах вызова данной функции. Так как до сих пор у нас пока эта функция использовалась для подготовки заголовков пакетов, в которых данных не было, последние два аргумента в них будут совпадать
tcp_header_prepare(tcp_pkt, port, флаги, len, len);
Теперь добавим функцию, которая будет передавать первый пакет многопакетного ответа HTTP, так как до сих пор у нас была только одна функция, в которой мы передавали единственный пакет. В функции, которую мы сейчас добавим, мы сначала отправим пакет TCP с подтверждением на запрос HTTP клиента. Код функции мы разместим сразу после функции отправки единственного пакета HTTP
//--------------------------------------------------
/*Отправка первого пакета многопакетного ответа HTTP*/
uint8_t tcp_send_http_first(enc28j60_frame_ptr *frame, uint8_t *ip_addr, uint16_t port)
{
uint8_t res=0;
uint16_t len=0;
uint16_t sz_data=0;
ip_pkt_ptr *ip_pkt = (void*)(frame->data);
tcp_pkt_ptr *tcp_pkt = (void*)(ip_pkt->data);
//Отправим сначала подтверждение на пакет запроса
sz_data = be16toword(ip_pkt->len)-20-(tcp_pkt->len_hdr>>2);
tcpprop.seq_num = tcp_pkt->num_ask;
tcpprop.ack_num = be32todword(be32todword(tcp_pkt->bt_num_seg) + sz_data);
len = sizeof(tcp_pkt_ptr);
tcp_header_prepare(tcp_pkt, port, TCP_ACK, len, len);
len+=sizeof(ip_pkt_ptr);
ip_header_prepare(ip_pkt, ip_addr, IP_TCP, len);
//Заполним заголовок Ethernet
memcpy(frame->addr_dest,frame->addr_src,6);
eth_send(frame,ETH_IP,len);
return res;
}
//--------------------------------------------------
А дальше передадим наш пакет, который является первой частью нашего общего ответа HTTP
eth_send(frame,ETH_IP,len);
//Отправляем первую часть страницы
if (tcpprop.http_doc==INDEX_HTML)
{
strcpy((char*)tcp_pkt->data,http_header);
memcpy((void*)(tcp_pkt->data+strlen(http_header)),(void*)index_htm,tcp_mss-strlen(http_header));
}
else
{
strcpy((char*)tcp_pkt->data,error_header);
memcpy((void*)(tcp_pkt->data+strlen(error_header)),(void*)e404_htm,tcp_mss-strlen(error_header));
}
tcp_pkt->fl = TCP_ACK;
len = sizeof(tcp_pkt_ptr);
len+=tcp_mss;
tcp_pkt->cs = 0;
tcp_pkt->cs=checksum((uint8_t*)tcp_pkt-8, len+8, 2);
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);
//Заполним заголовок Ethernet
eth_send(frame,ETH_IP,len);
Данных у нас будет ровно столько, какой максимальный размер имеет наш сегмент, то есть 458, поэтому смело используем переменную mss.
Декрементируем оставшееся количество частей, так как одну мы уже передали
eth_send(frame,ETH_IP,len);
//будем считать, что одну часть отправили, поэтому количество оставшихся частей декрементируем
tcpprop.cnt_rem_data_part--;
Осталось нам в данной функции лишь только определить, последняя ли будет следующая часть или ещё нет и в соответствии с этим присвоить определённой переменной структуры нужный статус
tcpprop.cnt_rem_data_part—;
if(tcpprop.cnt_rem_data_part>1)
{
tcpprop.data_stat=DATA_MIDDLE;
}
else
{
tcpprop.data_stat=DATA_LAST;
}
return res;
}
То есть если количество оставшихся частей — одна, то мы будем пользоваться в следующей передаче функцией передачи последнего пакета, а если больше — то среднего пакета.
Давайте пока попробуем передать такую страницу, которая вместе с заголовком HTTP влезет в два пакета, так сказать, пойдём к цели постепенно. Для этого нам нужно будет такую страницу подготовить. Готовить мы её будем также, как и в прошлом занятии, с помощью утилиты makefsdata. Процесс я показывать не буду, так как мы это уже видели. Заменим массив страницы на новый
const uint8_t index_htm[] = {
0x3c,0x68,0x74,0x6d,0x6c,0x3e,0x3c,0x62,0x6f,0x64,0x79,0x3e,0x3c,0x68,0x31,0x20,
0x73,0x74,0x79,0x6c,0x65,0x3d,0x22,0x74,0x65,0x78,0x74,0x2d,0x61,0x6c,0x69,0x67,
0x6e,0x3a,0x20,0x63,0x65,0x6e,0x74,0x65,0x72,0x3b,0x22,0x3e,0x53,0x54,0x4d,0x33,
0x32,0x46,0x31,0x30,0x33,0x78,0x38,0x3c,0x62,0x72,0x3e,0x3c,0x62,0x72,0x3e,0x57,
0x45,0x42,0x20,0x53,0x65,0x72,0x76,0x65,0x72,0x3c,0x2f,0x68,0x31,0x3e,0x0a,0x3c,
0x70,0x3e,0x3c,0x2f,0x70,0x3e,0x0a,0x3c,0x68,0x32,0x3e,0x46,0x65,0x61,0x74,0x75,
0x72,0x65,0x73,0x3c,0x2f,0x68,0x32,0x3e,0x0a,0x3c,0x70,0x3e,0x41,0x52,0x4d,0xae,
0x20,0x33,0x32,0x2d,0x62,0x69,0x74,0x20,0x43,0x6f,0x72,0x74,0x65,0x78,0xae,0x2d,
0x4d,0x33,0x20,0x43,0x50,0x55,0x20,0x43,0x6f,0x72,0x65,0x3c,0x2f,0x70,0x3e,0x0a,
0x3c,0x70,0x3e,0x3c,0x75,0x6c,0x3e,0x0a,0x09,0x3c,0x6c,0x69,0x3e,0x37,0x32,0x20,
0x4d,0x48,0x7a,0x20,0x6d,0x61,0x78,0x69,0x6d,0x75,0x6d,0x20,0x66,0x72,0x65,0x71,
0x75,0x65,0x6e,0x63,0x79,0x2c,0x20,0x31,0x2e,0x32,0x35,0x20,0x44,0x4d,0x49,0x50,
0x53,0x2f,0x4d,0x48,0x7a,0x20,0x28,0x44,0x68,0x72,0x79,0x73,0x74,0x6f,0x6e,0x65,
0x20,0x32,0x2e,0x31,0x29,0x20,0x70,0x65,0x72,0x66,0x6f,0x72,0x6d,0x61,0x6e,0x63,
0x65,0x20,0x61,0x74,0x20,0x30,0x20,0x77,0x61,0x69,0x74,0x20,0x73,0x74,0x61,0x74,
0x65,0x20,0x6d,0x65,0x6d,0x6f,0x72,0x79,0x20,0x61,0x63,0x63,0x65,0x73,0x73,0x3c,
0x2f,0x6c,0x69,0x3e,0x0a,0x09,0x3c,0x6c,0x69,0x3e,0x53,0x69,0x6e,0x67,0x6c,0x65,
0x2d,0x63,0x79,0x63,0x6c,0x65,0x20,0x6d,0x75,0x6c,0x74,0x69,0x70,0x6c,0x69,0x63,
0x61,0x74,0x69,0x6f,0x6e,0x20,0x61,0x6e,0x64,0x20,0x68,0x61,0x72,0x64,0x77,0x61,
0x72,0x65,0x20,0x64,0x69,0x76,0x69,0x73,0x69,0x6f,0x6e,0x3c,0x2f,0x6c,0x69,0x3e,
0x0a,0x3c,0x2f,0x75,0x6c,0x3e,0x3c,0x2f,0x70,0x3e,0x0a,0x3c,0x70,0x3e,0x4d,0x65,
0x6d,0x6f,0x72,0x69,0x65,0x73,0x3c,0x2f,0x70,0x3e,0x0a,0x3c,0x70,0x3e,0x3c,0x75,
0x6c,0x3e,0x0a,0x09,0x3c,0x6c,0x69,0x3e,0x36,0x34,0x20,0x6f,0x72,0x20,0x31,0x32,
0x38,0x20,0x4b,0x62,0x79,0x74,0x65,0x73,0x20,0x6f,0x66,0x20,0x46,0x6c,0x61,0x73,
0x68,0x20,0x6d,0x65,0x6d,0x6f,0x72,0x79,0x3c,0x2f,0x6c,0x69,0x3e,0x0a,0x09,0x3c,
0x6c,0x69,0x3e,0x32,0x30,0x20,0x4b,0x62,0x79,0x74,0x65,0x73,0x20,0x6f,0x66,0x20,
0x53,0x52,0x41,0x4d,0x3c,0x2f,0x6c,0x69,0x3e,0x0a,0x3c,0x2f,0x75,0x6c,0x3e,0x3c,
0x2f,0x70,0x3e,0x0a,0x3c,0x70,0x3e,0x43,0x6c,0x6f,0x63,0x6b,0x2c,0x20,0x72,0x65,
0x73,0x65,0x74,0x20,0x61,0x6e,0x64,0x20,0x73,0x75,0x70,0x70,0x6c,0x79,0x20,0x6d,
0x61,0x6e,0x61,0x67,0x65,0x6d,0x65,0x6e,0x74,0x3c,0x2f,0x70,0x3e,0x0a,0x3c,0x70,
0x3e,0x3c,0x75,0x6c,0x3e,0x0a,0x09,0x3c,0x6c,0x69,0x3e,0x32,0x2e,0x30,0x20,0x74,
0x6f,0x20,0x33,0x2e,0x36,0x20,0x56,0x20,0x61,0x70,0x70,0x6c,0x69,0x63,0x61,0x74,
0x69,0x6f,0x6e,0x20,0x73,0x75,0x70,0x70,0x6c,0x79,0x20,0x61,0x6e,0x64,0x20,0x49,
0x2f,0x4f,0x73,0x3c,0x2f,0x6c,0x69,0x3e,0x0a,0x09,0x3c,0x6c,0x69,0x3e,0x50,0x4f,
0x52,0x2c,0x20,0x50,0x44,0x52,0x2c,0x20,0x61,0x6e,0x64,0x20,0x70,0x72,0x6f,0x67,
0x72,0x61,0x6d,0x6d,0x61,0x62,0x6c,0x65,0x20,0x76,0x6f,0x6c,0x74,0x61,0x67,0x65,
0x20,0x64,0x65,0x74,0x65,0x63,0x74,0x6f,0x72,0x20,0x28,0x50,0x56,0x44,0x29,0x3c,
0x2f,0x6c,0x69,0x3e,0x0a,0x3c,0x2f,0x75,0x6c,0x3e,0x3c,0x2f,0x70,0x3e,0x0a,0x3c,
0x2f,0x62,0x6f,0x64,0x79,0x3e,0x3c,0x2f,0x68,0x74,0x6d,0x6c,0x3e};
Обработаем статус DATA_FIRST в функции tcp_read
tcp_send_http_one(frame, ip_pkt->ipaddr_src, tcpprop.port_dst);
}
else if(tcpprop.data_stat==DATA_FIRST)
{
tcp_send_http_first(frame, ip_pkt->ipaddr_src, tcpprop.port_dst);
}
}
//Иначе обычные данные
Соберём код, прошьём контроллер, запросим нашу страницу в браузере, и хотя он её и не покажет, так как мы не всё ещё передали, но всё же посмотреть, то что мы уже передали мы можем хотя бы в утилите Wireshark (нажмите на картинку для увеличения изображения)
Мы видим, что наш пакет клиенту был отправлен и последний подтвердил его приём. Значит все контрольные суммы у нас в порядке и номера сегментов и подтверждений тоже. Иначе клиент никогда не подтвердит получение такого пакета (проверено).
Давайте зайдём в файл tcp.h и на будущее ещё обезопасимся от некоторого трабла, которого мы вследствие наших действий, не увидим.
Мы добавим в структуру свойств TCP ещё одно поле для номера сегмента, так как единственное иногда нужно бывает в процессе работы кода и вследствие этого не всегда хранится в неизменном виде
volatile uint32_t seq_num;//порядковый номер байта
volatile uint32_t seq_num_tmp;//порядковый номер байта временный
Вернёмся в файл tcp.c и в конце функции tcp_send_http_one занесём в наше новое поле значение
eth_send(frame,ETH_IP,len);
//Подготовим номер на будущее - на пакет с желанием завершить соединение
tcpprop.seq_num_tmp = be32todword(be32todword(tcpprop.seq_num)+tcpprop.data_size);
tcpprop.data_stat=DATA_END;
Теперь перейдём в тело функции tcp_send_http_dataend и исправим в ней некоторый код
tcpprop.seq_num = tcp_pkt->num_ask;
tcpprop.seq_num = tcpprop.seq_num_tmp;
В последствии нам данное поле ещё пригодится.
Код передачи среднего пакета мы писать пока не будем, так как наш ответ состоит из двух пакетов, сделаем это позже.
А пока же мы займёмся функцией передачи последнего пакета ответа HTTP. Добавим её после функции, которую мы только что написали
//--------------------------------------------------
/*Отправка последнего пакета многопакетного ответа HTTP*/
uint8_t tcp_send_http_last(enc28j60_frame_ptr *frame, uint8_t *ip_addr, uint16_t port)
{
uint8_t res=0;
uint16_t len_tcp=0, len=0;
ip_pkt_ptr *ip_pkt = (void*)(frame->data);
tcp_pkt_ptr *tcp_pkt = (void*)(ip_pkt->data);
return res;
}
//--------------------------------------------------
Вызовем данную функцию в конце функции tcp_read в обработке пакета TCP с подтверждением
else if (tcp_pkt->fl == TCP_ACK)
{
if (tcpprop.data_stat==DATA_END)
{
tcp_send_http_dataend(frame, ip_pkt->ipaddr_src, tcpprop.port_dst);
}
else if (tcpprop.data_stat==DATA_LAST)
{
HAL_UART_Transmit(&huart1,(uint8_t*)"LAST\r\n",6,0x1000);
tcpprop.data_stat=DATA_COMPLETED;
tcp_send_http_last(frame, tcpprop.ipaddr_dst, tcpprop.port_dst);
}
HAL_UART_Transmit(&huart1,(uint8_t*)"ACK\r\n",5,0x1000);
}
return res;
Вернёмся в нашу новую функцию tcp_send_http_last и продолжим писать её код.
Подготовим заголовок TCP и данные
tcp_pkt_ptr *tcp_pkt = (void*)(ip_pkt->data);
//Отправляем последнюю часть страницы
//Заполним заголовок пакета TCP
tcpprop.seq_num = be32todword(be32todword(tcpprop.seq_num)+tcp_mss);
len_tcp = sizeof(tcp_pkt_ptr);
if (tcpprop.http_doc==INDEX_HTML)
{
memcpy((void*)tcp_pkt->data,(void*)(index_htm+(tcp_mss*(tcpprop.cnt_data_part-1))-strlen(http_header)),tcpprop.last_data_part_size);
}
else
{
memcpy((void*)tcp_pkt->data,(void*)(e404_htm+(tcp_mss*(tcpprop.cnt_data_part-1))-strlen(error_header)),tcpprop.last_data_part_size);
}
len=len_tcp + tcpprop.last_data_part_size;
tcp_header_prepare(tcp_pkt, port, TCP_PSH|TCP_ACK, len_tcp, len);
Флаг мы здесь также используем PSH, так как пакет последий и требует от клиента немедленной обработки и подтверждения на него.
Теперь подготовим заголовок IP и отправким наш пакет
tcp_header_prepare(tcp_pkt, port, TCP_PSH|TCP_ACK, len_tcp, len);
len+=sizeof(ip_pkt_ptr);
ip_header_prepare(ip_pkt, ip_addr, IP_TCP, len);
//Заполним заголовок Ethernet
memcpy(frame->addr_dest,tcpprop.macaddr_dst,6);
eth_send(frame,ETH_IP,len);
Далее мы обновим значение номера сегмента для следующего пакета TCP. Это у нас будет запрос на разъединение
eth_send(frame,ETH_IP,len);
//продвинемся по номеру сегмента
tcpprop.seq_num_tmp = be32todword(be32todword(tcp_pkt->bt_num_seg)+tcpprop.last_data_part_size);
tcpprop.data_stat=DATA_END;
return res;
}
Соберём код, прошьём контроллер и попробуем вновь запросить страницу в браузере
Всё у нас отобразилось.
Также посмотрим процесс передачи данных в Wireshark (нажмите на картинку для увеличения изображения)
Здесь также всё отлично! Правда, чтобы этого добиться пришлось попотеть, поиграться с номерами сегментов, с контрольной суммы, так как если последняя будет некорректна, клиент никогда не даст подтверждение на отправленный ему пакет, хотя сам зачастую (а по-честному практически всегда) шлёт пакеты с некорректной контрольной суммой. Ну, ничего с этим не попишешь. Клиент всегда прав!
В следующей части урока мы напишем код для передачи клиенту страницы, состоящей из неограниченного количества пакетов TCP.
Предыдущий урок Программирование МК STM32 Следующая часть
Отладочную плату STM32F103C8T6 можно приобрести здесь STM32F103C8T6
Программатор недорогой можно купить здесь ST-Link V2
Ethernet LAN Сетевой Модуль можно купить здесь ENC28J60 Ethernet LAN Сетевой Модуль.
Переходник USB to TTL можно приобрести здесь ftdi ft232rl
Смотреть ВИДЕОУРОК в RuTube (нажмите на картинку)
Смотреть ВИДЕОУРОК в YouTube (нажмите на картинку)
Hello
Thank you very much for your tutorials for the enc28j60 module.
I need source code udp clinet project
enc28j60.h, enc28j60.c, net.h, net.c, udp.h, udp.c, tcp.h, tcp.c, …
How to get these files?
Pls help me.
Thank you.
In the last part of the lesson at the bottom of the page is a link to the source code of the project.