Подключение микроконтроллера к локальной сети: UDP-клиент

Краткое содержание:
- Введение в роутинг
- ARP-ресолвер
- Отправка пакетов
- Пример работы со стеком
- Заключение
Маршрутизация в Internet
Немножко теории. Рассмотрим основы роутинга в Internet. Думаю, большинство читателей могут эту часть спокойно пропустить)
Итак, возьмём, для примера, мою домашнюю сеть.

Допустим, комп знает IP-адрес девайса и хочет отправить ему пакет. При этом происходит следующее:
- Комп убеждается, что девайс находится в той же локальной сети, что и он сам.
- С помощью ARP, комп определяет MAC-адрес девайса.
- Комп заворачивает IP-пакет в Ethernet-фрейм и отрпавляет на MAC-адрес девайса.
- Девайс получает фрейм, видит в нём IP-пакет, в котором в качестве адреса получателя указан его собственный IP-адрес.
- Полученный IP-пакет передаётся протоколу транспортного уровня (UDP, etc.) и, затем, приложению.
Как комп определяет, что девайс находится с ним в одной локальной сети? Любой IP-адрес состоит из адреса подсети и адреса хоста. Именно адрес подсети определяет принадлежность IP-адреса к конкретной локальной сети.

Для выделения адреса подсети из IP-адреса служит маска подсети. Накладываем на IP-адрес маску и получаем адрес подсети.
В моей домашней сетке маска равна 255.255.255.0. Соответственно, адрес подсети равен 192.168.0.0, а допустимые IP-адреса — от 192.168.0.1 до 192.168.0.254 (первый и последний адреса зарезервированы). Видя, что адрес девайса относится к этому промежутку, комп и понимает — девайс находится с ним в одной локальной сети.
Но что, если мы хотим отправить пакет узлу, который находится не в нашей локальной сети, а вообще неизвестно где? Рассмотрим вот такую сеть (случай не то, чтобы очень жизненный, зато наглядный):

Запись вроде 192.168.3.0/24 означает подсеть с адресом 192.168.3.0 и маской 255.255.255.0 (24 бита установлено).
Допустим, 192.168.0.33 хочет отправить пакет узлу 192.168.3.3, находящемуся в сети 192.168.3.0. Происходит следующее:
- 192.168.0.33 записывает в IP пакет адрес отправителя 192.168.0.33 (свой), а адрес получателя — 192.168.3.3. Ничего необычного.
- Заворачивает пакет в Ethernet-фрейм и отправляет его на MAC-адрес узла 192.168.0.22.
- 192.168.0.22 получает фрейм, видит в нём пакет, предназначеный 192.168.3.3. Поскольку он подключен к обоим локальным сетям, он просто пересылает пакет узлу 192.168.3.3.
Как 192.168.0.33 узнает кому пересылать пакет? Для этого у него есть таблица роутов. Выглядит она примерно так:

Когда узел хочет отправить пакет в другую сеть, он просматривает свою таблицу роутов. В ней он находит адрес узла (гейт), на который нужно переслать пакет, чтобы он попал в нужную сеть. Последняя запись — роут по-умолчанию (default route). Она определяет основной гейт (default gateway) — узел, на который пересылаются все пакеты, роут для которых не прописан в явном виде.
Вернёмся к нашей сети. Все пакеты, выходящие за пределы сети проходят через роутер (192.168.0.1), он-то и является основным гейтом. Дополнительные роуты нам прописывать ни к чему, для отправки пакетов наружу достаточно знать адрес основного гейта.
Собирая вместе всё вышесказанное, можно отметить: для полноценной работы наш девайс должен знать три вещи — свой IP-адрес, маску подсети и IP-адрес основного гейта. Маска подсети используется, чтобы определять, относится ли определённый IP-адрес к нашей локальной сети. Все пакеты, выходящие за пределы локальной сети мы пересылаем основному гейту.
ARP-ресолвер
В предыдущей части мы научились отвечать на ARP-запросы. Чтобы пересылать пакеты другим узлам, нам понадобится также написать ARP-ресолвер.
Алгоритм работы нашего ARP-ресолвера будет следующий:
- Когда нам нужно определить MAC-адрес узла, ищем его в ARP-кэше по IP-адресу.
- Если узел найден в кэше, просто возвращаем его MAC-адрес.
- Если узел в кэше не найден, посылаем широковещательный ARP-запрос, чтобы найти нужный узел.
- Получив ответ на наш запрос, добавляем узел в ARP-кэш.
Добавлять узлы в ARP-кэш будем по кругу (т.е при добавлении новой записи, самая старая будет затираться).
Проверять валидность записей кэша мы не будем — врядли MAC-адрес какого-либо узла внезапно изменится.
// Размер ARP-кэша
#define ARP_CACHE_SIZE 3
// ARP-кэш
typedef struct arp_cache_entry {
uint32_t ip_addr;
uint8_t mac_addr[6];
} arp_cache_entry_t;
uint8_t arp_cache_wr;
arp_cache_entry_t arp_cache[ARP_CACHE_SIZE];
// Поиск в ARP-кэше
uint8_t *arp_search_cache(uint32_t node_ip_addr)
{
uint8_t i;
for(i = 0; i < ARP_CACHE_SIZE; ++i)
{
if(arp_cache[i].ip_addr == node_ip_addr)
return arp_cache[i].mac_addr;
}
return 0;
}
// ARP-ресолвер
// Если MAC-адрес узла известен, возвращает его
// Неизвестен - посылает запрос и возвращает 0
uint8_t *arp_resolve(uint32_t node_ip_addr)
{
eth_frame_t *frame = (void*)net_buf;
arp_message_t *msg = (void*)(frame->data);
uint8_t *mac;
// Ищем узел в кэше
if((mac = arp_search_cache(node_ip_addr)))
return mac;
// Отправляем запрос
memset(frame->to_addr, 0xff, 6);
frame->type = ETH_TYPE_ARP;
msg->hw_type = ARP_HW_TYPE_ETH;
msg->proto_type = ARP_PROTO_TYPE_IP;
msg->hw_addr_len = 6;
msg->proto_addr_len = 4;
msg->type = ARP_TYPE_REQUEST;
memcpy(msg->mac_addr_from, mac_addr, 6);
msg->ip_addr_from = ip_addr;
memset(msg->mac_addr_to, 0x00, 6);
msg->ip_addr_to = node_ip_addr;
eth_send(frame, sizeof(arp_message_t));
return 0;
}
// Обработчик ARP-пакетов
void arp_filter(eth_frame_t *frame, uint16_t len)
{
arp_message_t *msg = (void*)(frame->data);
// Проверяем длину пакета
if(len >= sizeof(arp_message_t))
{
// Ethernet <> IP и наш IP-адрес
if( (msg->hw_type == ARP_HW_TYPE_ETH) &&
(msg->proto_type == ARP_PROTO_TYPE_IP) &&
(msg->ip_addr_to == ip_addr) )
{
switch(msg->type)
{
// ARP-запрос, посылаем ответ
case ARP_TYPE_REQUEST:
msg->type = ARP_TYPE_RESPONSE;
memcpy(msg->mac_addr_to, msg->mac_addr_from, 6);
memcpy(msg->mac_addr_from, mac_addr, 6);
msg->ip_addr_to = msg->ip_addr_from;
msg->ip_addr_from = ip_addr;
eth_reply(frame, sizeof(arp_message_t));
break;
// ARP-ответ, добавляем узел в кэш
case ARP_TYPE_RESPONSE:
if(!arp_search_cache(msg->ip_addr_from))
{
arp_cache[arp_cache_wr].ip_addr = msg->ip_addr_from;
memcpy(arp_cache[arp_cache_wr].mac_addr, msg->mac_addr_from, 6);
arp_cache_wr++;
if(arp_cache_wr == ARP_CACHE_SIZE)
arp_cache_wr = 0;
}
break;
}
}
}
}
Отправка пакетов
Теперь мы готовы отправлять IP-пакеты любым узлам, подключенным к интернету. Для начала, нам понадобится определить наш IP-адрес, маску подсети и основной гейт.
uint32_t ip_addr = inet_addr(192,168,0,222);
uint32_t ip_mask = inet_addr(255,255,255,0);
uint32_t ip_gateway = inet_addr(192,168,0,1);
Алгоритм отправки IP-пакета будет простой:
- Определяем IP-адрес узла, на который будем отправлять Etheret-фрейм, содержащий пакет. Если пакет пересылается в пределах локальной сети, сразу посылаем его нужному узлу. Иначе будем отправлять фрейм основному гейту.
- Ресолвим MAC-адрес узла.
- Заворачиваем IP-пакет в Ethernet-фрейм и посылаем.
// Отправка Ethernet-фрейма
// Должны быть установлены следующие поля:
// - frame.to_addr - MAC-адрес получателя
// - frame.type - протокол
// len - длина поля данных фрейма
void eth_send(eth_frame_t *frame, uint16_t len)
{
memcpy(frame->from_addr, mac_addr, 6);
enc28j60_send_packet((void*)frame, len +
sizeof(eth_frame_t));
}
// Отправка IP-пакета
// Следующие поля пакета должны быть установлены:
// ip.to_addr - адрес получателя
// ip.protocol - код протокола
// len - длина поля данных пакета
// Если MAC-адрес узла/гейта ещё не определён, функция возвращает 0
uint8_t ip_send(eth_frame_t *frame, uint16_t len)
{
ip_packet_t *ip = (void*)(frame->data);
uint32_t route_ip;
uint8_t *mac_addr_to;
// Если узел в локалке, отправляем пакет ему
// если нет, то гейту
if( ((ip->to_addr ^ ip_addr) & ip_mask) == 0 )
route_ip = ip->to_addr;
else
route_ip = ip_gateway;
// Ресолвим MAC-адрес
if(!(mac_addr_to = arp_resolve(route_ip)))
return 0;
// Отправляем пакет
len += sizeof(ip_packet_t);
memcpy(frame->to_addr, mac_addr_to, 6);
frame->type = ETH_TYPE_IP;
ip->ver_head_len = 0x45;
ip->tos = 0;
ip->total_len = htons(len);
ip->fragment_id = 0;
ip->flags_framgent_offset = 0;
ip->ttl = IP_PACKET_TTL;
ip->cksum = 0;
ip->from_addr = ip_addr;
ip->cksum = ip_cksum(0, (void*)ip, sizeof(ip_packet_t));
eth_send(frame, len);
return 1;
}
Ну и отправка UDP-пакета.
// Отправляет UDP-пакет
// Должны быть установлены следующие поля:
// ip.to_addr - адрес получателя
// udp.from_port - порт отрпавителя
// udp.to_port - порт получателя
// len - длина поля данных пакета
// Если MAC-адрес узла/гейта ещё не определён, функция возвращает 0
uint8_t udp_send(eth_frame_t *frame, uint16_t len)
{
ip_packet_t *ip = (void*)(frame->data);
udp_packet_t *udp = (void*)(ip->data);
len += sizeof(udp_packet_t);
ip->protocol = IP_PROTOCOL_UDP;
ip->from_addr = ip_addr;
udp->len = htons(len);
udp->cksum = 0;
udp->cksum = ip_cksum(len + IP_PROTOCOL_UDP,
(uint8_t*)udp-8, len+8);
return ip_send(frame, len);
}
Пишем приложение
Чисто чтобы потестить наш улучшенный стек, попробуем получить какую-нибудь информацию из интернета. Например, точное время по NTP.
NTP реализуем самым тупым способом — отрпавляем серверу запрос, получаем ответ с точным временем. Для правильной работы NTP, локальный UDP-порт не должен равняться UDP-порту сервера (123). Также, нужно учитывать, что NTP возвращает неправильный timestamp — количество секунд, прошедших с 1 января 1900 года. Чтобы получить нормальный timestamp, считающийся с 1 января 1970, года, нужно отнять от NTP-timestamp'а ровно 2208988800 секунд.
// Порт NTP-сервера
#define NTP_SRV_PORT htons(123)
// Локальный порт
#define NTP_LOCAL_PORT htons(14444)
// Фомат времени в NTP - время в секундах с 1 января 1900 г.
// В формате fixed point 32:32
typedef struct ntp_timestamp {
uint32_t seconds; // целая часть
uint32_t fraction; // дробная часть
} ntp_timestamp_t;
// Формат NTP-сообщения
typedef struct ntp_message {
// информация о пакете
uint8_t status;
// информация об эталонных часах (тип, точность, etc.)
uint8_t type;
uint16_t precision;
uint32_t est_error;
uint32_t est_drift_rate;
uint32_t ref_clock_id;
// информация о времени
ntp_timestamp_t ref_timestamp; // установки эталонных часов
ntp_timestamp_t orig_timestamp; // отправки пакета клиентом
ntp_timestamp_t recv_timestamp; // получения пакета сервером
ntp_timestamp_t xmit_timestamp; // отправки пакета сервером
} ntp_message_t;
// Отправка запроса на NTP-сервер
uint8_t ntp_request(uint32_t srv_ip)
{
eth_frame_t *frame = (void*)net_buf;
ip_packet_t *ip = (void*)(frame->data);
udp_packet_t *udp = (void*)(ip->data);
ntp_message_t *ntp =(void*)(udp->data);
ip->to_addr = srv_ip;
udp->to_port = NTP_SRV_PORT;
udp->from_port = NTP_LOCAL_PORT;
// ntp.status = 8
// остальные поля заполняем нулями
memset(ntp, 0, sizeof(ntp_message_t));
ntp->status = 0x08;
return udp_send(frame, sizeof(ntp_message_t));
}
// Обработка ответа NTP-сервера
uint32_t ntp_parse_reply(void *data, uint16_t len)
{
ntp_message_t *ntp = data;
uint32_t temp;
// Проверяем длину пакета
if(len >= sizeof(ntp_message_t))
{
// Переводим в нормальный timestamp и возвращаем
temp = ntp->xmit_timestamp.seconds;
return (ntohl(temp) - 2208988800UL);
}
return 0;
}
С помощью NTP, будем запрашивать время каждые 12 часов.
// Цепляем библиотеку для HD44780
#include "hd44780.h"
// Адрес NTP-сервера
// !!! Никогда не хардкодь адрес серевера в рабочий, не учебный девайс !!!
#define NTP_SERVER inet_addr(62,117,76,142)
// Часовой пояс. Для простоты забьём его константой
#define TIMEZONE 7
// Счётчик времени с момента включения девайса
static volatile uint16_t ms_count;
static volatile uint32_t second_count;
// Время следующего NTP-запроса
static volatile uint32_t ntp_next_update;
// Точное время (относительно момента second_count = 0)
static volatile uint32_t time_offset;
// Таймер на частоту 1 кГц
ISR(TIMER0_COMP_vect)
{
if(++ms_count == 1000)
{
++second_count;
ms_count = 0;
}
}
// Обработчик получаемых UDP-пакетов
void udp_packet(eth_frame_t *frame, uint16_t len)
{
ip_packet_t *ip = (void*)(frame->data);
udp_packet_t *udp = (void*)(ip->data);
uint32_t timestamp;
// Получили ответ от NTP-сервера?
if(udp->to_port == NTP_LOCAL_PORT)
{
if((timestamp = ntp_parse_reply(udp->data, len)))
{
// Запоминаем время
time_offset = timestamp - second_count;
// Следующее обновление через 12 часов
ntp_next_update = second_count + 12UL*60*60;
}
}
}
int main()
{
static uint8_t net_buf[512];
eth_frame_t *frame = (void*)net_buf;
uint32_t display_next_update = 0;
uint32_t loctime;
uint8_t s, m, h;
char buf[5];
// Инициализируем ENC28J60
enc28j60_init(mac_addr);
// Инициализируем LCD
_delay_ms(30);
hd44780_init();
hd44780_mode(1,1,0);
hd44780_clear();
// Инициализируем Таймер 0 в режиме CTC, 1 кГц (при FCLK = 16 МГц)
TCCR0 = (1<<WGM01)|(1<<CS01)|(1<<CS00);
OCR0 = 250;
TIMSK |= (1<<OCIE0);
sei();
while(1)
{
// Ловим пакеты
if((len = enc28j60_recv_packet(net_buf, sizeof(net_buf))))
eth_filter(frame, len);
// Пора отправить NTP-запрос
if(second_count >= ntp_next_update)
{
// Пробуем отправить запрос на NTP-сервер
if(!ntp_request(NTP_SERVER))
{
// Пакет не отрпавлен - MAC-адрес гейта ещё не известен
// попробуем снова через 2 секунды
ntp_next_update = second_count+2;
}
else
{
// Пакет отправлен - если ответ не получим,
// попробуем снова через 60 секунд
ntp_next_update = second_count+60;
}
}
// Пора обновить данные на экране (точное время известно)
if((time_offset) && (second_count >= display_next_update))
{
// Вычисляем время
loctime = time_offset+second_count + 60UL*60*TIMEZONE;
s = loctime % 60;
m = (loctime/60)%60;
h = (loctime/3600)%24;
// Рисуем время на экране
hd44780_clear();
itoa(h,buf,10);
hd44780_puts(buf);
hd44780_puts(":");
itoa(m,buf,10);
hd44780_puts(buf);
hd44780_puts(":");
itoa(s,buf,10);
hd44780_puts(buf);
// Следующее обновление через 1 секунду
display_next_update = second_count+1;
}
}
return 0;
}
Потестируем то, что получилось.
Заключение
Скачать проект можно тут.
В следующей части мы немного отвлечёмся от микроконтроллеров и посмотрим как можно общаться с сетевыми девайсами со стороны компа.

Все статьи цикла
- Подключение микроконтроллера к локальной сети: Теория
- Подключение микроконтроллера к локальной сети: работаем с ENC28J60
- Подключение микроконтроллера к локальной сети: UDP-сервер
- Подключение микроконтроллера к локальной сети: UDP-клиент
- Подключение микроконтроллера к локальной сети: Широковещательные сообщения и DHCP
- Подключение микроконтроллера к локальной сети: TCP-клиент
- Подключение микроконтроллера к локальной сети: HTTP и CGI
- Подключение микроконтроллера к локальной сети: TCP и HTTP (продолжение)
- Подключение микроконтроллера к локальной сети: HTTP и CGI (заключение)
- Подключение микроконтроллера к локальной сети: тесты производительности и краткое описание API стека
- Подключение микроконтроллера к локальной сети: Заключение
- Веб сервер на Tiny2313. Чисто ради лулзов
- +18
- 01 апреля 2011, 19:51
- Lifelover
Как раз все понятно. И все раскладывается постепенно по полочкам. До этого у меня в этом вопросе был некоторый сумбур и беспорядок, в связи с тем, что вся инфа в интернете довольно фрагментирована. Там кусок, сям кусок и пока до всего дойдешь и соберешь воедино, что пропадает всякая охота заниматься этим вопросом. Несколько раз брался и бросал.
Так что огромное Вам мерси Мастер и дальнейших успехов в вашем творчестве! Ждем с нетерпением следующих статей.
Так что огромное Вам мерси Мастер и дальнейших успехов в вашем творчестве! Ждем с нетерпением следующих статей.
Мёртвые маки из ARP таблицы лучше вычищать быстро и безжалостно. т.е. если по запросу к маку нет ответа — нахрен его из ARP таблицы, ибо бывает так, что выдернули один девайс, воткнули другой, IP один а коннекта нет.
Ну не знаю. Конечно, трудов положено много, задумка неплохая, но. Много досадных непоняток. Вот например «Но что, если мы хотим отправить пакет узлу, который находится не в нашей локальной сети, а вообще неизвестно где?» и тут же следует пример, где не «неизвестно куда» шлет пакет 192.168.0.33, а оказывается у него есть таблица роутов и он знает куда слать. Неплавно как-то изложено, прыжками.
И к скачанному примеру к этой статье есть вопросы, почему переменная uint32_t second_count изменяется в прерывании и преспокойненько считывается в main безо всякин намёков на критическую секцию? volatile здесь не поможет.
Простите.
И к скачанному примеру к этой статье есть вопросы, почему переменная uint32_t second_count изменяется в прерывании и преспокойненько считывается в main безо всякин намёков на критическую секцию? volatile здесь не поможет.
Простите.
Вот например «Но что, если мы хотим отправить пакет узлу, который находится не в нашей локальной сети, а вообще неизвестно где?» и тут же следует пример, где не «неизвестно куда» шлет пакет 192.168.0.33, а оказывается у него есть таблица роутов и он знает куда слать.Вполне нормально. Там показано как это вообще делается — таблица маршрутов содержит записи, определяющие через какой гейт слать пакет, чтобы он попал в такую-то сеть. Затем сказано, что для сабжа используется такая таблица из только одного адреса — главного гейта, т.е. все пакеты не в свою сеть будут отправлены ему.
И к скачанному примеру к этой статье есть вопросы, почему переменная uint32_t second_count изменяется в прерывании и преспокойненько считывается в main безо всякин намёков на критическую секцию? volatile здесь не поможет.Синхронизация доступа нужна только при записи. При чтении пофиг. Разве что то, что она многобайтная — если изменится между чтением отдельных байт, то могут быть траблы.
1. Угу. Это называется «неизвестно где»?Да. В данном примере гейт непосредственно смотрит в эту сеть, но в принципе она может быть от него еще через несколько хопов. Ну и под «неизвестно где» видимо подразумевалось «не в локалке». В общем, на мой взгляд вполне понятно написано, хотя я в принципе с сетями немного знаком.
2. uint32_t это по-Вашему сколько байт?Я и говорю, многобайтная. Но я не знаю, как компилятор читает многобайтные volatile переменные. Мож он там cli/sei вставляет.
Снимаю шапку перед Lifelover-ом за труды. Потихоньку начал понимать что к чему. Повторил проект с часами, адаприровав его к PIC24FJ256GB106 Только вот не могу понять почему он (проект) работает только с ntp1.vniiftri.ru (IP = 62.117.76.142) сервером указаном в проекте. Другие почему то не отвечают, например time.nist.gov (IP = 207.200.81.113) Хотя например на компе (интернет время) прекрасно синхронизируется с любым из них. В чем может быть причина? И еще, в статье написано локальный UDP-порт не должен равняться UDP-порту сервера (123). Но снифер показывает (при синхронизации времени с компа) оба порта равны 123
P.S. Просто разобраться хочется…
P.S. Просто разобраться хочется…
Lifelover, прошу помочь. Проблема с ARP резолвером. С моей железки резолвятся маки всех машин в локалке, НО роутер категорически отказывается отвечать на ARP пакеты. Посниферил ARP обмен между роутером, железкой и компом. Есть различия в запросах: запросы от железки на 18 байт длинее (заполнены нулями последние 18 байт) запросов от компа. Тем не менее комп на эти запросы выдает свой мак, а роутер «вертел их на своем х...» и молчит как партизан. причем пробовал дома (dir-100) и на работе (cisco), результат тот же.
Хочу обратить внимание на IP адрес NTP сервера указанного в проекте — 62.117.76.142. Данный IP адрес уже не действителен -> www.vniiftri.ru/index.php/ru/news/publicnews/198-smena-ip-adresov-ntp-serverov < — для успешной синхронизации используйте NTP ntp1.vniiftri.ru — 89.109.251.21. Хотя, когда вы будете читать это сообщение — может и он уже устареет, так что проверяйте.
P.S. Данная проблема вроде и очевидна, но я потратил несколько дней перепахивая код (проект на STM32), и пытаясь понять почему нет ответа от NTP.
P.S. Данная проблема вроде и очевидна, но я потратил несколько дней перепахивая код (проект на STM32), и пытаясь понять почему нет ответа от NTP.
Вообще правильнее всего, если есть возможность резолвить DNS имена, использовать пулы типа 0.debian.pool.ntp.org, 1.debian.pool.ntp.org, 2.debian.pool.ntp.org.
www.pool.ntp.org/ru/
www.pool.ntp.org/ru/
Нашел на меня невероятный тупняк. Не могу понять как происходит преобразование. Смотрю переменную loctime в отладчике.
loctime = 1395593785
s(25) = loctime % 60;
m(56)= (loctime/60)%60;
h(16)= (loctime/3600)%24;
s = 1395593785 % 60 (% я так понимаю — остаток от деления)
1395593785 / 60 = [23259896,] 4166666666666666 ну ни как не получается 25.
loctime = 1395593785
s(25) = loctime % 60;
m(56)= (loctime/60)%60;
h(16)= (loctime/3600)%24;
s = 1395593785 % 60 (% я так понимаю — остаток от деления)
1395593785 / 60 = [23259896,] 4166666666666666 ну ни как не получается 25.
Доброго времени суток!
Выражаю огромную благодарность за труд автора, за хорошую подачу материала и простоту объяснения довольно сложных механизмов.
Однако у меня возник вопрос касательно описанных структур заголовков протоколов.
А конкретно — про выравнивание данных в оперативной памяти. Допустим, есть структура заголовка ethernet:
typedef struct
{
unsigned char to[6];
unsigned char from[6];
unsigned short int type;
}ethernet_head;
Ну и допустим, есть буффер, куда данные складываются: unsigned char net_buf[512].
По приходу данных делаем следующую процедуру:
ethernet_head *Eth = (ethernet_head *)netbuf;
if(type == 0x0800)
…
Но это будет работать только на МК с 8-разрядной архитектурой, т.к. процессоры таких ядер оперируют со словами в 1 байт. Гранулярность других вычислительных платформ (тот же STM32) варьируется, и в общем случае составляет 4 байта. Компилятор выровняет поля создаваемой структуры до целых слов, чтобы процессор быстрее выполнял операции над данными, состоящими из нескольких байт. А это значит, что при наложении структуры на массив данных, пришедших через Ethernet, все покорежится. Структуры можно упаковать (заставить компилятор не выравнивать поля) директивой #pragma pack()...pop(), но это не шибко правильно и возможно решается это по-другому. Прошу уточнения данного аспекта. Благодарю!
Выражаю огромную благодарность за труд автора, за хорошую подачу материала и простоту объяснения довольно сложных механизмов.
Однако у меня возник вопрос касательно описанных структур заголовков протоколов.
А конкретно — про выравнивание данных в оперативной памяти. Допустим, есть структура заголовка ethernet:
typedef struct
{
unsigned char to[6];
unsigned char from[6];
unsigned short int type;
}ethernet_head;
Ну и допустим, есть буффер, куда данные складываются: unsigned char net_buf[512].
По приходу данных делаем следующую процедуру:
ethernet_head *Eth = (ethernet_head *)netbuf;
if(type == 0x0800)
…
Но это будет работать только на МК с 8-разрядной архитектурой, т.к. процессоры таких ядер оперируют со словами в 1 байт. Гранулярность других вычислительных платформ (тот же STM32) варьируется, и в общем случае составляет 4 байта. Компилятор выровняет поля создаваемой структуры до целых слов, чтобы процессор быстрее выполнял операции над данными, состоящими из нескольких байт. А это значит, что при наложении структуры на массив данных, пришедших через Ethernet, все покорежится. Структуры можно упаковать (заставить компилятор не выравнивать поля) директивой #pragma pack()...pop(), но это не шибко правильно и возможно решается это по-другому. Прошу уточнения данного аспекта. Благодарю!
Hello my friend!
Старина well-man даже написал маленький дайджест на тему того, о чем ты спрашиваешь.
Старина well-man даже написал маленький дайджест на тему того, о чем ты спрашиваешь.
- well-man2000
- 22 октября 2014, 15:57
- ↑
- ↓
Отличный цикл статей.Интересно всё описано.
А как бы так сделать посылку пакета на WEB сервер и дёргать там php файл.
В команду вложить значение температурного датчика, например DATCH=22.
Пробую сделать функцию, за основу взял ntp_request.
Ничего не получается, мало знаний ещё, во многом путаюсь.
Может кто поможет с кодом?
Очень был бы благодарен.
Спасибо.
А как бы так сделать посылку пакета на WEB сервер и дёргать там php файл.
В команду вложить значение температурного датчика, например DATCH=22.
Пробую сделать функцию, за основу взял ntp_request.
Ничего не получается, мало знаний ещё, во многом путаюсь.
Может кто поможет с кодом?
Очень был бы благодарен.
Спасибо.
Снова здравствуйте. Будьте так добры подсказать некоторые моменты.
На основе предыдущего примера удалось принять UDP сообщение на комп. А вот наоборот, отправить в комп UDP сообщение, не получается. Что я поменял
#define NTP_SERVER inet_addr(192,168,0,102) // 102 адрес моего компа
typedef struct ntp_message {
// информация о пакете
uint8_t udp_test_message;
} ntp_message_t;
uint8_t ntp_request(uint32_t srv_ip)
{
eth_frame_t *frame = (void*)net_buf;
ip_packet_t *ip = (void*)(frame->data);
udp_packet_t *udp = (void*)(ip->data);
ntp_message_t *ntp =(void*)(udp->data);
ip->to_addr = srv_ip;
udp->to_port = NTP_SRV_PORT;
udp->from_port = NTP_LOCAL_PORT;
// ntp.status = 8
// остальные поля заполняем нулями
ntp->udp_test_message = '3';
return udp_send(frame, sizeof(ntp_message_t));
}
Ну и соответственно вызываю полученную функцию. Подскажите, что я делаю не так? Как мне таки получить сообщение на комп. Комп видит что пакет приходит, но UDP клиент\сервер его не наблюдает. Заранее спасибо, ну видимо Vga. Чтоб я без вас делал)
На основе предыдущего примера удалось принять UDP сообщение на комп. А вот наоборот, отправить в комп UDP сообщение, не получается. Что я поменял
#define NTP_SERVER inet_addr(192,168,0,102) // 102 адрес моего компа
typedef struct ntp_message {
// информация о пакете
uint8_t udp_test_message;
} ntp_message_t;
uint8_t ntp_request(uint32_t srv_ip)
{
eth_frame_t *frame = (void*)net_buf;
ip_packet_t *ip = (void*)(frame->data);
udp_packet_t *udp = (void*)(ip->data);
ntp_message_t *ntp =(void*)(udp->data);
ip->to_addr = srv_ip;
udp->to_port = NTP_SRV_PORT;
udp->from_port = NTP_LOCAL_PORT;
// ntp.status = 8
// остальные поля заполняем нулями
ntp->udp_test_message = '3';
return udp_send(frame, sizeof(ntp_message_t));
}
Ну и соответственно вызываю полученную функцию. Подскажите, что я делаю не так? Как мне таки получить сообщение на комп. Комп видит что пакет приходит, но UDP клиент\сервер его не наблюдает. Заранее спасибо, ну видимо Vga. Чтоб я без вас делал)
А ты на компе на 123 порту слушаешь? Алсо, для своего сервера лучше выделить отдельный порт и не в диапазоне системных (0..1024) портов. Порт сервера задается в NTP_SRV_PORT.
Комментарии (41)
RSS свернуть / развернуть